slick-queue-py 1.1.0__tar.gz → 2.0.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.
Files changed (46) hide show
  1. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/API_DIFFERENCES.md +103 -13
  2. slick_queue_py-2.0.0/CHANGELOG.md +212 -0
  3. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/PKG-INFO +165 -19
  4. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/README.md +164 -18
  5. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/slick_queue_py.egg-info/SOURCES.txt +5 -1
  6. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/slick_queue_py.py +498 -105
  7. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/cpp_consumer.cpp +2 -2
  8. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/cpp_multi_producer.cpp +2 -2
  9. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/cpp_producer.cpp +2 -2
  10. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/cpp_read_last_tester.cpp +3 -3
  11. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/cpp_work_stealing_consumer.cpp +2 -2
  12. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_interop.py +33 -10
  13. slick_queue_py-2.0.0/tests/test_loss_count.py +185 -0
  14. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_modern_format.py +14 -6
  15. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_multi_producer.py +9 -0
  16. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_read_last_interop.py +3 -3
  17. slick_queue_py-2.0.0/tests/test_reset_detection.py +272 -0
  18. slick_queue_py-2.0.0/tests/test_traits.py +582 -0
  19. slick_queue_py-2.0.0/tests/test_wrap_invariants.py +406 -0
  20. slick_queue_py-1.1.0/CHANGELOG.md +0 -80
  21. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/.github/FUNDING.yml +0 -0
  22. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/.github/workflows/ci.yml +0 -0
  23. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/.github/workflows/publish-conda.yml +0 -0
  24. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/.github/workflows/publish-pypi.yml +0 -0
  25. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/.github/workflows/publish-release.yml +0 -0
  26. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/.github/workflows/release.yml +0 -0
  27. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/.gitignore +0 -0
  28. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/.pypirc.template +0 -0
  29. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/BUILDING.md +0 -0
  30. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/CMakeLists.txt +0 -0
  31. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/LICENSE +0 -0
  32. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/MANIFEST.in +0 -0
  33. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/atomic_ops.py +0 -0
  34. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/atomic_ops_ext.cpp +0 -0
  35. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/conda.recipe/meta.yaml +0 -0
  36. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/pyproject.toml +0 -0
  37. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/requirements.txt +0 -0
  38. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/setup.cfg +0 -0
  39. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/setup.py +0 -0
  40. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/cleanup_shm.py +0 -0
  41. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/run_test.py +0 -0
  42. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_atomic_cursor.py +0 -0
  43. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_atomic_ops.py +0 -0
  44. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_local_mode.py +0 -0
  45. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_queue.py +0 -0
  46. {slick_queue_py-1.1.0 → slick_queue_py-2.0.0}/tests/test_read_last.py +0 -0
@@ -1,6 +1,8 @@
1
1
  # C++ / Python API Differences
2
2
 
3
- This document explains the intentional API differences between the C++ `slick::SlickQueue<T>` and Python `SlickQueue` implementations.
3
+ This document explains the intentional API differences between the C++ `slick::queue<T>` and Python `SlickQueue` implementations.
4
+
5
+ Both track the same version line; this document describes slick-queue v2.0.0.
4
6
 
5
7
  ## Core Compatibility
6
8
 
@@ -17,13 +19,13 @@ Both implementations:
17
19
  **C++:**
18
20
  ```cpp
19
21
  // Create new queue in shared memory
20
- slick::SlickQueue<uint8_t> q(queue_size, queue_name);
22
+ slick::queue<uint8_t> q(queue_size, queue_name);
21
23
 
22
24
  // Open existing queue
23
- slick::SlickQueue<uint8_t> q(queue_name);
25
+ slick::queue<uint8_t> q(queue_name);
24
26
 
25
27
  // Local memory mode
26
- slick::SlickQueue<uint8_t> q(queue_size); // No shared memory
28
+ slick::queue<uint8_t> q(queue_size); // No shared memory
27
29
  ```
28
30
 
29
31
  **Python:**
@@ -81,7 +83,89 @@ if data is not None:
81
83
 
82
84
  ---
83
85
 
84
- ### 3. Element Access
86
+ ### 3. Feature Configuration (Traits)
87
+
88
+ **C++:**
89
+ ```cpp
90
+ // A template parameter - differently configured queues are different types
91
+ struct my_traits : slick::queue_traits {
92
+ static constexpr bool enable_reset_check = true;
93
+ static constexpr bool enable_read_last = false;
94
+ };
95
+
96
+ slick::queue<int, my_traits> lean(1024);
97
+ slick::queue<int> standard(1024); // default_queue_traits
98
+ ```
99
+
100
+ **Python:**
101
+ ```python
102
+ # A constructor argument - the configuration is per-instance
103
+ class MyTraits(QueueTraits):
104
+ enable_reset_check = True
105
+ enable_read_last = False
106
+
107
+ lean = SlickQueue(size=1024, element_size=4, traits=MyTraits)
108
+ standard = SlickQueue(size=1024, element_size=4) # default_queue_traits
109
+ ```
110
+
111
+ **Rationale:** Python has no templates, so the traits type is passed as a value
112
+ rather than a template argument. The consequences differ slightly:
113
+
114
+ | | C++ | Python |
115
+ |---|---|---|
116
+ | Malformed traits type | `queue_traits_type` concept, compile error | `validate_traits()`, `TypeError` at construction |
117
+ | Mutating the traits after use | Impossible - `static constexpr` members | Snapshotted at construction; `q.traits` is read-only |
118
+ | `read_last()` without `enable_read_last` | `static_assert`, compile error | `RuntimeError` at the call |
119
+ | Traits types | Two (`queue_traits`, `debug_queue_traits`) so a debug/release mismatch fails to link | One - no translation units, so nothing for a second type to protect |
120
+ | Default loss detection | Keyed off `NDEBUG` (Debug on, Release off) | Always on (+0.2%); not keyed off `__debug__`, which would zero `loss_count()` under `python -O` |
121
+ | Layout when a feature is off | Its counter is dropped from the object | No object-layout effect |
122
+
123
+ **Important:** `enable_read_last` is part of the *shared-memory* contract in both
124
+ languages, and they agree byte for byte. The creator records it in the segment's
125
+ layout marker - `'SLQ1'` when the last-published index is maintained, `'SLQ0'`
126
+ when it is not - and every attacher matches it against its own configuration,
127
+ raising `RuntimeError` (C++ `std::runtime_error`) on a mismatch in either
128
+ direction. A Python `'SLQ0'` segment is therefore rejected by a C++ peer built
129
+ with the feature on, and vice versa. The remaining traits are process-local and
130
+ mix freely on one segment, so a Python reader with `enable_reset_check` on can
131
+ share a segment with a C++ writer that has it off.
132
+
133
+ ---
134
+
135
+ ### 4. Lossy Reads: Pointer vs. Copy
136
+
137
+ **C++:**
138
+ ```cpp
139
+ auto [data, size] = queue.read(read_index); // data is a T* into the ring
140
+ process(data, size); // producer may overwrite as you read
141
+ ```
142
+
143
+ **Python:**
144
+ ```python
145
+ data, size, read_index = q.read(read_index) # data is a bytes copy
146
+ ```
147
+
148
+ **Rationale:** Python cannot hand out a raw pointer into the ring safely, so
149
+ `read()` copies. This changes what "lossy" feels like, without changing the
150
+ guarantee.
151
+
152
+ Both implementations guarantee that the cursor and the returned size describe one
153
+ and the same record - that is what the v2.0.0 seqlock validation fixed. Neither
154
+ guarantees the *contents*: a producer writes an element's data before it publishes
155
+ the slot's new index, so a consumer that has been lapped can read bytes the
156
+ producer is midway through overwriting, and no check on the slot can detect it
157
+ because the index has not changed yet.
158
+
159
+ The difference is only in how visible this is. A C++ caller holds a pointer into a
160
+ ring buffer and can see that its target is volatile. A Python caller holds what
161
+ looks like a snapshot, but under overrun those bytes may belong to a newer record
162
+ than the cursor names. Use `loss_count()` to detect it: a non-zero count means this
163
+ consumer was overrun and bytes may have been overwritten in flight. If your data
164
+ must be self-identifying, put a sequence number in the payload.
165
+
166
+ ---
167
+
168
+ ### 5. Element Access
85
169
 
86
170
  **C++:**
87
171
  ```cpp
@@ -103,12 +187,12 @@ q.publish(idx)
103
187
 
104
188
  ---
105
189
 
106
- ### 4. Type Safety
190
+ ### 6. Type Safety
107
191
 
108
192
  **C++:**
109
193
  ```cpp
110
194
  // Template-based, compile-time type safety
111
- slick::SlickQueue<int32_t> q(1024, "queue");
195
+ slick::queue<int32_t> q(1024, "queue");
112
196
  auto idx = q.reserve();
113
197
  *q[idx] = 42; // Type-safe int32_t access
114
198
  ```
@@ -126,12 +210,12 @@ q[idx][:4] = struct.pack("<i", 42) # Manual packing
126
210
 
127
211
  ---
128
212
 
129
- ### 5. Resource Management
213
+ ### 7. Resource Management
130
214
 
131
215
  **C++:**
132
216
  ```cpp
133
217
  {
134
- slick::SlickQueue<T> q(size, "name");
218
+ slick::queue<T> q(size, "name");
135
219
  // ... use queue ...
136
220
  } // Destructor automatically cleans up
137
221
  ```
@@ -162,11 +246,11 @@ with SlickQueue(name='name', size=size, element_size=elem_size, create=True) as
162
246
 
163
247
  **C++ (producer.cpp):**
164
248
  ```cpp
165
- #include <slick/queue.h>
249
+ #include <slick/queue.hpp>
166
250
  #include <cstring>
167
251
 
168
252
  int main() {
169
- slick::SlickQueue<uint8_t> queue("my_queue"); // Open existing queue
253
+ slick::queue<uint8_t> queue("my_queue"); // Open existing queue
170
254
 
171
255
  for (int i = 0; i < 100; i++) {
172
256
  auto idx = queue.reserve();
@@ -216,12 +300,12 @@ q.close()
216
300
 
217
301
  **C++ (consumer.cpp):**
218
302
  ```cpp
219
- #include <slick/queue.h>
303
+ #include <slick/queue.hpp>
220
304
  #include <iostream>
221
305
  #include <cstring>
222
306
 
223
307
  int main() {
224
- slick::SlickQueue<uint8_t> queue("my_queue"); // Open existing queue
308
+ slick::queue<uint8_t> queue("my_queue"); // Open existing queue
225
309
 
226
310
  uint64_t read_index = 0;
227
311
  for (int i = 0; i < 100; i++) {
@@ -245,5 +329,11 @@ int main() {
245
329
  | Element access | Pointer | `memoryview` | Python memory safety |
246
330
  | Type safety | Templates | `struct` module | Language difference |
247
331
  | Cleanup | RAII (destructor) | Explicit or context manager | Python GC is non-deterministic |
332
+ | `read()` data | `T*` into the ring | `bytes` copy | No safe raw pointers in Python |
333
+ | Overwritten-in-flight data | Caller sees a volatile pointer | Copy may hold a newer record's bytes | Detect with `loss_count()` |
334
+ | Feature configuration | `Traits` template parameter | `traits=` argument | Python has no templates |
335
+ | Misconfigured traits | Compile error (concept / `static_assert`) | `TypeError` / `RuntimeError` at runtime | No compile step |
336
+ | Default loss detection | Off in Release | Always on | +0.2% here, and the only overrun signal a caller has |
337
+ | Layout marker | `'SLQ1'` / `'SLQ0'` | Identical, and validated against C++ peers | Shared contract |
248
338
 
249
339
  **Bottom Line:** The Python API is designed to be Pythonic while maintaining **100% binary compatibility** with C++. All atomic operations and memory layouts are identical, enabling seamless C++/Python interoperability.
@@ -0,0 +1,212 @@
1
+ # Changelogs
2
+
3
+ ## [v2.0.0] - 2026-09-07
4
+
5
+ Tracks C++ slick-queue v2.0.0. Feature configuration moves to a `traits`
6
+ argument, `enable_read_last` becomes part of the shared-memory contract so a
7
+ mismatched peer is rejected at attach time instead of degrading silently, and
8
+ several read-path correctness bugs are fixed.
9
+
10
+ ### Breaking Changes
11
+
12
+ - **BREAKING:** `read_last()` requires `traits.enable_read_last` and raises
13
+ `RuntimeError` otherwise. The legacy reserved-cursor fallback is gone with it;
14
+ it reported reservations that were never published and truncated sizes above
15
+ 65,535.
16
+ - **BREAKING:** `enable_read_last` is part of the shared-memory contract. The
17
+ header magic carries a feature nibble - `'SLQ1'` when the last-published index
18
+ is maintained, `'SLQ0'` when it is not - and every attacher matches it against
19
+ its own configuration, raising `RuntimeError` on a mismatch in either
20
+ direction. The other traits stay process-local and mix freely on one segment.
21
+ - **BREAKING:** Segments created before slick-queue v1.4.0 carry no layout marker
22
+ and are rejected at attach time, rather than served by the reserved-cursor
23
+ fallback.
24
+ - **BREAKING:** Reset detection is opt-in via `enable_reset_check` (default off),
25
+ matching C++. `read()` no longer loads the producer's reservation counter on
26
+ every call.
27
+ - **BREAKING:** `q.traits` is a read-only snapshot, not the class passed in, so
28
+ `q.traits is MyTraits` is now False and its attributes cannot be assigned.
29
+ - **BREAKING:** The private `_last_published_valid` attribute is gone; use
30
+ `q.traits.enable_read_last`.
31
+ - `reserve(0)` raises `ValueError` instead of reserving nothing.
32
+
33
+ ### Added
34
+
35
+ - `QueueTraits`, `default_queue_traits`, `validate_traits()`, and a `traits=`
36
+ argument on `SlickQueue`. Subclass `QueueTraits` to override
37
+ `enable_read_last`, `enable_reset_check`, `enable_loss_detection` or
38
+ `enable_cpu_relax`; differently configured queues coexist in one process.
39
+ `validate_traits()` rejects a malformed traits type at construction, as the C++
40
+ `queue_traits_type` concept does, and like it cannot catch a misspelled
41
+ override.
42
+ - Constants `HEADER_MAGIC_FEATURE_MASK`, `HEADER_MAGIC_READ_LAST` and
43
+ `SLOT_SIZE_OFFSET`.
44
+ - 40 tests. `tests/test_traits.py` (24) covers traits validation, the marker each
45
+ configuration writes, both mismatch directions, unmarked and unknown-feature
46
+ segments, and mutation of a traits class after construction.
47
+ `tests/test_reset_detection.py` (9) asserts a stale reader actually rewinds
48
+ onto the new generation, across both read paths and both memory modes.
49
+ `tests/test_wrap_invariants.py` (7) pins the cursor/size invariant under a
50
+ wrapping producer; three drive the recycle deterministically, because CPython
51
+ switches threads far too rarely to hit the window by racing.
52
+
53
+ ### Fixed
54
+
55
+ - Both `read()` overloads loaded `slot.size` twice, unvalidated, so a producer
56
+ recycling the slot between the loads left the cursor describing a different
57
+ record than the caller was handed - in the shared-cursor overload the two loads
58
+ straddled the claiming CAS. Both now load it once and re-validate the slot
59
+ before committing, as `read_last()` does.
60
+ - `read_last()` could return a `(data, size)` pair describing two different
61
+ records; the slot is now validated before and after the size load.
62
+ - Reset detection never fired for the case it exists to handle. It compared the
63
+ *slot's* index against the reservation counter, but `reset()` clears the
64
+ control array before rewinding the counter, so a stale reader always landed on
65
+ a slot holding an invalid or fresh-low index - neither ahead of the counter.
66
+ The test is now on the reader's cursor, shared by both read paths through
67
+ `_was_reset()`.
68
+ - The reset branch in the shared-cursor `read()` overwrote the cursor with a
69
+ blind store, discarding other consumers' progress; it now rewinds via
70
+ compare-exchange.
71
+ - The single-consumer `read()` charged the loss counter when the overrun was
72
+ spotted rather than on commit, so a retry could count the same overrun twice.
73
+ - `reset()` did not rewind the reservation counter in local memory mode.
74
+ - Traits were re-read from the caller's class on every `publish()` and `read()`,
75
+ while the layout marker and the optional atomics were created once. Mutating
76
+ the class afterwards froze `read_last()` on an `'SLQ1'` segment, or raised
77
+ `AttributeError` from `publish()` and `reset()` because the last-published
78
+ atomic had never been created. They are snapshotted at construction now.
79
+
80
+ ### Changed
81
+
82
+ - Loss detection is gated on `enable_loss_detection`, on by default. C++ keys its
83
+ equivalent off `NDEBUG` and needs two traits types so a debug/release mismatch
84
+ fails to link instead of violating the ODR; Python has no translation units to
85
+ protect, and `__debug__` was rejected because it would zero `loss_count()`
86
+ under `python -O`. It costs +0.2% on a reader that keeps up.
87
+ - `publish()` updates the last-published index unconditionally when the feature
88
+ is on - validating the nibble once at attach time makes the old per-call
89
+ `_last_published_valid` check redundant.
90
+ - Contended CAS loops in `reserve()` and the shared-cursor `read()` back off
91
+ through `_cpu_relax()`, gated on `enable_cpu_relax`.
92
+ - C++ interop programs include the renamed `<slick/queue.hpp>` and use the
93
+ `slick::queue` alias; the old `<slick/queue.h>` path is a deprecation shim.
94
+ - `tests/CMakeLists.txt` registers `test_loss_count.py` (missed in v1.2.0) and
95
+ the three new files. `tests/test_interop.py` bounds each child-result `get()`,
96
+ which previously hung a run forever when a child was killed on a join timeout.
97
+
98
+ ### Notes
99
+
100
+ `slot.size` became a `std::atomic<uint32_t>` in C++ but stays a plain struct
101
+ field here: that change fixed C++ undefined behaviour and compiler re-loading,
102
+ neither of which exists in CPython, and the field is already an aligned 4-byte
103
+ access with an identical wire format.
104
+
105
+ The seqlock validation guarantees the cursor and the returned size describe one
106
+ and the same record. It does **not** make the returned bytes a snapshot: a
107
+ producer writes an element's data before it publishes the slot's new index, so a
108
+ lapped consumer can copy bytes mid-overwrite and no check on the slot can detect
109
+ it. C++ has the identical hazard behind the pointer it returns; `loss_count()` is
110
+ how a consumer detects it. See README.md and API_DIFFERENCES.md.
111
+
112
+ ### Compatibility
113
+
114
+ Verified against C++ slick-queue v2.0.0 binaries: all 10 interop tests pass, and
115
+ a Python-created `'SLQ0'` segment is rejected by a C++ peer built with
116
+ `enable_read_last` on, with the message the C++ side raises.
117
+
118
+ ## [v1.2.0] - 2026-07-09
119
+
120
+ ### Added
121
+ - `loss_count()`: per-instance count of items skipped due to overwrite, matching
122
+ C++ slick-queue v1.5.0 (queue.h:230-236). Counted in both the single-consumer
123
+ read (slot overwritten by a newer generation at the same position) and the
124
+ AtomicCursor work-stealing read (overrun attributed to the claiming consumer).
125
+ Cleared by `reset()`. Note: Python always counts; C++ counts only when
126
+ `SLICK_QUEUE_ENABLE_LOSS_DETECTION` is enabled (debug builds by default).
127
+ - `initial_reading_index()`: cursor for a late-joining consumer - 0 for a newly
128
+ created queue, or the current writing index of an opened queue, matching C++
129
+ slick-queue v1.5.0 (queue.h:242-244).
130
+ - `tests/test_loss_count.py`: 7 tests covering both new methods.
131
+
132
+ These are per-instance API additions only - the shared memory layout is unchanged
133
+ and remains fully compatible with C++ slick-queue v1.x ('SLQ1' format).
134
+
135
+ ## [v1.1.0] - 2026-02-04
136
+
137
+ ### Breaking Changes
138
+ - **BREAKING:** `read_last()` return signature changed from `Optional[bytes]` to `Tuple[Optional[bytes], int]`
139
+ - Now returns `(data, size)` tuple where `size` indicates the number of slots the item occupies
140
+ - Migration: Change `data = q.read_last()` to `data, size = q.read_last()`
141
+ - Provides actual slot size information instead of always returning full `element_size`
142
+
143
+ ### Added
144
+ - Modern format support with efficient `last_published_` atomic tracking
145
+ - New `last_published_` atomic field at offset 16 for O(1) `read_last()` performance
146
+ - Header magic number `0x534C5131` ('SLQ1') at offset 24 for format detection
147
+ - Init state atomic at offset 48 for CAS-based ownership detection
148
+ - Automatic format detection distinguishes modern vs legacy queues
149
+ - CAS-based queue ownership detection matching C++ implementation (queue.h:618-648)
150
+ - Helper methods `_wait_for_shared_memory_ready()` and `_detect_format_version()`
151
+ - Comprehensive test suites:
152
+ - `test_read_last.py`: 10 tests covering empty queue, single/multiple items, multi-slot publishes, concurrent publishes, reset, shared memory, wraparound, variable sizes
153
+ - `test_read_last_interop.py`: 5 tests for Python ↔ C++ interoperability
154
+ - `test_modern_format.py`: 6 tests for format compatibility and memory layout verification
155
+ - C++ test program `cpp_read_last_tester.cpp` for read_last() interoperability testing
156
+ - New constants: `SIZE_OFFSET`, `ELEMENT_SIZE_OFFSET`, `LAST_PUBLISHED_OFFSET`, `HEADER_MAGIC_OFFSET`, `HEADER_MAGIC`, `INIT_STATE_OFFSET`, `INIT_STATE_*`, `K_INVALID_INDEX`
157
+ - Documentation: `READ_LAST_INTEROP.md` comprehensive interoperability guide
158
+
159
+ ### Changed
160
+ - Updated shared memory header layout to match C++ slick-queue v1.x:
161
+ - Reserved info: 8 bytes at offset 0-7 (compact from previous 32-byte padded format)
162
+ - Size: 4 bytes at offset 8-11 (moved from offset 32)
163
+ - Element size: 4 bytes at offset 12-15 (moved from offset 36)
164
+ - Last published: 8 bytes at offset 16-23 (NEW)
165
+ - Header magic: 4 bytes at offset 24-27 (NEW)
166
+ - Init state: 4 bytes at offset 48-51 (NEW)
167
+ - `publish()` now updates `last_published_` atomic using CAS loop in modern format (queue.h:331-337)
168
+ - `reset()` now resets `last_published_` to `K_INVALID_INDEX` when in modern format
169
+ - `close()` properly releases `_atomic_last_published` wrapper
170
+ - `AtomicReservedInfo.RESERVED_INFO_FMT` simplified from 32-byte to 8-byte format
171
+ - Repository name changed from slick_queue_py to slick-queue-py
172
+ - Updated README.md with new `read_last()` API documentation
173
+ - Enhanced CMakeLists.txt with new test targets
174
+
175
+ ### Fixed
176
+ - `read_last()` now returns actual slot size instead of always returning full `element_size`
177
+ - Test queue names shortened to comply with macOS 31-character shared memory name limit
178
+
179
+ ### Performance
180
+ - O(1) `read_last()` via direct atomic load (improved from reserved_info calculation)
181
+ - Reduced header size: 8-byte reserved_info vs 32-byte padded (24 bytes saved)
182
+ - Lock-free `last_published_` tracking enables efficient concurrent access
183
+
184
+ ### Compatibility
185
+ - **Backward Compatible:** Automatically detects and supports legacy queue format
186
+ - **C++ Interop:** Full compatibility with C++ slick-queue modern format
187
+ - Python and C++ can share queues created by either language
188
+ - All existing tests continue to pass (atomic ops, cursors, local mode, multi-producer, interop)
189
+
190
+ ## [v1.0.1] - 2025-12-27
191
+
192
+ ### Fixed
193
+ - Fixed extra `/` prefix being added to shared memory names on POSIX systems (Linux/macOS)
194
+ - Fixed test failures on Linux and macOS platforms
195
+
196
+ ### Added
197
+ - macOS shared memory name length validation (31 character limit including `/` prefix)
198
+ - C++ extension support for Linux/macOS platforms with automatic fallback to native methods if unavailable
199
+ - `atomic_store_64` and `atomic_cas_64` functions to C++ extension for improved cross-platform consistency
200
+
201
+ ### Changed
202
+ - 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
203
+ - Improved atomic operation reliability across all platforms
204
+ - Enhanced test suite for better cross-platform compatibility
205
+ - Publishing: Updated PyPI builds to use `cibuildwheel` for proper manylinux wheel generation
206
+ - Ignore conda publish failing for now
207
+
208
+ ## [v1.0.0] - 2025-12-26
209
+
210
+ - Initial release
211
+ - Python implementation of SlickQueue - a lock-free multi-producer multi-consumer (MPMC) queue with C++ interoperability through shared memory.
212
+ - Windows, Linux, and macOS support