slick-queue-py 2.0.0__tar.gz → 2.1.0__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.
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/API_DIFFERENCES.md +11 -1
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/CHANGELOG.md +275 -211
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/PKG-INFO +66 -7
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/README.md +65 -6
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/slick_queue_py.egg-info/SOURCES.txt +1 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/slick_queue_py.py +188 -72
- slick_queue_py-2.1.0/tests/test_items_per_slot.py +485 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_modern_format.py +4 -2
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_multi_producer.py +47 -10
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_traits.py +2 -2
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/.github/FUNDING.yml +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/.github/workflows/ci.yml +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/.github/workflows/publish-conda.yml +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/.github/workflows/publish-pypi.yml +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/.github/workflows/publish-release.yml +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/.github/workflows/release.yml +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/.gitignore +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/.pypirc.template +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/BUILDING.md +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/CMakeLists.txt +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/LICENSE +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/MANIFEST.in +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/atomic_ops.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/atomic_ops_ext.cpp +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/conda.recipe/meta.yaml +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/pyproject.toml +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/requirements.txt +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/setup.cfg +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/setup.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/cleanup_shm.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/cpp_consumer.cpp +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/cpp_multi_producer.cpp +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/cpp_producer.cpp +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/cpp_read_last_tester.cpp +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/cpp_work_stealing_consumer.cpp +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/run_test.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_atomic_cursor.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_atomic_ops.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_interop.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_local_mode.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_loss_count.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_queue.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_read_last.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_read_last_interop.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_reset_detection.py +0 -0
- {slick_queue_py-2.0.0 → slick_queue_py-2.1.0}/tests/test_wrap_invariants.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This document explains the intentional API differences between the C++ `slick::queue<T>` and Python `SlickQueue` implementations.
|
|
4
4
|
|
|
5
|
-
Both track the same version line; this document describes slick-queue v2.
|
|
5
|
+
Both track the same version line; this document describes slick-queue v2.1.0.
|
|
6
6
|
|
|
7
7
|
## Core Compatibility
|
|
8
8
|
|
|
@@ -26,6 +26,9 @@ slick::queue<uint8_t> q(queue_name);
|
|
|
26
26
|
|
|
27
27
|
// Local memory mode
|
|
28
28
|
slick::queue<uint8_t> q(queue_size); // No shared memory
|
|
29
|
+
|
|
30
|
+
// Minimum of 64 items per reservation (one control slot per 64 items)
|
|
31
|
+
slick::queue<uint8_t> q(queue_size, 64, queue_name);
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
**Python:**
|
|
@@ -38,10 +41,17 @@ q = SlickQueue(name=queue_name, element_size=element_size)
|
|
|
38
41
|
|
|
39
42
|
# Local memory mode
|
|
40
43
|
q = SlickQueue(size=queue_size, element_size=element_size) # No shared memory
|
|
44
|
+
|
|
45
|
+
# Minimum of 64 items per reservation (one control slot per 64 items)
|
|
46
|
+
q = SlickQueue(name=queue_name, size=queue_size, element_size=1, items_per_slot=64)
|
|
41
47
|
```
|
|
42
48
|
|
|
43
49
|
**Rationale:** Python uses keyword arguments for clarity and supports both shared memory and local memory modes.
|
|
44
50
|
|
|
51
|
+
`items_per_slot` is the one place the opener differs: C++ `queue(name)` can only adopt the
|
|
52
|
+
segment's value, while Python's opener also accepts `items_per_slot=` and raises `ValueError`
|
|
53
|
+
if the segment disagrees. Omitting it adopts the segment's value, exactly as C++ does.
|
|
54
|
+
|
|
45
55
|
---
|
|
46
56
|
|
|
47
57
|
### 2. read() Method - Return Value vs. Reference Parameter
|
|
@@ -1,212 +1,276 @@
|
|
|
1
|
-
# Changelogs
|
|
2
|
-
|
|
3
|
-
## [v2.
|
|
4
|
-
|
|
5
|
-
Tracks C++ slick-queue v2.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
###
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
`
|
|
14
|
-
it
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
`
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
-
|
|
183
|
-
|
|
184
|
-
###
|
|
185
|
-
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
###
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
-
|
|
205
|
-
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
-
|
|
211
|
-
-
|
|
1
|
+
# Changelogs
|
|
2
|
+
|
|
3
|
+
## [v2.1.0] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
Tracks C++ slick-queue v2.1.0. Adds `items_per_slot`, so one control slot can
|
|
6
|
+
cover several elements and a byte buffer no longer carries a 16-byte control
|
|
7
|
+
slot per byte. With the default of 1 the shared-memory layout is unchanged, and
|
|
8
|
+
2.0.0 peers in either language interoperate as before.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `items_per_slot=` argument on `SlickQueue`: the minimum number of elements a
|
|
13
|
+
single `reserve()` consumes (a power of 2, `<= size`). `reserve(n)` rounds `n`
|
|
14
|
+
up to a multiple of it and one control slot covers one such unit, so the
|
|
15
|
+
control array holds `size // items_per_slot` slots instead of `size`. A 16M
|
|
16
|
+
byte buffer drops from 256 MB of control to 4 MB with `items_per_slot=64`, at
|
|
17
|
+
the cost that a message shorter than the minimum still takes a whole unit.
|
|
18
|
+
- `read()` returns the published size and advances the cursor by the whole units
|
|
19
|
+
the reservation consumed. The geometry is exposed as `q.items_per_slot` and
|
|
20
|
+
`q.slot_count`.
|
|
21
|
+
- Defaults to 1 when creating. Opening an existing segment by name adopts the
|
|
22
|
+
segment's value, as `size` is adopted; passing one explicitly raises
|
|
23
|
+
`ValueError` if the segment disagrees. C++'s attacher can only adopt - see
|
|
24
|
+
`API_DIFFERENCES.md`.
|
|
25
|
+
- Constants `ITEMS_PER_SLOT_OFFSET`, `HEADER_MAGIC_ITEMS_PER_SLOT` and
|
|
26
|
+
`HEADER_MAGIC_KNOWN_FEATURES`.
|
|
27
|
+
|
|
28
|
+
### Shared-Memory Layout
|
|
29
|
+
|
|
30
|
+
- `items_per_slot` is stored at header offset 28, previously padding. A zero
|
|
31
|
+
field - every segment created by 2.0.0 and earlier - reads as 1. A creator
|
|
32
|
+
opening a segment with a different value raises `ValueError`.
|
|
33
|
+
- Layout marker feature bit 1 is set when `items_per_slot != 1` (`'SLQ3'`, or
|
|
34
|
+
`'SLQ2'` without `enable_read_last`). slick-queue-py 2.0.0 and C++ slick-queue
|
|
35
|
+
2.0.0 reject feature bits they do not recognise, so they refuse such a segment
|
|
36
|
+
at attach time instead of misreading it; default segments keep
|
|
37
|
+
`'SLQ1'`/`'SLQ0'`. A marker whose bit disagrees with the offset-28 field
|
|
38
|
+
raises `RuntimeError`.
|
|
39
|
+
- When `items_per_slot != 1` the data array is padded to the lowest set bit of
|
|
40
|
+
`element_size`, matching C++, so the offset agrees with a C++ peer whose
|
|
41
|
+
element type is over-aligned.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- Local and shared-memory construction, and `reset()`, share helpers for slot
|
|
46
|
+
initialisation and view setup instead of repeating them per path.
|
|
47
|
+
|
|
48
|
+
### Tests
|
|
49
|
+
|
|
50
|
+
- New `tests/test_items_per_slot.py` with 20 tests: geometry, reservation
|
|
51
|
+
rounding, cursor advance by unit for both read paths, multi-unit messages,
|
|
52
|
+
mid-buffer wrap, `read_last()` after wrap, `reset()`, loss counting in
|
|
53
|
+
elements, attacher adoption and mismatch on both attach paths, the legacy zero
|
|
54
|
+
field, the marker bit in each configuration, a marker contradicting its field,
|
|
55
|
+
and data-array padding.
|
|
56
|
+
- `test_modern_format.py` asserts the new offset and marker bit.
|
|
57
|
+
`test_attach_rejects_unknown_feature_bits` uses bit 2, since bit 1 now has a
|
|
58
|
+
meaning.
|
|
59
|
+
|
|
60
|
+
Checked against C++ built from the v2.1.0 header in both directions - byte
|
|
61
|
+
elements with `items_per_slot=16`, and an `alignas(32)` element with a single
|
|
62
|
+
control slot - and against unmodified 2.0.0 code in both languages, which
|
|
63
|
+
refuses an `'SLQ3'` segment and still attaches to an `'SLQ1'` one. The interop
|
|
64
|
+
tests in `tests/` fetch slick-queue from GitHub, so they exercise the default
|
|
65
|
+
layout until the v2.1.0 release is published.
|
|
66
|
+
|
|
67
|
+
## [v2.0.0] - 2026-09-07
|
|
68
|
+
|
|
69
|
+
Tracks C++ slick-queue v2.0.0. Feature configuration moves to a `traits`
|
|
70
|
+
argument, `enable_read_last` becomes part of the shared-memory contract so a
|
|
71
|
+
mismatched peer is rejected at attach time instead of degrading silently, and
|
|
72
|
+
several read-path correctness bugs are fixed.
|
|
73
|
+
|
|
74
|
+
### Breaking Changes
|
|
75
|
+
|
|
76
|
+
- **BREAKING:** `read_last()` requires `traits.enable_read_last` and raises
|
|
77
|
+
`RuntimeError` otherwise. The legacy reserved-cursor fallback is gone with it;
|
|
78
|
+
it reported reservations that were never published and truncated sizes above
|
|
79
|
+
65,535.
|
|
80
|
+
- **BREAKING:** `enable_read_last` is part of the shared-memory contract. The
|
|
81
|
+
header magic carries a feature nibble - `'SLQ1'` when the last-published index
|
|
82
|
+
is maintained, `'SLQ0'` when it is not - and every attacher matches it against
|
|
83
|
+
its own configuration, raising `RuntimeError` on a mismatch in either
|
|
84
|
+
direction. The other traits stay process-local and mix freely on one segment.
|
|
85
|
+
- **BREAKING:** Segments created before slick-queue v1.4.0 carry no layout marker
|
|
86
|
+
and are rejected at attach time, rather than served by the reserved-cursor
|
|
87
|
+
fallback.
|
|
88
|
+
- **BREAKING:** Reset detection is opt-in via `enable_reset_check` (default off),
|
|
89
|
+
matching C++. `read()` no longer loads the producer's reservation counter on
|
|
90
|
+
every call.
|
|
91
|
+
- **BREAKING:** `q.traits` is a read-only snapshot, not the class passed in, so
|
|
92
|
+
`q.traits is MyTraits` is now False and its attributes cannot be assigned.
|
|
93
|
+
- **BREAKING:** The private `_last_published_valid` attribute is gone; use
|
|
94
|
+
`q.traits.enable_read_last`.
|
|
95
|
+
- `reserve(0)` raises `ValueError` instead of reserving nothing.
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- `QueueTraits`, `default_queue_traits`, `validate_traits()`, and a `traits=`
|
|
100
|
+
argument on `SlickQueue`. Subclass `QueueTraits` to override
|
|
101
|
+
`enable_read_last`, `enable_reset_check`, `enable_loss_detection` or
|
|
102
|
+
`enable_cpu_relax`; differently configured queues coexist in one process.
|
|
103
|
+
`validate_traits()` rejects a malformed traits type at construction, as the C++
|
|
104
|
+
`queue_traits_type` concept does, and like it cannot catch a misspelled
|
|
105
|
+
override.
|
|
106
|
+
- Constants `HEADER_MAGIC_FEATURE_MASK`, `HEADER_MAGIC_READ_LAST` and
|
|
107
|
+
`SLOT_SIZE_OFFSET`.
|
|
108
|
+
- 40 tests. `tests/test_traits.py` (24) covers traits validation, the marker each
|
|
109
|
+
configuration writes, both mismatch directions, unmarked and unknown-feature
|
|
110
|
+
segments, and mutation of a traits class after construction.
|
|
111
|
+
`tests/test_reset_detection.py` (9) asserts a stale reader actually rewinds
|
|
112
|
+
onto the new generation, across both read paths and both memory modes.
|
|
113
|
+
`tests/test_wrap_invariants.py` (7) pins the cursor/size invariant under a
|
|
114
|
+
wrapping producer; three drive the recycle deterministically, because CPython
|
|
115
|
+
switches threads far too rarely to hit the window by racing.
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- Both `read()` overloads loaded `slot.size` twice, unvalidated, so a producer
|
|
120
|
+
recycling the slot between the loads left the cursor describing a different
|
|
121
|
+
record than the caller was handed - in the shared-cursor overload the two loads
|
|
122
|
+
straddled the claiming CAS. Both now load it once and re-validate the slot
|
|
123
|
+
before committing, as `read_last()` does.
|
|
124
|
+
- `read_last()` could return a `(data, size)` pair describing two different
|
|
125
|
+
records; the slot is now validated before and after the size load.
|
|
126
|
+
- Reset detection never fired for the case it exists to handle. It compared the
|
|
127
|
+
*slot's* index against the reservation counter, but `reset()` clears the
|
|
128
|
+
control array before rewinding the counter, so a stale reader always landed on
|
|
129
|
+
a slot holding an invalid or fresh-low index - neither ahead of the counter.
|
|
130
|
+
The test is now on the reader's cursor, shared by both read paths through
|
|
131
|
+
`_was_reset()`.
|
|
132
|
+
- The reset branch in the shared-cursor `read()` overwrote the cursor with a
|
|
133
|
+
blind store, discarding other consumers' progress; it now rewinds via
|
|
134
|
+
compare-exchange.
|
|
135
|
+
- The single-consumer `read()` charged the loss counter when the overrun was
|
|
136
|
+
spotted rather than on commit, so a retry could count the same overrun twice.
|
|
137
|
+
- `reset()` did not rewind the reservation counter in local memory mode.
|
|
138
|
+
- Traits were re-read from the caller's class on every `publish()` and `read()`,
|
|
139
|
+
while the layout marker and the optional atomics were created once. Mutating
|
|
140
|
+
the class afterwards froze `read_last()` on an `'SLQ1'` segment, or raised
|
|
141
|
+
`AttributeError` from `publish()` and `reset()` because the last-published
|
|
142
|
+
atomic had never been created. They are snapshotted at construction now.
|
|
143
|
+
|
|
144
|
+
### Changed
|
|
145
|
+
|
|
146
|
+
- Loss detection is gated on `enable_loss_detection`, on by default. C++ keys its
|
|
147
|
+
equivalent off `NDEBUG` and needs two traits types so a debug/release mismatch
|
|
148
|
+
fails to link instead of violating the ODR; Python has no translation units to
|
|
149
|
+
protect, and `__debug__` was rejected because it would zero `loss_count()`
|
|
150
|
+
under `python -O`. It costs +0.2% on a reader that keeps up.
|
|
151
|
+
- `publish()` updates the last-published index unconditionally when the feature
|
|
152
|
+
is on - validating the nibble once at attach time makes the old per-call
|
|
153
|
+
`_last_published_valid` check redundant.
|
|
154
|
+
- Contended CAS loops in `reserve()` and the shared-cursor `read()` back off
|
|
155
|
+
through `_cpu_relax()`, gated on `enable_cpu_relax`.
|
|
156
|
+
- C++ interop programs include the renamed `<slick/queue.hpp>` and use the
|
|
157
|
+
`slick::queue` alias; the old `<slick/queue.h>` path is a deprecation shim.
|
|
158
|
+
- `tests/CMakeLists.txt` registers `test_loss_count.py` (missed in v1.2.0) and
|
|
159
|
+
the three new files. `tests/test_interop.py` bounds each child-result `get()`,
|
|
160
|
+
which previously hung a run forever when a child was killed on a join timeout.
|
|
161
|
+
|
|
162
|
+
### Notes
|
|
163
|
+
|
|
164
|
+
`slot.size` became a `std::atomic<uint32_t>` in C++ but stays a plain struct
|
|
165
|
+
field here: that change fixed C++ undefined behaviour and compiler re-loading,
|
|
166
|
+
neither of which exists in CPython, and the field is already an aligned 4-byte
|
|
167
|
+
access with an identical wire format.
|
|
168
|
+
|
|
169
|
+
The seqlock validation guarantees the cursor and the returned size describe one
|
|
170
|
+
and the same record. It does **not** make the returned bytes a snapshot: a
|
|
171
|
+
producer writes an element's data before it publishes the slot's new index, so a
|
|
172
|
+
lapped consumer can copy bytes mid-overwrite and no check on the slot can detect
|
|
173
|
+
it. C++ has the identical hazard behind the pointer it returns; `loss_count()` is
|
|
174
|
+
how a consumer detects it. See README.md and API_DIFFERENCES.md.
|
|
175
|
+
|
|
176
|
+
### Compatibility
|
|
177
|
+
|
|
178
|
+
Verified against C++ slick-queue v2.0.0 binaries: all 10 interop tests pass, and
|
|
179
|
+
a Python-created `'SLQ0'` segment is rejected by a C++ peer built with
|
|
180
|
+
`enable_read_last` on, with the message the C++ side raises.
|
|
181
|
+
|
|
182
|
+
## [v1.2.0] - 2026-07-09
|
|
183
|
+
|
|
184
|
+
### Added
|
|
185
|
+
- `loss_count()`: per-instance count of items skipped due to overwrite, matching
|
|
186
|
+
C++ slick-queue v1.5.0 (queue.h:230-236). Counted in both the single-consumer
|
|
187
|
+
read (slot overwritten by a newer generation at the same position) and the
|
|
188
|
+
AtomicCursor work-stealing read (overrun attributed to the claiming consumer).
|
|
189
|
+
Cleared by `reset()`. Note: Python always counts; C++ counts only when
|
|
190
|
+
`SLICK_QUEUE_ENABLE_LOSS_DETECTION` is enabled (debug builds by default).
|
|
191
|
+
- `initial_reading_index()`: cursor for a late-joining consumer - 0 for a newly
|
|
192
|
+
created queue, or the current writing index of an opened queue, matching C++
|
|
193
|
+
slick-queue v1.5.0 (queue.h:242-244).
|
|
194
|
+
- `tests/test_loss_count.py`: 7 tests covering both new methods.
|
|
195
|
+
|
|
196
|
+
These are per-instance API additions only - the shared memory layout is unchanged
|
|
197
|
+
and remains fully compatible with C++ slick-queue v1.x ('SLQ1' format).
|
|
198
|
+
|
|
199
|
+
## [v1.1.0] - 2026-02-04
|
|
200
|
+
|
|
201
|
+
### Breaking Changes
|
|
202
|
+
- **BREAKING:** `read_last()` return signature changed from `Optional[bytes]` to `Tuple[Optional[bytes], int]`
|
|
203
|
+
- Now returns `(data, size)` tuple where `size` indicates the number of slots the item occupies
|
|
204
|
+
- Migration: Change `data = q.read_last()` to `data, size = q.read_last()`
|
|
205
|
+
- Provides actual slot size information instead of always returning full `element_size`
|
|
206
|
+
|
|
207
|
+
### Added
|
|
208
|
+
- Modern format support with efficient `last_published_` atomic tracking
|
|
209
|
+
- New `last_published_` atomic field at offset 16 for O(1) `read_last()` performance
|
|
210
|
+
- Header magic number `0x534C5131` ('SLQ1') at offset 24 for format detection
|
|
211
|
+
- Init state atomic at offset 48 for CAS-based ownership detection
|
|
212
|
+
- Automatic format detection distinguishes modern vs legacy queues
|
|
213
|
+
- CAS-based queue ownership detection matching C++ implementation (queue.h:618-648)
|
|
214
|
+
- Helper methods `_wait_for_shared_memory_ready()` and `_detect_format_version()`
|
|
215
|
+
- Comprehensive test suites:
|
|
216
|
+
- `test_read_last.py`: 10 tests covering empty queue, single/multiple items, multi-slot publishes, concurrent publishes, reset, shared memory, wraparound, variable sizes
|
|
217
|
+
- `test_read_last_interop.py`: 5 tests for Python ↔ C++ interoperability
|
|
218
|
+
- `test_modern_format.py`: 6 tests for format compatibility and memory layout verification
|
|
219
|
+
- C++ test program `cpp_read_last_tester.cpp` for read_last() interoperability testing
|
|
220
|
+
- New constants: `SIZE_OFFSET`, `ELEMENT_SIZE_OFFSET`, `LAST_PUBLISHED_OFFSET`, `HEADER_MAGIC_OFFSET`, `HEADER_MAGIC`, `INIT_STATE_OFFSET`, `INIT_STATE_*`, `K_INVALID_INDEX`
|
|
221
|
+
- Documentation: `READ_LAST_INTEROP.md` comprehensive interoperability guide
|
|
222
|
+
|
|
223
|
+
### Changed
|
|
224
|
+
- Updated shared memory header layout to match C++ slick-queue v1.x:
|
|
225
|
+
- Reserved info: 8 bytes at offset 0-7 (compact from previous 32-byte padded format)
|
|
226
|
+
- Size: 4 bytes at offset 8-11 (moved from offset 32)
|
|
227
|
+
- Element size: 4 bytes at offset 12-15 (moved from offset 36)
|
|
228
|
+
- Last published: 8 bytes at offset 16-23 (NEW)
|
|
229
|
+
- Header magic: 4 bytes at offset 24-27 (NEW)
|
|
230
|
+
- Init state: 4 bytes at offset 48-51 (NEW)
|
|
231
|
+
- `publish()` now updates `last_published_` atomic using CAS loop in modern format (queue.h:331-337)
|
|
232
|
+
- `reset()` now resets `last_published_` to `K_INVALID_INDEX` when in modern format
|
|
233
|
+
- `close()` properly releases `_atomic_last_published` wrapper
|
|
234
|
+
- `AtomicReservedInfo.RESERVED_INFO_FMT` simplified from 32-byte to 8-byte format
|
|
235
|
+
- Repository name changed from slick_queue_py to slick-queue-py
|
|
236
|
+
- Updated README.md with new `read_last()` API documentation
|
|
237
|
+
- Enhanced CMakeLists.txt with new test targets
|
|
238
|
+
|
|
239
|
+
### Fixed
|
|
240
|
+
- `read_last()` now returns actual slot size instead of always returning full `element_size`
|
|
241
|
+
- Test queue names shortened to comply with macOS 31-character shared memory name limit
|
|
242
|
+
|
|
243
|
+
### Performance
|
|
244
|
+
- O(1) `read_last()` via direct atomic load (improved from reserved_info calculation)
|
|
245
|
+
- Reduced header size: 8-byte reserved_info vs 32-byte padded (24 bytes saved)
|
|
246
|
+
- Lock-free `last_published_` tracking enables efficient concurrent access
|
|
247
|
+
|
|
248
|
+
### Compatibility
|
|
249
|
+
- **Backward Compatible:** Automatically detects and supports legacy queue format
|
|
250
|
+
- **C++ Interop:** Full compatibility with C++ slick-queue modern format
|
|
251
|
+
- Python and C++ can share queues created by either language
|
|
252
|
+
- All existing tests continue to pass (atomic ops, cursors, local mode, multi-producer, interop)
|
|
253
|
+
|
|
254
|
+
## [v1.0.1] - 2025-12-27
|
|
255
|
+
|
|
256
|
+
### Fixed
|
|
257
|
+
- Fixed extra `/` prefix being added to shared memory names on POSIX systems (Linux/macOS)
|
|
258
|
+
- Fixed test failures on Linux and macOS platforms
|
|
259
|
+
|
|
260
|
+
### Added
|
|
261
|
+
- macOS shared memory name length validation (31 character limit including `/` prefix)
|
|
262
|
+
- C++ extension support for Linux/macOS platforms with automatic fallback to native methods if unavailable
|
|
263
|
+
- `atomic_store_64` and `atomic_cas_64` functions to C++ extension for improved cross-platform consistency
|
|
264
|
+
|
|
265
|
+
### Changed
|
|
266
|
+
- Linux/macOS now prioritize C++ extension for atomic operations, falling back to native methods (`__sync_val_compare_and_swap` or `libatomic`) if extension is not available
|
|
267
|
+
- Improved atomic operation reliability across all platforms
|
|
268
|
+
- Enhanced test suite for better cross-platform compatibility
|
|
269
|
+
- Publishing: Updated PyPI builds to use `cibuildwheel` for proper manylinux wheel generation
|
|
270
|
+
- Ignore conda publish failing for now
|
|
271
|
+
|
|
272
|
+
## [v1.0.0] - 2025-12-26
|
|
273
|
+
|
|
274
|
+
- Initial release
|
|
275
|
+
- Python implementation of SlickQueue - a lock-free multi-producer multi-consumer (MPMC) queue with C++ interoperability through shared memory.
|
|
212
276
|
- Windows, Linux, and macOS support
|