1
0
Fork 0
mirror of https://github.com/borgbackup/borg.git synced 2024-12-28 02:38:43 +00:00

avoid stale filehandle issues, fixes #3265

This commit is contained in:
Thomas Waldmann 2018-06-13 02:43:34 +02:00
parent 86a91af67a
commit 5b5546d7e9
3 changed files with 34 additions and 7 deletions

View file

@ -52,6 +52,8 @@
DEFAULT_SEGMENTS_PER_DIR = 1000 DEFAULT_SEGMENTS_PER_DIR = 1000
FD_MAX_AGE = 4 * 60 # 4 minutes
CHUNK_MIN_EXP = 19 # 2**19 == 512kiB CHUNK_MIN_EXP = 19 # 2**19 == 512kiB
CHUNK_MAX_EXP = 23 # 2**23 == 8MiB CHUNK_MAX_EXP = 23 # 2**23 == 8MiB
HASH_WINDOW_SIZE = 0xfff # 4095B HASH_WINDOW_SIZE = 0xfff # 4095B

View file

@ -39,6 +39,12 @@ def get(self, key, default=None):
self._lru.append(key) self._lru.append(key)
return value return value
def upd(self, key, value):
# special use only: update the value for an existing key without having to dispose it first
# this method complements __setitem__ which should be used for the normal use case.
assert key in self._cache, "Unexpected attempt to update a non-existing item."
self._cache[key] = value
def clear(self): def clear(self):
for value in self._cache.values(): for value in self._cache.values():
self._dispose(value) self._dispose(value)

View file

@ -3,6 +3,7 @@
import os import os
import shutil import shutil
import struct import struct
import time
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from collections import defaultdict from collections import defaultdict
from configparser import ConfigParser from configparser import ConfigParser
@ -1135,8 +1136,7 @@ class SegmentFull(Exception):
def __init__(self, path, limit, segments_per_dir, capacity=90): def __init__(self, path, limit, segments_per_dir, capacity=90):
self.path = path self.path = path
self.fds = LRUCache(capacity, self.fds = LRUCache(capacity, dispose=self._close_fd)
dispose=self.close_fd)
self.segment = 0 self.segment = 0
self.limit = limit self.limit = limit
self.segments_per_dir = segments_per_dir self.segments_per_dir = segments_per_dir
@ -1148,7 +1148,8 @@ def close(self):
self.fds.clear() self.fds.clear()
self.fds = None # Just to make sure we're disabled self.fds = None # Just to make sure we're disabled
def close_fd(self, fd): def _close_fd(self, ts_fd):
ts, fd = ts_fd
safe_fadvise(fd.fileno(), 0, 0, 'DONTNEED') safe_fadvise(fd.fileno(), 0, 0, 'DONTNEED')
fd.close() fd.close()
@ -1262,11 +1263,29 @@ def get_write_fd(self, no_new=False, raise_full=False):
return self._write_fd return self._write_fd
def get_fd(self, segment): def get_fd(self, segment):
try: # note: get_fd() returns a fd with undefined file pointer position,
return self.fds[segment] # so callers must always seek() to desired position afterwards.
except KeyError: now = time.monotonic()
def open_fd():
fd = open(self.segment_filename(segment), 'rb') fd = open(self.segment_filename(segment), 'rb')
self.fds[segment] = fd self.fds[segment] = (now, fd)
return fd
try:
ts, fd = self.fds[segment]
except KeyError:
fd = open_fd()
else:
if now - ts > FD_MAX_AGE:
# we do not want to touch long-unused file handles to
# avoid ESTALE issues (e.g. on network filesystems).
del self.fds[segment]
fd = open_fd()
else:
# fd is fresh enough, so we use it.
# also, we update the timestamp of the lru cache entry.
self.fds.upd(segment, (now, fd))
return fd return fd
def close_segment(self): def close_segment(self):