slick-queue-py 1.1.0__tar.gz → 1.2.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-1.1.0 → slick_queue_py-1.2.0}/CHANGELOG.md +96 -79
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/PKG-INFO +1 -1
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/slick_queue_py.egg-info/SOURCES.txt +1 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/slick_queue_py.py +48 -1
- slick_queue_py-1.2.0/tests/test_loss_count.py +185 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/.github/FUNDING.yml +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/.github/workflows/ci.yml +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/.github/workflows/publish-conda.yml +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/.github/workflows/publish-pypi.yml +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/.github/workflows/publish-release.yml +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/.github/workflows/release.yml +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/.gitignore +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/.pypirc.template +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/API_DIFFERENCES.md +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/BUILDING.md +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/CMakeLists.txt +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/LICENSE +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/MANIFEST.in +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/README.md +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/atomic_ops.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/atomic_ops_ext.cpp +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/conda.recipe/meta.yaml +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/pyproject.toml +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/requirements.txt +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/setup.cfg +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/setup.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/cleanup_shm.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/cpp_consumer.cpp +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/cpp_multi_producer.cpp +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/cpp_producer.cpp +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/cpp_read_last_tester.cpp +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/cpp_work_stealing_consumer.cpp +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/run_test.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_atomic_cursor.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_atomic_ops.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_interop.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_local_mode.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_modern_format.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_multi_producer.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_queue.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_read_last.py +0 -0
- {slick_queue_py-1.1.0 → slick_queue_py-1.2.0}/tests/test_read_last_interop.py +0 -0
|
@@ -1,80 +1,97 @@
|
|
|
1
|
-
# Changelogs
|
|
2
|
-
|
|
3
|
-
## [v1.
|
|
4
|
-
|
|
5
|
-
###
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
-
|
|
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
|
-
### Fixed
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
###
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
###
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
1
|
+
# Changelogs
|
|
2
|
+
|
|
3
|
+
## [v1.2.0] - 2026-07-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `loss_count()`: per-instance count of items skipped due to overwrite, matching
|
|
7
|
+
C++ slick-queue v1.5.0 (queue.h:230-236). Counted in both the single-consumer
|
|
8
|
+
read (slot overwritten by a newer generation at the same position) and the
|
|
9
|
+
AtomicCursor work-stealing read (overrun attributed to the claiming consumer).
|
|
10
|
+
Cleared by `reset()`. Note: Python always counts; C++ counts only when
|
|
11
|
+
`SLICK_QUEUE_ENABLE_LOSS_DETECTION` is enabled (debug builds by default).
|
|
12
|
+
- `initial_reading_index()`: cursor for a late-joining consumer - 0 for a newly
|
|
13
|
+
created queue, or the current writing index of an opened queue, matching C++
|
|
14
|
+
slick-queue v1.5.0 (queue.h:242-244).
|
|
15
|
+
- `tests/test_loss_count.py`: 7 tests covering both new methods.
|
|
16
|
+
|
|
17
|
+
These are per-instance API additions only - the shared memory layout is unchanged
|
|
18
|
+
and remains fully compatible with C++ slick-queue v1.x ('SLQ1' format).
|
|
19
|
+
|
|
20
|
+
## [v1.1.0] - 2026-02-04
|
|
21
|
+
|
|
22
|
+
### Breaking Changes
|
|
23
|
+
- **BREAKING:** `read_last()` return signature changed from `Optional[bytes]` to `Tuple[Optional[bytes], int]`
|
|
24
|
+
- Now returns `(data, size)` tuple where `size` indicates the number of slots the item occupies
|
|
25
|
+
- Migration: Change `data = q.read_last()` to `data, size = q.read_last()`
|
|
26
|
+
- Provides actual slot size information instead of always returning full `element_size`
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Modern format support with efficient `last_published_` atomic tracking
|
|
30
|
+
- New `last_published_` atomic field at offset 16 for O(1) `read_last()` performance
|
|
31
|
+
- Header magic number `0x534C5131` ('SLQ1') at offset 24 for format detection
|
|
32
|
+
- Init state atomic at offset 48 for CAS-based ownership detection
|
|
33
|
+
- Automatic format detection distinguishes modern vs legacy queues
|
|
34
|
+
- CAS-based queue ownership detection matching C++ implementation (queue.h:618-648)
|
|
35
|
+
- Helper methods `_wait_for_shared_memory_ready()` and `_detect_format_version()`
|
|
36
|
+
- Comprehensive test suites:
|
|
37
|
+
- `test_read_last.py`: 10 tests covering empty queue, single/multiple items, multi-slot publishes, concurrent publishes, reset, shared memory, wraparound, variable sizes
|
|
38
|
+
- `test_read_last_interop.py`: 5 tests for Python ↔ C++ interoperability
|
|
39
|
+
- `test_modern_format.py`: 6 tests for format compatibility and memory layout verification
|
|
40
|
+
- C++ test program `cpp_read_last_tester.cpp` for read_last() interoperability testing
|
|
41
|
+
- New constants: `SIZE_OFFSET`, `ELEMENT_SIZE_OFFSET`, `LAST_PUBLISHED_OFFSET`, `HEADER_MAGIC_OFFSET`, `HEADER_MAGIC`, `INIT_STATE_OFFSET`, `INIT_STATE_*`, `K_INVALID_INDEX`
|
|
42
|
+
- Documentation: `READ_LAST_INTEROP.md` comprehensive interoperability guide
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- Updated shared memory header layout to match C++ slick-queue v1.x:
|
|
46
|
+
- Reserved info: 8 bytes at offset 0-7 (compact from previous 32-byte padded format)
|
|
47
|
+
- Size: 4 bytes at offset 8-11 (moved from offset 32)
|
|
48
|
+
- Element size: 4 bytes at offset 12-15 (moved from offset 36)
|
|
49
|
+
- Last published: 8 bytes at offset 16-23 (NEW)
|
|
50
|
+
- Header magic: 4 bytes at offset 24-27 (NEW)
|
|
51
|
+
- Init state: 4 bytes at offset 48-51 (NEW)
|
|
52
|
+
- `publish()` now updates `last_published_` atomic using CAS loop in modern format (queue.h:331-337)
|
|
53
|
+
- `reset()` now resets `last_published_` to `K_INVALID_INDEX` when in modern format
|
|
54
|
+
- `close()` properly releases `_atomic_last_published` wrapper
|
|
55
|
+
- `AtomicReservedInfo.RESERVED_INFO_FMT` simplified from 32-byte to 8-byte format
|
|
56
|
+
- Repository name changed from slick_queue_py to slick-queue-py
|
|
57
|
+
- Updated README.md with new `read_last()` API documentation
|
|
58
|
+
- Enhanced CMakeLists.txt with new test targets
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- `read_last()` now returns actual slot size instead of always returning full `element_size`
|
|
62
|
+
- Test queue names shortened to comply with macOS 31-character shared memory name limit
|
|
63
|
+
|
|
64
|
+
### Performance
|
|
65
|
+
- O(1) `read_last()` via direct atomic load (improved from reserved_info calculation)
|
|
66
|
+
- Reduced header size: 8-byte reserved_info vs 32-byte padded (24 bytes saved)
|
|
67
|
+
- Lock-free `last_published_` tracking enables efficient concurrent access
|
|
68
|
+
|
|
69
|
+
### Compatibility
|
|
70
|
+
- **Backward Compatible:** Automatically detects and supports legacy queue format
|
|
71
|
+
- **C++ Interop:** Full compatibility with C++ slick-queue modern format
|
|
72
|
+
- Python and C++ can share queues created by either language
|
|
73
|
+
- All existing tests continue to pass (atomic ops, cursors, local mode, multi-producer, interop)
|
|
74
|
+
|
|
75
|
+
## [v1.0.1] - 2025-12-27
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
- Fixed extra `/` prefix being added to shared memory names on POSIX systems (Linux/macOS)
|
|
79
|
+
- Fixed test failures on Linux and macOS platforms
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
- macOS shared memory name length validation (31 character limit including `/` prefix)
|
|
83
|
+
- C++ extension support for Linux/macOS platforms with automatic fallback to native methods if unavailable
|
|
84
|
+
- `atomic_store_64` and `atomic_cas_64` functions to C++ extension for improved cross-platform consistency
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
- 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
|
|
88
|
+
- Improved atomic operation reliability across all platforms
|
|
89
|
+
- Enhanced test suite for better cross-platform compatibility
|
|
90
|
+
- Publishing: Updated PyPI builds to use `cibuildwheel` for proper manylinux wheel generation
|
|
91
|
+
- Ignore conda publish failing for now
|
|
92
|
+
|
|
93
|
+
## [v1.0.0] - 2025-12-26
|
|
94
|
+
|
|
95
|
+
- Initial release
|
|
96
|
+
- Python implementation of SlickQueue - a lock-free multi-producer multi-consumer (MPMC) queue with C++ interoperability through shared memory.
|
|
80
97
|
- Windows, Linux, and macOS support
|
|
@@ -20,7 +20,7 @@ Supported on Python 3.8+ (uses multiprocessing.shared_memory).
|
|
|
20
20
|
"""
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
|
-
__version__ = '1.
|
|
23
|
+
__version__ = '1.2.0'
|
|
24
24
|
|
|
25
25
|
import struct
|
|
26
26
|
import sys
|
|
@@ -104,6 +104,8 @@ class SlickQueue:
|
|
|
104
104
|
self._own = False
|
|
105
105
|
self._last_published_valid = False
|
|
106
106
|
self._atomic_last_published = None
|
|
107
|
+
# per-instance loss counter (matches C++ queue.h:98 loss_count_; local, not shared)
|
|
108
|
+
self._loss_count = 0
|
|
107
109
|
|
|
108
110
|
# Validate parameters
|
|
109
111
|
if size is not None:
|
|
@@ -529,6 +531,12 @@ class SlickQueue:
|
|
|
529
531
|
read_index = 0
|
|
530
532
|
continue
|
|
531
533
|
|
|
534
|
+
# Loss detection (C++ queue.h:357-361): the slot holds a newer
|
|
535
|
+
# generation at the same position - the items in between were
|
|
536
|
+
# overwritten before this consumer read them
|
|
537
|
+
if data_index != (2**64 - 1) and data_index > read_index and ((data_index & self.mask) == idx):
|
|
538
|
+
self._loss_count += data_index - read_index
|
|
539
|
+
|
|
532
540
|
# Check if data is ready (C++ lines 258-261)
|
|
533
541
|
if data_index == (2**64 - 1) or data_index < read_index:
|
|
534
542
|
return None, 0, read_index
|
|
@@ -583,6 +591,12 @@ class SlickQueue:
|
|
|
583
591
|
if data_index == (2**64 - 1) or data_index < current_index:
|
|
584
592
|
return None, 0, -1
|
|
585
593
|
|
|
594
|
+
# Loss detection (C++ queue.h:406-411): the slot holds a newer
|
|
595
|
+
# generation at the same position; count only if we claim it below
|
|
596
|
+
overrun = 0
|
|
597
|
+
if data_index > current_index and ((data_index & self.mask) == idx):
|
|
598
|
+
overrun = data_index - current_index
|
|
599
|
+
|
|
586
600
|
# Check for wrap (C++ lines 300-304)
|
|
587
601
|
if data_index > current_index and ((data_index & self.mask) != idx):
|
|
588
602
|
# Try to atomically update cursor to skip wrapped slots
|
|
@@ -595,12 +609,42 @@ class SlickQueue:
|
|
|
595
609
|
|
|
596
610
|
if success:
|
|
597
611
|
# Successfully claimed the item, read and return it
|
|
612
|
+
# (loss attributed to the claiming consumer, C++ queue.h:422-426)
|
|
613
|
+
if overrun != 0:
|
|
614
|
+
self._loss_count += overrun
|
|
598
615
|
data_off = self._data_offset + (current_index & self.mask) * self.element_size
|
|
599
616
|
data = bytes(self._buf[data_off: data_off + slot_size * self.element_size])
|
|
600
617
|
return data, slot_size, current_index
|
|
601
618
|
|
|
602
619
|
# CAS failed, another consumer claimed it, retry
|
|
603
620
|
|
|
621
|
+
def loss_count(self) -> int:
|
|
622
|
+
"""
|
|
623
|
+
Get the number of items skipped due to overwrite observed by this
|
|
624
|
+
queue instance.
|
|
625
|
+
|
|
626
|
+
Matches C++ queue.h:230-236 (added in slick-queue v1.5.0). The counter
|
|
627
|
+
is per-instance (not shared through the segment). Note: Python always
|
|
628
|
+
counts; C++ counts only when SLICK_QUEUE_ENABLE_LOSS_DETECTION is
|
|
629
|
+
enabled (debug builds by default).
|
|
630
|
+
|
|
631
|
+
Returns:
|
|
632
|
+
Count of skipped items observed by this instance.
|
|
633
|
+
"""
|
|
634
|
+
return self._loss_count
|
|
635
|
+
|
|
636
|
+
def initial_reading_index(self) -> int:
|
|
637
|
+
"""
|
|
638
|
+
Get the initial reading index for a late-joining consumer: 0 if the
|
|
639
|
+
queue is newly created, or the current writing index if opened existing.
|
|
640
|
+
|
|
641
|
+
Matches C++ queue.h:242-244 (added in slick-queue v1.5.0).
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
Initial reading index.
|
|
645
|
+
"""
|
|
646
|
+
return self._atomic_reserved.load()[0]
|
|
647
|
+
|
|
604
648
|
def read_last(self) -> Tuple[Optional[bytes], int]:
|
|
605
649
|
"""
|
|
606
650
|
Read the last published data in the queue.
|
|
@@ -655,6 +699,9 @@ class SlickQueue:
|
|
|
655
699
|
if self._last_published_valid:
|
|
656
700
|
self._atomic_last_published.store_release(K_INVALID_INDEX)
|
|
657
701
|
|
|
702
|
+
# Reset loss counter (C++ queue.h:474-476)
|
|
703
|
+
self._loss_count = 0
|
|
704
|
+
|
|
658
705
|
def close(self) -> None:
|
|
659
706
|
"""Close the queue connection.
|
|
660
707
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for loss_count() and initial_reading_index() (added in v1.2.0,
|
|
3
|
+
matching C++ slick-queue v1.5.0 queue.h:230-244).
|
|
4
|
+
"""
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
9
|
+
|
|
10
|
+
from slick_queue_py import SlickQueue
|
|
11
|
+
from atomic_ops import AtomicCursor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_initial_reading_index_new_queue():
|
|
15
|
+
"""A newly created queue starts writing at index 0."""
|
|
16
|
+
q = SlickQueue(size=16, element_size=8)
|
|
17
|
+
assert q.initial_reading_index() == 0
|
|
18
|
+
q.close()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_initial_reading_index_after_publish():
|
|
22
|
+
"""initial_reading_index equals the current writing index."""
|
|
23
|
+
q = SlickQueue(size=16, element_size=8)
|
|
24
|
+
for i in range(5):
|
|
25
|
+
idx = q.reserve()
|
|
26
|
+
q[idx][:8] = i.to_bytes(8, 'little')
|
|
27
|
+
q.publish(idx)
|
|
28
|
+
assert q.initial_reading_index() == 5
|
|
29
|
+
|
|
30
|
+
# a late joiner starting there sees nothing until a new publish
|
|
31
|
+
cursor = q.initial_reading_index()
|
|
32
|
+
data, size, cursor = q.read(cursor)
|
|
33
|
+
assert data is None
|
|
34
|
+
|
|
35
|
+
idx = q.reserve()
|
|
36
|
+
q[idx][:8] = (99).to_bytes(8, 'little')
|
|
37
|
+
q.publish(idx)
|
|
38
|
+
data, size, cursor = q.read(cursor)
|
|
39
|
+
assert data is not None
|
|
40
|
+
assert int.from_bytes(data[:8], 'little') == 99
|
|
41
|
+
q.close()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_initial_reading_index_shm_opener():
|
|
45
|
+
"""An opener of an existing shm queue sees the creator's write index."""
|
|
46
|
+
creator = SlickQueue(name="slq_test_init_idx", size=16, element_size=8)
|
|
47
|
+
try:
|
|
48
|
+
for i in range(3):
|
|
49
|
+
idx = creator.reserve()
|
|
50
|
+
creator[idx][:8] = i.to_bytes(8, 'little')
|
|
51
|
+
creator.publish(idx)
|
|
52
|
+
|
|
53
|
+
opener = SlickQueue(name="slq_test_init_idx", element_size=8)
|
|
54
|
+
try:
|
|
55
|
+
assert opener.initial_reading_index() == 3
|
|
56
|
+
finally:
|
|
57
|
+
opener.close()
|
|
58
|
+
finally:
|
|
59
|
+
creator.close()
|
|
60
|
+
creator.unlink()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_no_loss_on_normal_reads():
|
|
64
|
+
"""Sequential reads with no overwrite never count loss."""
|
|
65
|
+
q = SlickQueue(size=16, element_size=8)
|
|
66
|
+
for i in range(10):
|
|
67
|
+
idx = q.reserve()
|
|
68
|
+
q[idx][:8] = i.to_bytes(8, 'little')
|
|
69
|
+
q.publish(idx)
|
|
70
|
+
|
|
71
|
+
cursor = 0
|
|
72
|
+
for i in range(10):
|
|
73
|
+
data, size, cursor = q.read(cursor)
|
|
74
|
+
assert int.from_bytes(data[:8], 'little') == i
|
|
75
|
+
assert q.loss_count() == 0
|
|
76
|
+
q.close()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_single_consumer_loss_on_lap():
|
|
80
|
+
"""A lapped consumer skips overwritten items and counts them as loss
|
|
81
|
+
(C++ queue.h:357-361)."""
|
|
82
|
+
q = SlickQueue(size=4, element_size=8)
|
|
83
|
+
for i in range(8): # laps the 4-slot queue once
|
|
84
|
+
idx = q.reserve()
|
|
85
|
+
q[idx][:8] = i.to_bytes(8, 'little')
|
|
86
|
+
q.publish(idx)
|
|
87
|
+
|
|
88
|
+
# slot 0 now holds item 4; a cursor at 0 must return item 4 and count 4 lost
|
|
89
|
+
data, size, cursor = q.read(0)
|
|
90
|
+
assert data is not None
|
|
91
|
+
assert int.from_bytes(data[:8], 'little') == 4
|
|
92
|
+
assert q.loss_count() == 4, f"expected loss 4, got {q.loss_count()}"
|
|
93
|
+
assert cursor == 5
|
|
94
|
+
|
|
95
|
+
for i in range(5, 8):
|
|
96
|
+
data, size, cursor = q.read(cursor)
|
|
97
|
+
assert int.from_bytes(data[:8], 'little') == i
|
|
98
|
+
assert q.loss_count() == 4 # no further loss
|
|
99
|
+
q.close()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_atomic_cursor_loss_on_lap():
|
|
103
|
+
"""Work-stealing consumers count loss when claiming a lapped slot
|
|
104
|
+
(C++ queue.h:406-426)."""
|
|
105
|
+
q = SlickQueue(size=4, element_size=8)
|
|
106
|
+
for i in range(8):
|
|
107
|
+
idx = q.reserve()
|
|
108
|
+
q[idx][:8] = i.to_bytes(8, 'little')
|
|
109
|
+
q.publish(idx)
|
|
110
|
+
|
|
111
|
+
cursor_buf = bytearray(8)
|
|
112
|
+
cursor = AtomicCursor(cursor_buf, 0)
|
|
113
|
+
cursor.store(0)
|
|
114
|
+
|
|
115
|
+
data, size, index = q.read(cursor)
|
|
116
|
+
assert data is not None
|
|
117
|
+
assert int.from_bytes(data[:8], 'little') == 4
|
|
118
|
+
assert q.loss_count() == 4, f"expected loss 4, got {q.loss_count()}"
|
|
119
|
+
|
|
120
|
+
remaining = []
|
|
121
|
+
while True:
|
|
122
|
+
data, size, index = q.read(cursor)
|
|
123
|
+
if data is None:
|
|
124
|
+
break
|
|
125
|
+
remaining.append(int.from_bytes(data[:8], 'little'))
|
|
126
|
+
assert remaining == [5, 6, 7]
|
|
127
|
+
assert q.loss_count() == 4
|
|
128
|
+
cursor.release()
|
|
129
|
+
q.close()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_reset_clears_loss_count():
|
|
133
|
+
"""reset() zeroes the loss counter (C++ queue.h:474-476)."""
|
|
134
|
+
q = SlickQueue(size=4, element_size=8)
|
|
135
|
+
for i in range(8):
|
|
136
|
+
idx = q.reserve()
|
|
137
|
+
q[idx][:8] = i.to_bytes(8, 'little')
|
|
138
|
+
q.publish(idx)
|
|
139
|
+
q.read(0)
|
|
140
|
+
assert q.loss_count() == 4
|
|
141
|
+
|
|
142
|
+
q.reset()
|
|
143
|
+
assert q.loss_count() == 0
|
|
144
|
+
q.close()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def run_all_tests():
|
|
148
|
+
tests = [
|
|
149
|
+
("InitialReadingIndexNewQueue", test_initial_reading_index_new_queue),
|
|
150
|
+
("InitialReadingIndexAfterPublish", test_initial_reading_index_after_publish),
|
|
151
|
+
("InitialReadingIndexShmOpener", test_initial_reading_index_shm_opener),
|
|
152
|
+
("NoLossOnNormalReads", test_no_loss_on_normal_reads),
|
|
153
|
+
("SingleConsumerLossOnLap", test_single_consumer_loss_on_lap),
|
|
154
|
+
("AtomicCursorLossOnLap", test_atomic_cursor_loss_on_lap),
|
|
155
|
+
("ResetClearsLossCount", test_reset_clears_loss_count),
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
print("=" * 70)
|
|
159
|
+
print("Running Loss Count / Initial Reading Index Tests")
|
|
160
|
+
print("=" * 70)
|
|
161
|
+
|
|
162
|
+
passed = 0
|
|
163
|
+
failed = 0
|
|
164
|
+
for name, test_func in tests:
|
|
165
|
+
try:
|
|
166
|
+
print(f"\n[TEST] {name}...", end=" ")
|
|
167
|
+
test_func()
|
|
168
|
+
print("[PASSED]")
|
|
169
|
+
passed += 1
|
|
170
|
+
except Exception as e:
|
|
171
|
+
print("[FAILED]")
|
|
172
|
+
print(f" Error: {e}")
|
|
173
|
+
import traceback
|
|
174
|
+
traceback.print_exc()
|
|
175
|
+
failed += 1
|
|
176
|
+
|
|
177
|
+
print("\n" + "=" * 70)
|
|
178
|
+
print(f"Results: {passed} passed, {failed} failed")
|
|
179
|
+
print("=" * 70)
|
|
180
|
+
return failed == 0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == '__main__':
|
|
184
|
+
success = run_all_tests()
|
|
185
|
+
sys.exit(0 if success else 1)
|
|
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
|