straw-queue 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {straw_queue-0.1.2 → straw_queue-0.1.3}/CHANGELOG.md +10 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/CHANGELOG_zh.md +6 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/Cargo.lock +1 -1
- {straw_queue-0.1.2 → straw_queue-0.1.3}/Cargo.toml +1 -1
- {straw_queue-0.1.2 → straw_queue-0.1.3}/PKG-INFO +1 -1
- {straw_queue-0.1.2 → straw_queue-0.1.3}/pyproject.toml +1 -1
- {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/store.rs +10 -4
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_extent_visibility.py +48 -11
- {straw_queue-0.1.2 → straw_queue-0.1.3}/.github/workflows/tests.yml +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/.github/workflows/wheels.yml +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/CONTRIBUTING.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/CONTRIBUTING_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/LICENSE +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/README.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/README_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/APPLICATIONS.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/APPLICATIONS_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/BENCHMARKS.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/BENCHMARKS_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/BUILDING.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/BUILDING_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/DESIGN.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/DESIGN_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/FILESYSTEM.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/FILESYSTEM_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/FORMAT.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/FORMAT_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/PROTOCOL_AND_VERIFICATION.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/PROTOCOL_AND_VERIFICATION_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/README.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/README_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/USAGE.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/USAGE_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/VERIFICATION.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/VERIFICATION_zh.md +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/examples/multiprocess.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/examples/records.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/examples/tensors.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/examples/work_queue.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/coordinator.rs +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/gc.rs +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/journal.rs +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/lib.rs +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/payload.rs +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/python.rs +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/__init__.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/backend.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/benchmark.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/coordinator.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/errors.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/inspect.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/journal.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/protocol.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/reporting.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/rpc.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/store.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/tensor.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/tracing.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/native.rs +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_benchmark_profile.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_consumer_rewind.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_filesystem_queue.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_gc_extent_validation.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_online_gc.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_packed_store.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_read_session.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_streaming_write.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_tensor.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_unlimited_capacity.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/tools/check_wheel.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/verification/crash_campaign.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/verification/gc_cross_client.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/verification/gc_scale.py +0 -0
- {straw_queue-0.1.2 → straw_queue-0.1.3}/verification/ownership_model.py +0 -0
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
[中文版](CHANGELOG_zh.md)
|
|
4
4
|
|
|
5
|
+
## 0.1.3
|
|
6
|
+
|
|
7
|
+
- Retry temporarily zero-filled bytes anywhere in a published extent's header,
|
|
8
|
+
including partial and noncontiguous visibility gaps. Previously, only an
|
|
9
|
+
entirely zero-filled header triggered this retry. The retry limit and checksum
|
|
10
|
+
validation remain unchanged; other incorrect header bytes fail immediately.
|
|
11
|
+
|
|
12
|
+
Existing callers and stored data remain compatible. Packed-record and WAL
|
|
13
|
+
formats are unchanged.
|
|
14
|
+
|
|
5
15
|
## 0.1.2
|
|
6
16
|
|
|
7
17
|
- Stream large record payloads through bounded native write buffers. Records and
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[English](CHANGELOG.md)
|
|
4
4
|
|
|
5
|
+
## 0.1.3
|
|
6
|
+
|
|
7
|
+
- 已发布 extent 的文件头在任意位置出现暂时零值时都会触发重试,支持部分字节以及不连续位置尚不可见的情况。此前只有整个文件头为零时才会触发这类重试。重试上限和 checksum 校验保持不变;其他错误的文件头字节仍会立即报错。
|
|
8
|
+
|
|
9
|
+
已有调用方和存储数据保持兼容,packed record 和 WAL 格式不变。
|
|
10
|
+
|
|
5
11
|
## 0.1.2
|
|
6
12
|
|
|
7
13
|
- 大记录的 payload 通过有界 native 写入缓冲区分块写入。记录和批量发布不再需要适配 writer 的临时缓冲区;原有 packed record 格式和张量读取方式不变。
|
|
@@ -332,12 +332,18 @@ impl Config {
|
|
|
332
332
|
if header == HEADER {
|
|
333
333
|
if attempt > 0 {
|
|
334
334
|
eprintln!(
|
|
335
|
-
"
|
|
335
|
+
"straw extent visible after {attempt} retries; path={path:?}, offset={offset}"
|
|
336
336
|
);
|
|
337
337
|
}
|
|
338
338
|
return Ok(f);
|
|
339
339
|
}
|
|
340
|
-
|
|
340
|
+
// A header spanning cached pages can be only partly visible:
|
|
341
|
+
// missing bytes read as zero while the other bytes already match.
|
|
342
|
+
// Reopen within the same bounded window, then validate normally.
|
|
343
|
+
let zero_filled = header
|
|
344
|
+
.iter()
|
|
345
|
+
.zip(HEADER)
|
|
346
|
+
.all(|(observed, expected)| *observed == 0 || observed == expected);
|
|
341
347
|
(
|
|
342
348
|
Error::new(
|
|
343
349
|
"CorruptData",
|
|
@@ -347,7 +353,7 @@ impl Config {
|
|
|
347
353
|
hex(HEADER)
|
|
348
354
|
),
|
|
349
355
|
),
|
|
350
|
-
|
|
356
|
+
zero_filled,
|
|
351
357
|
)
|
|
352
358
|
};
|
|
353
359
|
// Close before sleeping so the next attempt gets a fresh open and
|
|
@@ -358,7 +364,7 @@ impl Config {
|
|
|
358
364
|
}
|
|
359
365
|
let delay = EXTENT_VISIBILITY_DELAYS_MS[attempt];
|
|
360
366
|
eprintln!(
|
|
361
|
-
"
|
|
367
|
+
"straw extent visibility retry {}/{} after {delay}ms: {error}",
|
|
362
368
|
attempt + 1,
|
|
363
369
|
EXTENT_VISIBILITY_DELAYS_MS.len()
|
|
364
370
|
);
|
|
@@ -11,13 +11,16 @@ from straw.errors import CorruptData
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@contextmanager
|
|
14
|
-
def delayed_header(root, segment):
|
|
14
|
+
def delayed_header(root, segment, zero_positions=range(12)):
|
|
15
15
|
path = root / segment.path
|
|
16
16
|
with path.open("r+b") as f:
|
|
17
17
|
f.seek(segment.offset)
|
|
18
18
|
header = f.read(12)
|
|
19
19
|
f.seek(segment.offset)
|
|
20
|
-
|
|
20
|
+
visible_header = bytearray(header)
|
|
21
|
+
for position in zero_positions:
|
|
22
|
+
visible_header[position] = 0
|
|
23
|
+
f.write(visible_header)
|
|
21
24
|
f.flush()
|
|
22
25
|
os.fsync(f.fileno())
|
|
23
26
|
|
|
@@ -37,7 +40,20 @@ def delayed_header(root, segment):
|
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
@pytest.mark.parametrize("entry", ["inspect", "read_record", "read", "validate", "session_read", "dependency"])
|
|
40
|
-
|
|
43
|
+
@pytest.mark.parametrize(
|
|
44
|
+
"zero_positions",
|
|
45
|
+
[
|
|
46
|
+
tuple(range(12)),
|
|
47
|
+
tuple(range(4)),
|
|
48
|
+
tuple(range(4, 12)),
|
|
49
|
+
(3, 4, 5),
|
|
50
|
+
(0, 3, 6, 8),
|
|
51
|
+
# The last three header bytes are already zero. Exercise each nonzero
|
|
52
|
+
# byte independently as well as contiguous and scattered gaps above.
|
|
53
|
+
*((position,) for position in range(9)),
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
def test_published_zero_header_is_reopened_and_revalidated(tmp_path, capfd, entry, zero_positions):
|
|
41
57
|
writer = SharedFilesystemStore(tmp_path, "visibility")
|
|
42
58
|
writer.publish([Record("prefix", b"prefix")], submission_id="prefix")
|
|
43
59
|
ref = writer.publish([Record("result", b"immutable payload")], submission_id="result")
|
|
@@ -46,7 +62,7 @@ def test_published_zero_header_is_reopened_and_revalidated(tmp_path, capfd, entr
|
|
|
46
62
|
parent = writer.publish([Record("parent", b"parent")], dependencies=[ref], submission_id="parent")
|
|
47
63
|
reader = SharedFilesystemStore(tmp_path, "visibility")
|
|
48
64
|
assert ref.manifest.segment.offset > 0
|
|
49
|
-
with delayed_header(tmp_path, ref.manifest.segment):
|
|
65
|
+
with delayed_header(tmp_path, ref.manifest.segment, zero_positions):
|
|
50
66
|
if entry == "inspect":
|
|
51
67
|
reader.inspect_segment(ref.manifest.segment, verify_payload=True)
|
|
52
68
|
elif entry == "read_record":
|
|
@@ -60,30 +76,51 @@ def test_published_zero_header_is_reopened_and_revalidated(tmp_path, capfd, entr
|
|
|
60
76
|
else:
|
|
61
77
|
with reader.read_session() as session:
|
|
62
78
|
assert list(session.read(ref))[0].payload == b"immutable payload"
|
|
63
|
-
assert "
|
|
79
|
+
assert "straw extent visibility retry" in capfd.readouterr().err
|
|
64
80
|
|
|
65
81
|
|
|
66
|
-
|
|
82
|
+
@pytest.mark.parametrize("zero_positions", [tuple(range(12)), tuple(range(4)), (3, 4, 5), (0, 3, 6, 8)])
|
|
83
|
+
def test_persistent_zero_header_still_fails_after_bounded_retries(tmp_path, capfd, zero_positions):
|
|
67
84
|
store = SharedFilesystemStore(tmp_path, "persistent")
|
|
68
85
|
ref = store.publish([Record("r", b"payload")], submission_id="r")
|
|
69
86
|
segment = ref.manifest.segment
|
|
70
87
|
with (tmp_path / segment.path).open("r+b") as f:
|
|
71
|
-
|
|
72
|
-
|
|
88
|
+
for position in zero_positions:
|
|
89
|
+
f.seek(segment.offset + position)
|
|
90
|
+
f.write(b"\x00")
|
|
73
91
|
with pytest.raises(CorruptData, match="Invalid segment header"):
|
|
74
92
|
store.validate(ref)
|
|
75
93
|
log = capfd.readouterr().err
|
|
76
|
-
assert log.count("
|
|
94
|
+
assert log.count("straw extent visibility retry") == 5
|
|
77
95
|
assert "visible after" not in log
|
|
78
96
|
|
|
79
97
|
|
|
80
|
-
|
|
98
|
+
@pytest.mark.parametrize("corrupt_position", range(12))
|
|
99
|
+
def test_nonzero_corruption_is_not_treated_as_delayed_publication(tmp_path, capfd, corrupt_position):
|
|
81
100
|
store = SharedFilesystemStore(tmp_path, "corrupt")
|
|
82
101
|
ref = store.publish([Record("r", b"payload")], submission_id="r")
|
|
83
102
|
segment = ref.manifest.segment
|
|
84
103
|
with (tmp_path / segment.path).open("r+b") as f:
|
|
85
104
|
f.seek(segment.offset)
|
|
86
|
-
f.write(
|
|
105
|
+
f.write(bytes(12))
|
|
106
|
+
f.seek(segment.offset + corrupt_position)
|
|
107
|
+
f.write(b"\xff")
|
|
87
108
|
with pytest.raises(CorruptData, match="Invalid segment header"):
|
|
88
109
|
store.validate(ref)
|
|
89
110
|
assert "visibility retry" not in capfd.readouterr().err
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_recovered_header_does_not_bypass_payload_validation(tmp_path, capfd):
|
|
114
|
+
store = SharedFilesystemStore(tmp_path, "corrupt-payload")
|
|
115
|
+
ref = store.publish([Record("r", b"immutable payload")], submission_id="r")
|
|
116
|
+
segment = ref.manifest.segment
|
|
117
|
+
path = tmp_path / segment.path
|
|
118
|
+
data = path.read_bytes()
|
|
119
|
+
payload_offset = data.index(b"immutable payload")
|
|
120
|
+
with path.open("r+b") as f:
|
|
121
|
+
f.seek(payload_offset)
|
|
122
|
+
f.write(b"X")
|
|
123
|
+
with delayed_header(tmp_path, segment, range(4)):
|
|
124
|
+
with pytest.raises(CorruptData, match="checksum mismatch"):
|
|
125
|
+
store.inspect_segment(segment, verify_payload=True)
|
|
126
|
+
assert "visibility retry" in capfd.readouterr().err
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|