Skip to content

Commit

Permalink
remove the repository.flags call / feature
Browse files Browse the repository at this point in the history
this heavily depended on having a repository index where the flags get stored.

we don't have that with borgstore.
  • Loading branch information
ThomasWaldmann committed Aug 20, 2024
1 parent 05ca6fe commit 3ca014d
Show file tree
Hide file tree
Showing 8 changed files with 27 additions and 214 deletions.
54 changes: 12 additions & 42 deletions src/borg/hashindex.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ NSIndexEntry = namedtuple('NSIndexEntry', 'segment offset size')

cdef class NSIndex(IndexBase):

value_size = 16
value_size = 12

def __getitem__(self, key):
assert len(key) == self.key_size
Expand All @@ -209,13 +209,13 @@ cdef class NSIndex(IndexBase):

def __setitem__(self, key, value):
assert len(key) == self.key_size
cdef uint32_t[4] data
cdef uint32_t[3] data
assert len(value) == len(data)
cdef uint32_t segment = value[0]
assert segment <= _MAX_VALUE, "maximum number of segments reached"
data[0] = _htole32(segment)
data[1] = _htole32(value[1])
data[2] = _htole32(value[2])
data[3] = 0 # init flags to all cleared
if not hashindex_set(self.index, <unsigned char *>key, data):
raise Exception('hashindex_set failed')

Expand All @@ -228,12 +228,10 @@ cdef class NSIndex(IndexBase):
assert segment <= _MAX_VALUE, "maximum number of segments reached"
return data != NULL

def iteritems(self, marker=None, mask=0, value=0):
def iteritems(self, marker=None):
"""iterate over all items or optionally only over items having specific flag values"""
cdef const unsigned char *key
assert isinstance(mask, int)
assert isinstance(value, int)
iter = NSKeyIterator(self.key_size, mask, value)
iter = NSKeyIterator(self.key_size)
iter.idx = self
iter.index = self.index
if marker:
Expand All @@ -243,36 +241,17 @@ cdef class NSIndex(IndexBase):
iter.key = key - self.key_size
return iter

def flags(self, key, mask=0xFFFFFFFF, value=None):
"""query and optionally set flags"""
assert len(key) == self.key_size
assert isinstance(mask, int)
data = <uint32_t *>hashindex_get(self.index, <unsigned char *>key)
if not data:
raise KeyError(key)
flags = _le32toh(data[3])
if isinstance(value, int):
new_flags = flags & ~mask # clear masked bits
new_flags |= value & mask # set value bits
data[3] = _htole32(new_flags)
return flags & mask # always return previous flags value


cdef class NSKeyIterator:
cdef NSIndex idx
cdef HashIndex *index
cdef const unsigned char *key
cdef int key_size
cdef int exhausted
cdef unsigned int flag_mask
cdef unsigned int flag_value

def __cinit__(self, key_size, mask, value):
def __cinit__(self, key_size):
self.key = NULL
self.key_size = key_size
# note: mask and value both default to 0, so they will match all entries
self.flag_mask = _htole32(mask)
self.flag_value = _htole32(value)
self.exhausted = 0

def __iter__(self):
Expand All @@ -282,16 +261,11 @@ cdef class NSKeyIterator:
cdef uint32_t *value
if self.exhausted:
raise StopIteration
while True:
self.key = hashindex_next_key(self.index, <unsigned char *>self.key)
if not self.key:
self.exhausted = 1
raise StopIteration
value = <uint32_t *> (self.key + self.key_size)
if value[3] & self.flag_mask == self.flag_value:
# we found a matching entry!
break

self.key = hashindex_next_key(self.index, <unsigned char *>self.key)
if not self.key:
self.exhausted = 1
raise StopIteration
value = <uint32_t *> (self.key + self.key_size)
cdef uint32_t segment = _le32toh(value[0])
assert segment <= _MAX_VALUE, "maximum number of segments reached"
return ((<char *>self.key)[:self.key_size],
Expand Down Expand Up @@ -331,9 +305,8 @@ cdef class NSIndex1(IndexBase): # legacy borg 1.x
assert segment <= _MAX_VALUE, "maximum number of segments reached"
return data != NULL

def iteritems(self, marker=None, mask=0, value=0):
def iteritems(self, marker=None):
cdef const unsigned char *key
assert mask == 0 and value == 0, "using mask/value is not supported for old index"
iter = NSKeyIterator1(self.key_size)
iter.idx = self
iter.index = self.index
Expand All @@ -344,9 +317,6 @@ cdef class NSIndex1(IndexBase): # legacy borg 1.x
iter.key = key - self.key_size
return iter

def flags(self, key, mask=0xFFFFFFFF, value=None):
raise NotImplemented("calling .flags() is not supported for old index")


cdef class NSKeyIterator1: # legacy borg 1.x
cdef NSIndex1 idx
Expand Down
18 changes: 2 additions & 16 deletions src/borg/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ class RepositoryServer: # pragma: no cover
"commit",
"delete",
"destroy",
"flags",
"flags_many",
"get",
"list",
"negotiate",
Expand Down Expand Up @@ -984,20 +982,8 @@ def destroy(self):
def __len__(self):
"""actual remoting is done via self.call in the @api decorator"""

@api(
since=parse_version("1.0.0"),
mask={"since": parse_version("2.0.0b2"), "previously": 0},
value={"since": parse_version("2.0.0b2"), "previously": 0},
)
def list(self, limit=None, marker=None, mask=0, value=0):
"""actual remoting is done via self.call in the @api decorator"""

@api(since=parse_version("2.0.0b2"))
def flags(self, id, mask=0xFFFFFFFF, value=None):
"""actual remoting is done via self.call in the @api decorator"""

@api(since=parse_version("2.0.0b2"))
def flags_many(self, ids, mask=0xFFFFFFFF, value=None):
@api(since=parse_version("1.0.0"))
def list(self, limit=None, marker=None):
"""actual remoting is done via self.call in the @api decorator"""

def get(self, id, read_data=True):
Expand Down
10 changes: 2 additions & 8 deletions src/borg/remote3.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ class RepositoryServer: # pragma: no cover
"commit",
"delete",
"destroy",
"flags",
"flags_many",
"get",
"list",
"negotiate",
Expand Down Expand Up @@ -1021,12 +1019,8 @@ def destroy(self):
def __len__(self):
"""actual remoting is done via self.call in the @api decorator"""

@api(
since=parse_version("1.0.0"),
mask={"since": parse_version("2.0.0b2"), "previously": 0},
value={"since": parse_version("2.0.0b2"), "previously": 0},
)
def list(self, limit=None, marker=None, mask=0, value=0):
@api(since=parse_version("1.0.0"))
def list(self, limit=None, marker=None):
"""actual remoting is done via self.call in the @api decorator"""

def get(self, id, read_data=True):
Expand Down
22 changes: 2 additions & 20 deletions src/borg/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -1207,31 +1207,13 @@ def __contains__(self, id):
self.index = self.open_index(self.get_transaction_id())
return id in self.index

def list(self, limit=None, marker=None, mask=0, value=0):
def list(self, limit=None, marker=None):
"""
list <limit> IDs starting from after id <marker> - in index (pseudo-random) order.
if mask and value are given, only return IDs where flags & mask == value (default: all IDs).
"""
if not self.index:
self.index = self.open_index(self.get_transaction_id())
return [id_ for id_, _ in islice(self.index.iteritems(marker=marker, mask=mask, value=value), limit)]

def flags(self, id, mask=0xFFFFFFFF, value=None):
"""
query and optionally set flags
:param id: id (key) of object
:param mask: bitmask for flags (default: operate on all 32 bits)
:param value: value to set masked bits to (default: do not change any flags)
:return: (previous) flags value (only masked bits)
"""
if not self.index:
self.index = self.open_index(self.get_transaction_id())
return self.index.flags(id, mask, value)

def flags_many(self, ids, mask=0xFFFFFFFF, value=None):
return [self.flags(id_, mask, value) for id_ in ids]
return [id_ for id_, _ in islice(self.index.iteritems(marker=marker), limit)]

def get(self, id, read_data=True):
if not self.index:
Expand Down
6 changes: 2 additions & 4 deletions src/borg/repository3.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,9 @@ def __len__(self):
def __contains__(self, id):
raise NotImplementedError

def list(self, limit=None, marker=None, mask=0, value=0):
def list(self, limit=None, marker=None):
"""
list <limit> IDs starting from after id <marker> - in index (pseudo-random) order.
if mask and value are given, only return IDs where flags & mask == value (default: all IDs).
list <limit> IDs starting from after id <marker>.
"""
self._lock_refresh()
infos = self.store.list("data") # XXX we can only get the full list from the store
Expand Down
2 changes: 1 addition & 1 deletion src/borg/selftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
ChunkerTestCase,
]

SELFTEST_COUNT = 32
SELFTEST_COUNT = 30


class SelfTestResult(TestResult):
Expand Down
76 changes: 6 additions & 70 deletions src/borg/testsuite/hashindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def _generic_test(self, cls, make_value, sha):

def test_nsindex(self):
self._generic_test(
NSIndex, lambda x: (x, x, x), "0d7880dbe02b64f03c471e60e193a1333879b4f23105768b10c9222accfeac5e"
NSIndex, lambda x: (x, x, x), "640b909cf07884cc11fdf5431ffc27dee399770ceadecce31dffecd130a311a3"
)

def test_chunkindex(self):
Expand All @@ -102,7 +102,7 @@ def test_resize(self):
initial_size = os.path.getsize(filepath)
self.assert_equal(len(idx), 0)
for x in range(n):
idx[H(x)] = x, x, x, x
idx[H(x)] = x, x, x
idx.write(filepath)
assert initial_size < os.path.getsize(filepath)
for x in range(n):
Expand All @@ -114,7 +114,7 @@ def test_resize(self):
def test_iteritems(self):
idx = NSIndex()
for x in range(100):
idx[H(x)] = x, x, x, x
idx[H(x)] = x, x, x
iterator = idx.iteritems()
all = list(iterator)
self.assert_equal(len(all), 100)
Expand All @@ -141,70 +141,6 @@ def test_chunkindex_merge(self):
assert idx1[H(3)] == (3, 300)
assert idx1[H(4)] == (6, 400)

def test_flags(self):
idx = NSIndex()
key = H(0)
self.assert_raises(KeyError, idx.flags, key, 0)
idx[key] = 0, 0, 0 # create entry
# check bit 0 and 1, should be both 0 after entry creation
self.assert_equal(idx.flags(key, mask=3), 0)
# set bit 0
idx.flags(key, mask=1, value=1)
self.assert_equal(idx.flags(key, mask=1), 1)
# set bit 1
idx.flags(key, mask=2, value=2)
self.assert_equal(idx.flags(key, mask=2), 2)
# check both bit 0 and 1, both should be set
self.assert_equal(idx.flags(key, mask=3), 3)
# clear bit 1
idx.flags(key, mask=2, value=0)
self.assert_equal(idx.flags(key, mask=2), 0)
# clear bit 0
idx.flags(key, mask=1, value=0)
self.assert_equal(idx.flags(key, mask=1), 0)
# check both bit 0 and 1, both should be cleared
self.assert_equal(idx.flags(key, mask=3), 0)

def test_flags_iteritems(self):
idx = NSIndex()
keys_flagged0 = {H(i) for i in (1, 2, 3, 42)}
keys_flagged1 = {H(i) for i in (11, 12, 13, 142)}
keys_flagged2 = {H(i) for i in (21, 22, 23, 242)}
keys_flagged3 = {H(i) for i in (31, 32, 33, 342)}
for key in keys_flagged0:
idx[key] = 0, 0, 0 # create entry
idx.flags(key, mask=3, value=0) # not really necessary, unflagged is default
for key in keys_flagged1:
idx[key] = 0, 0, 0 # create entry
idx.flags(key, mask=3, value=1)
for key in keys_flagged2:
idx[key] = 0, 0, 0 # create entry
idx.flags(key, mask=3, value=2)
for key in keys_flagged3:
idx[key] = 0, 0, 0 # create entry
idx.flags(key, mask=3, value=3)
# check if we can iterate over all items
k_all = {k for k, v in idx.iteritems()}
self.assert_equal(k_all, keys_flagged0 | keys_flagged1 | keys_flagged2 | keys_flagged3)
# check if we can iterate over the flagged0 items
k0 = {k for k, v in idx.iteritems(mask=3, value=0)}
self.assert_equal(k0, keys_flagged0)
# check if we can iterate over the flagged1 items
k1 = {k for k, v in idx.iteritems(mask=3, value=1)}
self.assert_equal(k1, keys_flagged1)
# check if we can iterate over the flagged2 items
k1 = {k for k, v in idx.iteritems(mask=3, value=2)}
self.assert_equal(k1, keys_flagged2)
# check if we can iterate over the flagged3 items
k1 = {k for k, v in idx.iteritems(mask=3, value=3)}
self.assert_equal(k1, keys_flagged3)
# check if we can iterate over the flagged1 + flagged3 items
k1 = {k for k, v in idx.iteritems(mask=1, value=1)}
self.assert_equal(k1, keys_flagged1 | keys_flagged3)
# check if we can iterate over the flagged0 + flagged2 items
k1 = {k for k, v in idx.iteritems(mask=1, value=0)}
self.assert_equal(k1, keys_flagged0 | keys_flagged2)


class HashIndexExtraTestCase(BaseTestCase):
"""These tests are separate because they should not become part of the selftest."""
Expand Down Expand Up @@ -553,9 +489,9 @@ class NSIndexTestCase(BaseTestCase):
def test_nsindex_segment_limit(self):
idx = NSIndex()
with self.assert_raises(AssertionError):
idx[H(1)] = NSIndex.MAX_VALUE + 1, 0, 0, 0
idx[H(1)] = NSIndex.MAX_VALUE + 1, 0, 0
assert H(1) not in idx
idx[H(2)] = NSIndex.MAX_VALUE, 0, 0, 0
idx[H(2)] = NSIndex.MAX_VALUE, 0, 0
assert H(2) in idx


Expand Down Expand Up @@ -583,7 +519,7 @@ def HH(x, y, z):
for y in range(700): # stay below max load not to trigger resize
idx[HH(0, y, 0)] = (0, y, 0)

assert idx.size() == 1024 + 1031 * 48 # header + 1031 buckets
assert idx.size() == 1024 + 1031 * 44 # header + 1031 buckets

# delete lots of the collisions, creating lots of tombstones
for y in range(400): # stay above min load not to trigger resize
Expand Down
Loading

0 comments on commit 3ca014d

Please sign in to comment.