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.
Files changed (74) hide show
  1. {straw_queue-0.1.2 → straw_queue-0.1.3}/CHANGELOG.md +10 -0
  2. {straw_queue-0.1.2 → straw_queue-0.1.3}/CHANGELOG_zh.md +6 -0
  3. {straw_queue-0.1.2 → straw_queue-0.1.3}/Cargo.lock +1 -1
  4. {straw_queue-0.1.2 → straw_queue-0.1.3}/Cargo.toml +1 -1
  5. {straw_queue-0.1.2 → straw_queue-0.1.3}/PKG-INFO +1 -1
  6. {straw_queue-0.1.2 → straw_queue-0.1.3}/pyproject.toml +1 -1
  7. {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/store.rs +10 -4
  8. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_extent_visibility.py +48 -11
  9. {straw_queue-0.1.2 → straw_queue-0.1.3}/.github/workflows/tests.yml +0 -0
  10. {straw_queue-0.1.2 → straw_queue-0.1.3}/.github/workflows/wheels.yml +0 -0
  11. {straw_queue-0.1.2 → straw_queue-0.1.3}/CONTRIBUTING.md +0 -0
  12. {straw_queue-0.1.2 → straw_queue-0.1.3}/CONTRIBUTING_zh.md +0 -0
  13. {straw_queue-0.1.2 → straw_queue-0.1.3}/LICENSE +0 -0
  14. {straw_queue-0.1.2 → straw_queue-0.1.3}/README.md +0 -0
  15. {straw_queue-0.1.2 → straw_queue-0.1.3}/README_zh.md +0 -0
  16. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/APPLICATIONS.md +0 -0
  17. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/APPLICATIONS_zh.md +0 -0
  18. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/BENCHMARKS.md +0 -0
  19. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/BENCHMARKS_zh.md +0 -0
  20. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/BUILDING.md +0 -0
  21. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/BUILDING_zh.md +0 -0
  22. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/DESIGN.md +0 -0
  23. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/DESIGN_zh.md +0 -0
  24. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/FILESYSTEM.md +0 -0
  25. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/FILESYSTEM_zh.md +0 -0
  26. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/FORMAT.md +0 -0
  27. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/FORMAT_zh.md +0 -0
  28. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/PROTOCOL_AND_VERIFICATION.md +0 -0
  29. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/PROTOCOL_AND_VERIFICATION_zh.md +0 -0
  30. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/README.md +0 -0
  31. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/README_zh.md +0 -0
  32. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/USAGE.md +0 -0
  33. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/USAGE_zh.md +0 -0
  34. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/VERIFICATION.md +0 -0
  35. {straw_queue-0.1.2 → straw_queue-0.1.3}/docs/VERIFICATION_zh.md +0 -0
  36. {straw_queue-0.1.2 → straw_queue-0.1.3}/examples/multiprocess.py +0 -0
  37. {straw_queue-0.1.2 → straw_queue-0.1.3}/examples/records.py +0 -0
  38. {straw_queue-0.1.2 → straw_queue-0.1.3}/examples/tensors.py +0 -0
  39. {straw_queue-0.1.2 → straw_queue-0.1.3}/examples/work_queue.py +0 -0
  40. {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/coordinator.rs +0 -0
  41. {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/gc.rs +0 -0
  42. {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/journal.rs +0 -0
  43. {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/lib.rs +0 -0
  44. {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/payload.rs +0 -0
  45. {straw_queue-0.1.2 → straw_queue-0.1.3}/rust/python.rs +0 -0
  46. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/__init__.py +0 -0
  47. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/backend.py +0 -0
  48. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/benchmark.py +0 -0
  49. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/coordinator.py +0 -0
  50. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/errors.py +0 -0
  51. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/inspect.py +0 -0
  52. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/journal.py +0 -0
  53. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/protocol.py +0 -0
  54. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/reporting.py +0 -0
  55. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/rpc.py +0 -0
  56. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/store.py +0 -0
  57. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/tensor.py +0 -0
  58. {straw_queue-0.1.2 → straw_queue-0.1.3}/src/straw/tracing.py +0 -0
  59. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/native.rs +0 -0
  60. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_benchmark_profile.py +0 -0
  61. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_consumer_rewind.py +0 -0
  62. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_filesystem_queue.py +0 -0
  63. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_gc_extent_validation.py +0 -0
  64. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_online_gc.py +0 -0
  65. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_packed_store.py +0 -0
  66. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_read_session.py +0 -0
  67. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_streaming_write.py +0 -0
  68. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_tensor.py +0 -0
  69. {straw_queue-0.1.2 → straw_queue-0.1.3}/tests/test_unlimited_capacity.py +0 -0
  70. {straw_queue-0.1.2 → straw_queue-0.1.3}/tools/check_wheel.py +0 -0
  71. {straw_queue-0.1.2 → straw_queue-0.1.3}/verification/crash_campaign.py +0 -0
  72. {straw_queue-0.1.2 → straw_queue-0.1.3}/verification/gc_cross_client.py +0 -0
  73. {straw_queue-0.1.2 → straw_queue-0.1.3}/verification/gc_scale.py +0 -0
  74. {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 格式和张量读取方式不变。
@@ -370,7 +370,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
370
370
 
371
371
  [[package]]
372
372
  name = "straw"
373
- version = "0.1.2"
373
+ version = "0.1.3"
374
374
  dependencies = [
375
375
  "pyo3",
376
376
  "serde",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "straw"
3
- version = "0.1.2"
3
+ version = "0.1.3"
4
4
  edition = "2024"
5
5
  rust-version = "1.89"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: straw-queue
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Operating System :: POSIX :: Linux
6
6
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "straw-queue"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Rust durable queues for AI tensors and large immutable records"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -332,12 +332,18 @@ impl Config {
332
332
  if header == HEADER {
333
333
  if attempt > 0 {
334
334
  eprintln!(
335
- "Straw extent visible after {attempt} retries; path={path:?}, offset={offset}"
335
+ "straw extent visible after {attempt} retries; path={path:?}, offset={offset}"
336
336
  );
337
337
  }
338
338
  return Ok(f);
339
339
  }
340
- let all_zero = header.iter().all(|b| *b == 0);
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
- all_zero,
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
- "Straw extent visibility retry {}/{} after {delay}ms: {error}",
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
- f.write(bytes(12))
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
- def test_published_zero_header_is_reopened_and_revalidated(tmp_path, capfd, entry):
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 "Straw extent visibility retry" in capfd.readouterr().err
79
+ assert "straw extent visibility retry" in capfd.readouterr().err
64
80
 
65
81
 
66
- def test_persistent_zero_header_still_fails_after_bounded_retries(tmp_path, capfd):
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
- f.seek(segment.offset)
72
- f.write(bytes(12))
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("Straw extent visibility retry") == 5
94
+ assert log.count("straw extent visibility retry") == 5
77
95
  assert "visible after" not in log
78
96
 
79
97
 
80
- def test_nonzero_corruption_is_not_treated_as_delayed_publication(tmp_path, capfd):
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(b"BROKENHEADER")
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