Skip to content

Commit 13a3472

Browse files
Merge pull request #10603 from ThomasWaldmann/chunker-review-fixes
chunkers: review fixes (chunking_time precision, param checks, cleanups)
2 parents dce3632 + 94ac75b commit 13a3472

16 files changed

Lines changed: 117 additions & 59 deletions

‎src/borg/chunkers/base.pxd‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ cdef class ChunkerBase:
1818
cdef int done, eof
1919
cdef size_t min_size, buf_size, remaining, position, last
2020
cdef long long bytes_read, bytes_yielded
21-
cdef readonly float chunking_time
21+
cdef readonly double chunking_time # a sum of many small increments, a C float would stop growing
2222
cdef object file_reader
2323
cdef size_t reader_block_size
2424
cdef bint sparse

‎src/borg/chunkers/base.pyx‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ cdef class ChunkerBase:
5050
"""
5151
min_size = 1 << chunk_min_exp
5252
max_size = 1 << chunk_max_exp
53-
assert max_size <= len(zeros)
54-
assert min_size + 1 <= max_size, "too small max_size"
53+
# The parameter checks here and in the subclasses raise ValueError rather than
54+
# assert: the scan code relies on them for memory safety and python -O drops asserts.
55+
if max_size > len(zeros):
56+
raise ValueError("too big max_size")
57+
if min_size + 1 > max_size:
58+
raise ValueError("too small max_size")
5559

5660
self.algo = algo
5761
self.chunk_mask = _mask_bits(hash_mask_bits, high_masks)
@@ -61,9 +65,10 @@ cdef class ChunkerBase:
6165
# probability). This concentrates chunk sizes around the target and reduces
6266
# chunk-size variance. nc_level == 0 disables it, keeping behavior byte-identical
6367
# to the single-mask chunker.
64-
assert nc_level >= 0
65-
assert hash_mask_bits - nc_level >= 1, "nc_level too large for hash_mask_bits"
66-
assert hash_mask_bits + nc_level <= 48, "nc_level too large for hash_mask_bits"
68+
if nc_level < 0:
69+
raise ValueError("nc_level must not be negative")
70+
if hash_mask_bits - nc_level < 1 or hash_mask_bits + nc_level > 48:
71+
raise ValueError("nc_level too large for hash_mask_bits")
6772
self.nc_level = nc_level
6873
if nc_level:
6974
self.mask_s = _mask_bits(hash_mask_bits + nc_level, high_masks)
@@ -152,10 +157,9 @@ cdef class ChunkerBase:
152157
cdef uint64_t digest = 0, mask, mask_s = self.mask_s, mask_l = self.mask_l
153158
cdef int nc_level = self.nc_level
154159
cdef size_t n, old_last, min_size = self.min_size
155-
cdef size_t normal_size = self.normal_size, normal_pos, chunk_len, span, did
160+
cdef size_t normal_size = self.normal_size, chunk_len, span, did
156161
cdef int64_t r
157162
cdef uint8_t* p
158-
cdef uint8_t* stop
159163

160164
if self.done:
161165
if self.bytes_read == self.bytes_yielded:
@@ -202,14 +206,13 @@ cdef class ChunkerBase:
202206
continue
203207

204208
p = self.data + self.position
205-
stop = p + self.remaining
206-
if nc_level and chunk_len < normal_size:
207-
# do not scan past the strict->loose transition; re-evaluate the mask there
208-
normal_pos = self.last + normal_size
209-
if (self.data + normal_pos) < stop:
210-
stop = self.data + normal_pos
211-
212-
span = stop - p
209+
span = self.remaining
210+
if nc_level and chunk_len < normal_size and normal_size - chunk_len < span:
211+
# do not scan past the strict->loose transition; re-evaluate the mask there.
212+
# This compares lengths, not positions: normal_size is a caller-given size_t
213+
# and can be anything, self.last + normal_size might not even be representable.
214+
span = normal_size - chunk_len
215+
213216
r = self._scan(p, span, &digest, mask)
214217
if r == -2:
215218
raise Exception(f"{self.algo}: OpenSSL EVP encryption failed")

‎src/borg/chunkers/buzhash.pyx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ cdef class Chunker(ChunkerBase):
138138
def __cinit__(self, int seed, int chunk_min_exp, int chunk_max_exp, int hash_mask_bits, int hash_window_size, bint sparse=False):
139139
self.table = NULL
140140
# see process, first while loop condition, first term must be able to get True:
141-
assert hash_window_size + (1 << chunk_min_exp) + 1 <= (1 << chunk_max_exp), "too small max_size"
141+
if hash_window_size < 0:
142+
raise ValueError("hash_window_size must not be negative")
143+
if hash_window_size + (1 << chunk_min_exp) + 1 > (1 << chunk_max_exp):
144+
raise ValueError("too small max_size")
142145
self.window_size = hash_window_size
143146
self.table = buzhash_init_table(seed & 0xffffffff)
144147
# buzhash is single-mask (no normalized chunking): nc_level 0 makes
@@ -167,7 +170,7 @@ cdef class Chunker(ChunkerBase):
167170
else:
168171
raise Exception("chunkifier byte count mismatch")
169172

170-
while self.remaining < min_size + window_size + 1 and not self.eof: # see assert in Chunker init
173+
while self.remaining < min_size + window_size + 1 and not self.eof: # see check in __cinit__
171174
self.fill()
172175

173176
# Here we either are at eof...

‎src/borg/chunkers/buzhash64.pyx‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ cdef class ChunkerBuzHash64(ChunkerBase):
150150
self.table = NULL
151151
self.table_rot = NULL
152152
# see process, first while loop condition, first term must be able to get True:
153-
assert hash_window_size + (1 << chunk_min_exp) + 1 <= (1 << chunk_max_exp), "too small max_size"
153+
if hash_window_size < 0:
154+
raise ValueError("hash_window_size must not be negative")
155+
if hash_window_size + (1 << chunk_min_exp) + 1 > (1 << chunk_max_exp):
156+
raise ValueError("too small max_size")
154157

155158
self.window_size = hash_window_size
156159
self.table = buzhash64_init_table(key)
@@ -191,10 +194,8 @@ cdef class ChunkerBuzHash64(ChunkerBase):
191194
cdef uint64_t mask_s = self.mask_s, mask_l = self.mask_l
192195
cdef int nc_level = self.nc_level
193196
cdef size_t n, old_last, min_size = self.min_size, window_size = self.window_size
194-
cdef size_t normal_size = self.normal_size, normal_pos
197+
cdef size_t normal_size = self.normal_size, chunk_len
195198
cdef uint8_t* p
196-
cdef uint8_t* stop_at
197-
cdef uint8_t* nc_stop
198199
cdef size_t did_bytes, span
199200
cdef int kernel_id = self.kernel_id
200201

@@ -204,7 +205,7 @@ cdef class ChunkerBuzHash64(ChunkerBase):
204205
else:
205206
raise Exception("chunkifier byte count mismatch")
206207

207-
while self.remaining < min_size + window_size + 1 and not self.eof: # see assert in Chunker init
208+
while self.remaining < min_size + window_size + 1 and not self.eof: # see check in __cinit__
208209
self.fill()
209210

210211
# Here we either are at eof...
@@ -231,29 +232,28 @@ cdef class ChunkerBuzHash64(ChunkerBase):
231232
# Normalized chunking: pick the mask based on how far we are into the current chunk.
232233
# While below normal_size use the strict mask (lower cut probability), afterward the
233234
# loose mask (higher cut probability). The mask is re-evaluated at the top of every
234-
# iteration, so the transition is honored exactly at normal_pos. When nc is disabled,
235-
# mask_s == mask_l == chunk_mask and the normal_pos cap is not applied, so this reduces
235+
# iteration, so the transition is honored exactly at normal_size. When nc is disabled,
236+
# mask_s == mask_l == chunk_mask and the normal_size cap is not applied, so this reduces
236237
# to the original single-mask behavior.
237238
mask = mask_s
238-
normal_pos = 0
239+
chunk_len = 0
239240
while True:
240241
if nc_level:
241-
normal_pos = self.last + normal_size
242-
mask = mask_s if self.position < normal_pos else mask_l
242+
chunk_len = self.position - self.last
243+
mask = mask_s if chunk_len < normal_size else mask_l
243244

244245
if not (self.remaining > window_size and (sum & mask) and not (self.eof and self.remaining <= window_size)):
245246
break
246247

247248
p = self.data + self.position
248-
stop_at = p + self.remaining - window_size
249+
span = self.remaining - window_size
249250

250-
if nc_level and self.position < normal_pos:
251-
# do not scan past the strict->loose transition; re-evaluate the mask there
252-
nc_stop = self.data + normal_pos
253-
if nc_stop < stop_at:
254-
stop_at = nc_stop
251+
if nc_level and chunk_len < normal_size and normal_size - chunk_len < span:
252+
# do not scan past the strict->loose transition; re-evaluate the mask there.
253+
# This compares lengths, not positions: normal_size is a caller-given size_t
254+
# and can be anything, self.last + normal_size might not even be representable.
255+
span = normal_size - chunk_len
255256

256-
span = stop_at - p
257257
with nogil:
258258
did_bytes = bz64_scan(self.table, self.table_rot, p, p + window_size,
259259
span, &sum, mask, kernel_id)

‎src/borg/chunkers/buzhash64_impl.c‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,14 +428,10 @@ int bz64_kernel_select(const char *name, int *out_id)
428428
return BZ_KSEL_OK;
429429
}
430430
if (strcmp(name, "avx512") == 0) {
431-
#ifndef BZ_KIND_512
432-
return BZ_KSEL_NOTBUILT; /* compiler too old for the target attribute */
433-
#else
434431
if (!__builtin_cpu_supports("avx512f"))
435432
return BZ_KSEL_NOCPU;
436433
*out_id = BZ_K_VECTOR512;
437434
return BZ_KSEL_OK;
438-
#endif
439435
}
440436
#endif
441437
return BZ_KSEL_UNKNOWN;

‎src/borg/chunkers/buzhash64_impl.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
/* Results of bz64_kernel_select(). */
3333
#define BZ_KSEL_OK 0
3434
#define BZ_KSEL_UNKNOWN 1 /* not a kernel name on this platform */
35-
#define BZ_KSEL_NOTBUILT 2 /* known, but not compiled into this binary */
35+
#define BZ_KSEL_NOTBUILT 2 /* known, but not compiled into this binary (never returned, see fastcdc_impl.h) */
3636
#define BZ_KSEL_NOCPU 3 /* known and built, but this CPU cannot run it */
3737

3838
/* Resolve a kernel name for this build; see fc_kernel_select(). */

‎src/borg/chunkers/fastcdc_impl.c‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -409,14 +409,10 @@ int fc_kernel_select(const char *name, int *out_id)
409409
return FC_KSEL_OK;
410410
}
411411
if (strcmp(name, "avx512") == 0) {
412-
#ifndef FC_KIND_512
413-
return FC_KSEL_NOTBUILT; /* compiler too old for the target attribute */
414-
#else
415412
if (!__builtin_cpu_supports("avx512f"))
416413
return FC_KSEL_NOCPU;
417414
*out_id = FC_K_VECTOR512;
418415
return FC_KSEL_OK;
419-
#endif
420416
}
421417
#endif
422418
return FC_KSEL_UNKNOWN;

‎src/borg/chunkers/fastcdc_impl.h‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@
2828
/* Results of fc_kernel_select(). */
2929
#define FC_KSEL_OK 0
3030
#define FC_KSEL_UNKNOWN 1 /* not a kernel name on this platform */
31-
#define FC_KSEL_NOTBUILT 2 /* known, but not compiled into this binary */
31+
#define FC_KSEL_NOTBUILT 2 /* known, but not compiled into this binary (see below) */
3232
#define FC_KSEL_NOCPU 3 /* known and built, but this CPU cannot run it */
3333

3434
/* Resolve a kernel name for this build. On FC_KSEL_OK the id is stored in
35-
* *out_id, otherwise *out_id is left alone. The three failures are kept apart
36-
* because they need different fixes: a typo, too old a compiler, or the wrong
37-
* CPU. */
35+
* *out_id, otherwise *out_id is left alone. The failures are kept apart
36+
* because they need different fixes: a typo or the wrong CPU.
37+
* FC_KSEL_NOTBUILT (too old a compiler) is never returned: a compiler that can
38+
* build this file at all builds all of a platform's kernels. The code exists
39+
* because the kernel selectors share their result codes (see kernel_env.py)
40+
* and the AES chunkers' selector does return it. */
3841
int fc_kernel_select(const char *name, int *out_id);
3942

4043
/* Comma-separated list of the kernel names this build accepts, for error

‎src/borg/chunkers/goldilocks_aes_impl.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ uint64_t gl_digest64(const GL_CTX *c, const uint8_t *q)
171171
#define PH_DIGEST64 gl_digest64
172172
#include "phte_scan.h"
173173

174-
GL_CTX *gl_new(const uint64_t tables[GL_TABLES * 256], uint64_t k1, uint64_t k2,
174+
GL_CTX *gl_new(const uint64_t *tables, uint64_t k1, uint64_t k2,
175175
const uint8_t aes_key[16], int kernel)
176176
{
177177
GL_CTX *c = calloc(1, sizeof(GL_CTX));

‎src/borg/chunkers/phte_chunker.pyx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ cdef class ChunkerPHTE(ChunkerBase):
3333
# the rolling window needs 64 bytes of in-chunk history at the first
3434
# possible cut position (min_size), so the minimum chunk size must be
3535
# at least the window size:
36-
assert chunk_min_exp >= 6, "chunk_min_exp must be >= 6 (2^6 = 64 = window size)"
36+
if chunk_min_exp < 6:
37+
raise ValueError("chunk_min_exp must be >= 6 (2^6 = 64 = window size)")
3738
self.kernel_str = "unknown"
3839
# the AES output is uniform, so contiguous low-bit masks are fine
3940
return self._setup_common(algo, chunk_min_exp, chunk_max_exp, hash_mask_bits,

0 commit comments

Comments
 (0)