mmapq 0.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.
- mmapq-0.1.0/LICENSE +29 -0
- mmapq-0.1.0/MANIFEST.in +6 -0
- mmapq-0.1.0/PKG-INFO +427 -0
- mmapq-0.1.0/README.md +399 -0
- mmapq-0.1.0/c/include/batch_payload_iterator.h +141 -0
- mmapq-0.1.0/c/include/batch_queue_transfer.h +274 -0
- mmapq-0.1.0/c/include/indexed_appender.h +225 -0
- mmapq-0.1.0/c/include/indexed_archiver.h +148 -0
- mmapq-0.1.0/c/include/indexed_batch_appender.h +295 -0
- mmapq-0.1.0/c/include/indexed_batch_archiver.h +125 -0
- mmapq-0.1.0/c/include/indexed_batch_poller.h +197 -0
- mmapq-0.1.0/c/include/indexed_batch_queue.h +168 -0
- mmapq-0.1.0/c/include/indexed_batch_reader.h +244 -0
- mmapq-0.1.0/c/include/indexed_poller.h +190 -0
- mmapq-0.1.0/c/include/indexed_queue.h +229 -0
- mmapq-0.1.0/c/include/indexed_reader.h +172 -0
- mmapq-0.1.0/c/include/mmap_platform.h +160 -0
- mmapq-0.1.0/c/include/mmapq.h +27 -0
- mmapq-0.1.0/c/include/poller.h +54 -0
- mmapq-0.1.0/c/include/queue_transfer.h +312 -0
- mmapq-0.1.0/c/include/runtime.h +316 -0
- mmapq-0.1.0/c/src/appender_id_pool.c +177 -0
- mmapq-0.1.0/c/src/appender_meta.c +146 -0
- mmapq-0.1.0/c/src/batch_payload_iterator.c +133 -0
- mmapq-0.1.0/c/src/batch_queue_latency_probe.c +189 -0
- mmapq-0.1.0/c/src/batch_queue_transfer.c +740 -0
- mmapq-0.1.0/c/src/file_initialiser.c +40 -0
- mmapq-0.1.0/c/src/file_mapper_rolled.c +321 -0
- mmapq-0.1.0/c/src/file_mapper_single_ro.c +168 -0
- mmapq-0.1.0/c/src/file_mapper_single_rw.c +244 -0
- mmapq-0.1.0/c/src/fixed_size_region.c +184 -0
- mmapq-0.1.0/c/src/header_reader.c +161 -0
- mmapq-0.1.0/c/src/indexed_appender.c +690 -0
- mmapq-0.1.0/c/src/indexed_archiver.c +498 -0
- mmapq-0.1.0/c/src/indexed_batch_appender.c +507 -0
- mmapq-0.1.0/c/src/indexed_batch_archiver.c +421 -0
- mmapq-0.1.0/c/src/indexed_batch_poller.c +243 -0
- mmapq-0.1.0/c/src/indexed_batch_queue.c +182 -0
- mmapq-0.1.0/c/src/indexed_batch_reader.c +298 -0
- mmapq-0.1.0/c/src/indexed_poller.c +336 -0
- mmapq-0.1.0/c/src/indexed_queue.c +279 -0
- mmapq-0.1.0/c/src/indexed_reader.c +309 -0
- mmapq-0.1.0/c/src/internal/appender_cache.h +160 -0
- mmapq-0.1.0/c/src/internal/appender_id_pool.h +107 -0
- mmapq-0.1.0/c/src/internal/appender_meta.h +116 -0
- mmapq-0.1.0/c/src/internal/batch_payload_iterator_internal.h +55 -0
- mmapq-0.1.0/c/src/internal/batch_queue_latency_probe.h +40 -0
- mmapq-0.1.0/c/src/internal/export_meta.h +98 -0
- mmapq-0.1.0/c/src/internal/file_initialiser.h +49 -0
- mmapq-0.1.0/c/src/internal/file_mapper.h +164 -0
- mmapq-0.1.0/c/src/internal/fixed_size_region.h +57 -0
- mmapq-0.1.0/c/src/internal/header_codec.h +220 -0
- mmapq-0.1.0/c/src/internal/header_reader.h +115 -0
- mmapq-0.1.0/c/src/internal/mmap_assert.h +34 -0
- mmapq-0.1.0/c/src/internal/mmap_stats.h +62 -0
- mmapq-0.1.0/c/src/internal/payload_iterator.h +157 -0
- mmapq-0.1.0/c/src/internal/payload_walk.h +54 -0
- mmapq-0.1.0/c/src/internal/platform_internal.h +40 -0
- mmapq-0.1.0/c/src/internal/queue_files.h +85 -0
- mmapq-0.1.0/c/src/internal/queue_latency_probe.h +70 -0
- mmapq-0.1.0/c/src/internal/queue_meta.h +227 -0
- mmapq-0.1.0/c/src/internal/queue_region_accessor.h +160 -0
- mmapq-0.1.0/c/src/internal/region.h +164 -0
- mmapq-0.1.0/c/src/internal/region_accessor.h +124 -0
- mmapq-0.1.0/c/src/internal/region_ring.h +109 -0
- mmapq-0.1.0/c/src/internal/region_ring_accessor.h +78 -0
- mmapq-0.1.0/c/src/internal/uncommitted_payload_iterator.h +174 -0
- mmapq-0.1.0/c/src/mmapq_strerror.c +19 -0
- mmapq-0.1.0/c/src/payload_iterator.c +177 -0
- mmapq-0.1.0/c/src/payload_walk.c +87 -0
- mmapq-0.1.0/c/src/queue_files.c +70 -0
- mmapq-0.1.0/c/src/queue_latency_probe.c +205 -0
- mmapq-0.1.0/c/src/queue_meta.c +245 -0
- mmapq-0.1.0/c/src/queue_region_accessor.c +301 -0
- mmapq-0.1.0/c/src/queue_transfer.c +660 -0
- mmapq-0.1.0/c/src/region_async.c +460 -0
- mmapq-0.1.0/c/src/region_ring.c +365 -0
- mmapq-0.1.0/c/src/region_ring_accessor.c +205 -0
- mmapq-0.1.0/c/src/region_sync.c +250 -0
- mmapq-0.1.0/c/src/runtime.c +861 -0
- mmapq-0.1.0/c/src/uncommitted_payload_iterator.c +383 -0
- mmapq-0.1.0/pyproject.toml +42 -0
- mmapq-0.1.0/setup.cfg +4 -0
- mmapq-0.1.0/setup.py +375 -0
- mmapq-0.1.0/src/mmapq/__init__.py +105 -0
- mmapq-0.1.0/src/mmapq/_cdata.py +23 -0
- mmapq-0.1.0/src/mmapq/_errors.py +78 -0
- mmapq-0.1.0/src/mmapq/archiver.py +155 -0
- mmapq-0.1.0/src/mmapq/batch_appender.py +123 -0
- mmapq-0.1.0/src/mmapq/batch_poller.py +144 -0
- mmapq-0.1.0/src/mmapq/batch_queue.py +148 -0
- mmapq-0.1.0/src/mmapq/reader.py +328 -0
- mmapq-0.1.0/src/mmapq/runtime.py +56 -0
- mmapq-0.1.0/src/mmapq/single_appender.py +82 -0
- mmapq-0.1.0/src/mmapq/single_poller.py +117 -0
- mmapq-0.1.0/src/mmapq/single_queue.py +126 -0
- mmapq-0.1.0/src/mmapq/transfer.py +449 -0
- mmapq-0.1.0/src/mmapq/types.py +79 -0
- mmapq-0.1.0/src/mmapq.egg-info/PKG-INFO +427 -0
- mmapq-0.1.0/src/mmapq.egg-info/SOURCES.txt +138 -0
- mmapq-0.1.0/src/mmapq.egg-info/dependency_links.txt +1 -0
- mmapq-0.1.0/src/mmapq.egg-info/requires.txt +4 -0
- mmapq-0.1.0/src/mmapq.egg-info/top_level.txt +1 -0
- mmapq-0.1.0/tests/test_poller_lifecycle.py +154 -0
- mmapq-0.1.0/tests/test_queue.py +1865 -0
mmapq-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Anton Anufriev, Algoteq Pty Ltd, and contributors
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
this list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
POSSIBILITY OF SUCH DAMAGE.
|
mmapq-0.1.0/MANIFEST.in
ADDED
mmapq-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mmapq
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python bindings for mmapq — low-latency memory-mapped queue
|
|
5
|
+
Author: Anton Anufriev
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/algoteq-labs/mmapq
|
|
8
|
+
Project-URL: Source, https://github.com/algoteq-labs/mmapq-py
|
|
9
|
+
Project-URL: BugTracker, https://github.com/algoteq-labs/mmapq-py/issues
|
|
10
|
+
Keywords: mmap,shared-memory,queue,lock-free,high-performance
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: C
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: cffi
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
|
|
29
|
+
# mmapq
|
|
30
|
+
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
|
|
33
|
+
**Python bindings for [mmapq](https://github.com/algoteq-labs/mmapq)** — a persistent,
|
|
34
|
+
indexed, replayable inter-process queue backed by memory-mapped files. No
|
|
35
|
+
broker, no daemon; the queue is just files.
|
|
36
|
+
|
|
37
|
+
## Why Python for a sub-microsecond queue?
|
|
38
|
+
|
|
39
|
+
Not for the hot path — for the *read* side. The pattern mmapq is built for
|
|
40
|
+
fast producers (C or Rust: market data, sequenced commands, sensor and
|
|
41
|
+
telemetry streams) on one side of a persistent, indexed log, and lets any
|
|
42
|
+
number of consumers read that log independently. Python is the natural
|
|
43
|
+
language for the consumer: replay, analysis, notebooks, dashboards,
|
|
44
|
+
projections into pandas — over the exact same queue files the fast
|
|
45
|
+
producers write, with no serialization bridge, no message broker, and no
|
|
46
|
+
copy out of shared memory beyond the one `bytes()` you ask for.
|
|
47
|
+
|
|
48
|
+
Because the queue is indexed and replayable, the Python side gets the
|
|
49
|
+
capability the whole architecture exists for: **replay is the product.**
|
|
50
|
+
Copy a queue directory from production to a laptop and re-read it from index
|
|
51
|
+
zero — the same operation that drives recovery and replication is also how
|
|
52
|
+
you debug, backtest, and audit. "What did command K do" is a single indexed
|
|
53
|
+
read from a Python script. `doc/event-sourcing-guide.md` in the
|
|
54
|
+
[mmapq](https://github.com/algoteq-labs/mmapq) repository describes the
|
|
55
|
+
full pattern; `doc/feature-matrix.md` gives exact coverage across C, Rust,
|
|
56
|
+
and Python.
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
### Single-message queue
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from mmapq import IndexedQueue, NextMove
|
|
64
|
+
|
|
65
|
+
q = IndexedQueue.create("/dev/shm", "my_queue")
|
|
66
|
+
|
|
67
|
+
appender = q.create_appender()
|
|
68
|
+
appender.append(b"hello world") # committed atomically
|
|
69
|
+
|
|
70
|
+
def handler(index, appender_id, data):
|
|
71
|
+
print(f"[{index}] {data!r}")
|
|
72
|
+
return NextMove.ADVANCE
|
|
73
|
+
|
|
74
|
+
poller = q.create_poller()
|
|
75
|
+
poller.poll(handler)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Batch queue (atomic multi-message commit)
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from mmapq import IndexedBatchQueue, NextMove
|
|
82
|
+
|
|
83
|
+
q = IndexedBatchQueue.create("/dev/shm", "events")
|
|
84
|
+
appender = q.create_appender()
|
|
85
|
+
|
|
86
|
+
with appender.start_batch() as batch: # context manager = transaction
|
|
87
|
+
batch.append(b"event 1")
|
|
88
|
+
batch.append(b"event 2")
|
|
89
|
+
batch.append(b"event 3")
|
|
90
|
+
# leaving the block discards the batch — call batch.commit() to persist;
|
|
91
|
+
# an exception inside the block also discards it, so uncommitted data
|
|
92
|
+
# never becomes visible. This is the command/event rollback semantics.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Reading and replay (random access)
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from mmapq import IndexedReader, export_queue
|
|
99
|
+
|
|
100
|
+
reader = IndexedReader.create("/dev/shm", "events")
|
|
101
|
+
first = reader.read_first() # bytes | None
|
|
102
|
+
last = reader.read_last()
|
|
103
|
+
msg = reader.read(42) # read any committed index
|
|
104
|
+
print(reader.last_index())
|
|
105
|
+
|
|
106
|
+
# Export the durable stream for long-term storage or offline analysis:
|
|
107
|
+
export_queue("/dev/shm", "events", "/data/events-2026-07.mmapq")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## A note on validation and safety
|
|
111
|
+
|
|
112
|
+
The Python extension compiles the C library with `-DNDEBUG`, so it runs the
|
|
113
|
+
*release* build with internal asserts stripped. Input validation therefore
|
|
114
|
+
happens in the Python layer and is mandatory, not defensive decoration:
|
|
115
|
+
empty appends, out-of-range indices, and closed-handle use are checked
|
|
116
|
+
before the FFI call and raise `ValueError` / `QueueClosedError`. Corruption
|
|
117
|
+
detected on open or during a poll raises `QueueCorruptError` (distinct from
|
|
118
|
+
a transient timeout) rather than returning silently. Every wrapper object
|
|
119
|
+
supports explicit `close()`, is a context manager, and is finalized safely
|
|
120
|
+
at interpreter shutdown.
|
|
121
|
+
|
|
122
|
+
The raw cffi `FFI`/`lib` objects are deliberately **not** exported as
|
|
123
|
+
`mmapq.ffi` / `mmapq.lib`. Every C symbol reachable through them has no
|
|
124
|
+
argument checking — one bad call segfaults the interpreter. If you need
|
|
125
|
+
low-level access (e.g. iterator access), reach the raw objects only through
|
|
126
|
+
the documented escape hatches, and only when you know what you are doing:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from mmapq import unsafe_ffi, unsafe_lib
|
|
130
|
+
ffi = unsafe_ffi() # cffi FFI object
|
|
131
|
+
lib = unsafe_lib() # loaded C library
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Error messages
|
|
135
|
+
|
|
136
|
+
Exception messages are the single source of truth in the C library
|
|
137
|
+
(`src/mmapq_strerror.c`): the Python layer maps return codes to exception
|
|
138
|
+
*types* only (e.g. `QueueCorruptError`), and the message text comes from
|
|
139
|
+
`mmapq_strerror` via cffi. This guarantees the text in Python matches C
|
|
140
|
+
by construction and never drifts independently.
|
|
141
|
+
|
|
142
|
+
| Return code | Exception type |
|
|
143
|
+
|---|---:|
|
|
144
|
+
| `MMAPQ_ETIMEDOUT` (-1) | `MmapqError` |
|
|
145
|
+
| `MMAPQ_EINVAL` (-2) | `ValueError` |
|
|
146
|
+
| `MMAPQ_ECLOSED` (-3) | `QueueClosedError` |
|
|
147
|
+
| `MMAPQ_ECORRUPT` (-4) | `QueueCorruptError` |
|
|
148
|
+
| `MMAPQ_ESCHEMA` (-5) | `SchemaMismatchError` |
|
|
149
|
+
| `MMAPQ_EEXIST` (-6) | `QueueExistsError` |
|
|
150
|
+
| `MMAPQ_ENOENT` (-7) | `FileNotFoundError` |
|
|
151
|
+
| `MMAPQ_EIDCONFLICT` (-8) | `AppenderIdConflictError` |
|
|
152
|
+
| `MMAPQ_ENOMEM` (-9) | `MmapOutOfMemoryError` |
|
|
153
|
+
| `MMAPQ_EIO` (-10) | `MmapqError` |
|
|
154
|
+
| `MMAPQ_ENOTREADY` (-11) | `MmapqError` |
|
|
155
|
+
|
|
156
|
+
## Installation
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
pip install mmapq
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### From source
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pip install -e . # builds the cffi extension against the C sources
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## API
|
|
169
|
+
|
|
170
|
+
### Exports
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from mmapq import (
|
|
174
|
+
# Shared types
|
|
175
|
+
NextMove, # Handler return: ADVANCE, STOP, RETREAT
|
|
176
|
+
PollResult, # Poll result: END_OF_STREAM, ADVANCED, etc.
|
|
177
|
+
BatchPollResult, # Alias for PollResult
|
|
178
|
+
BatchEntryStatus, # Batch availability
|
|
179
|
+
EntryStatus, # Entry availability (reader)
|
|
180
|
+
|
|
181
|
+
# Single-message queue
|
|
182
|
+
IndexedQueue, # Queue factory
|
|
183
|
+
IndexedAppender, # Producer
|
|
184
|
+
IndexedPoller, # Sequential consumer
|
|
185
|
+
|
|
186
|
+
# Batch queue
|
|
187
|
+
IndexedBatchQueue, # Queue factory
|
|
188
|
+
IndexedBatchPoller, # Sequential batch consumer
|
|
189
|
+
IndexedBatchAppender, # Batch producer
|
|
190
|
+
BatchWriter, # In-progress batch (context manager)
|
|
191
|
+
|
|
192
|
+
# Random-access readers
|
|
193
|
+
IndexedReader, # Single-message reader
|
|
194
|
+
IndexedBatchReader, # Batch reader
|
|
195
|
+
|
|
196
|
+
# Exceptions
|
|
197
|
+
MmapqError, # Base exception
|
|
198
|
+
QueueClosedError, # Closed handle
|
|
199
|
+
QueueCorruptError, # On-disk corruption
|
|
200
|
+
SchemaMismatchError, # Schema/type mismatch
|
|
201
|
+
QueueExistsError, # Destination already exists
|
|
202
|
+
AppenderIdConflictError,# Appender ID conflict
|
|
203
|
+
MmapOutOfMemoryError, # Allocation failure
|
|
204
|
+
|
|
205
|
+
# Runtime
|
|
206
|
+
Runtime, # Async region mapper
|
|
207
|
+
|
|
208
|
+
# Archiver
|
|
209
|
+
IndexedArchiver, # Start-index management (single-message)
|
|
210
|
+
|
|
211
|
+
# Transfer (export/import/copy)
|
|
212
|
+
export_queue, # Export queue to flat stream
|
|
213
|
+
export_batch_queue, # Export batch queue to flat stream
|
|
214
|
+
import_queue, # Import flat stream to queue
|
|
215
|
+
import_batch_stream, # Import batch stream to flat queue
|
|
216
|
+
import_batch_queue, # Import batch stream to batch queue
|
|
217
|
+
import_flat_to_batch, # Import flat stream to batch queue
|
|
218
|
+
copy_queue, # Copy queue to new directory
|
|
219
|
+
copy_batch_queue, # Copy batch queue to new directory
|
|
220
|
+
copy_normal_to_batch, # Copy flat queue as batch queue
|
|
221
|
+
copy_batch_to_normal, # Copy batch queue as flat queue
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Single-message handler callback
|
|
226
|
+
|
|
227
|
+
The handler passed to `poller.poll()` receives `(index: int, appender_id: int, data: bytes)`:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
def handler(index, appender_id, data):
|
|
231
|
+
print(f"[{index}] {data}")
|
|
232
|
+
return NextMove.ADVANCE
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Batch handler callback
|
|
236
|
+
|
|
237
|
+
The handler passed to `poller.poll()` receives `(batch_index: int, messages: list[bytes])`:
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
def handler(batch_index, messages):
|
|
241
|
+
# messages is a list of payload bytes, one per message in the batch
|
|
242
|
+
for msg in messages:
|
|
243
|
+
print(f"[{batch_index}] {msg}")
|
|
244
|
+
return NextMove.ADVANCE
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Additional appender methods
|
|
248
|
+
|
|
249
|
+
`IndexedAppender` (single-message) also provides:
|
|
250
|
+
|
|
251
|
+
- **`append_uncommitted(data)`** — writes payload without committing. Invisible to readers,
|
|
252
|
+
discarded by crash recovery. Returns a payload offset for later use with `commit()`.
|
|
253
|
+
- **`commit(payload_offset)`** — publishes a previously uncommitted payload. Returns the
|
|
254
|
+
committed index.
|
|
255
|
+
|
|
256
|
+
`IndexedQueue` (single-message) also provides:
|
|
257
|
+
|
|
258
|
+
- **`create_appender_with_id(appender_id)`** — creates an appender with a specific lane ID
|
|
259
|
+
(0–255). Raises `ValueError` if the ID is out of range.
|
|
260
|
+
|
|
261
|
+
`IndexedBatchQueue` (batch) also provides:
|
|
262
|
+
|
|
263
|
+
- **`create_appender_with_id(appender_id)`** — creates a batch appender with a specific lane ID
|
|
264
|
+
(0–255). Raises `ValueError` if the ID is out of range.
|
|
265
|
+
- **`create(directory, queue_name, *, region_size=..., ring_size=..., max_file_size=..., start_index=..., timeout_ns=..., archivable=..., runtime=...)`**
|
|
266
|
+
— full-configuration create. All params match the C library's `INDEXED_QUEUE_DEFAULT_*` constants.
|
|
267
|
+
Existing queues ignore sizing params (on-disk config is authoritative).
|
|
268
|
+
|
|
269
|
+
`IndexedBatchAppender` (batch) also provides:
|
|
270
|
+
|
|
271
|
+
- **`id()`** — returns this appender's lane ID (0–255).
|
|
272
|
+
|
|
273
|
+
### Sync vs async mapping
|
|
274
|
+
|
|
275
|
+
By default mapping happens synchronously on the hot path. For async background
|
|
276
|
+
region mapping, pass a `Runtime`:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
from mmapq import Runtime, IndexedQueue
|
|
280
|
+
|
|
281
|
+
rt = Runtime.region_mapper()
|
|
282
|
+
q = IndexedQueue.create("/dev/shm", "rt_queue", runtime=rt)
|
|
283
|
+
|
|
284
|
+
# Runtime is kept alive by the queue — safe to drop the local reference
|
|
285
|
+
del rt
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Archiving
|
|
289
|
+
|
|
290
|
+
An archivable queue (`archivable=True`) can advance its start index so old
|
|
291
|
+
entries become inaccessible and their backing files can be safely deleted:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from mmapq import IndexedArchiver, IndexedQueue
|
|
295
|
+
|
|
296
|
+
q = IndexedQueue.create("/dev/shm", "arch_q", archivable=True)
|
|
297
|
+
# ... produce messages ...
|
|
298
|
+
|
|
299
|
+
arch = IndexedArchiver.create("/dev/shm", "arch_q")
|
|
300
|
+
arch.archive_up_to_index(1000) # archive entries < 1000
|
|
301
|
+
files = arch.archivable_files() # paths safe to delete
|
|
302
|
+
for path in files:
|
|
303
|
+
# delete, compress, or move them
|
|
304
|
+
pass
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Transfer (export/import/copy)
|
|
308
|
+
|
|
309
|
+
The library provides 10 transfer functions across single-message and batch
|
|
310
|
+
queues:
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
from mmapq import (
|
|
314
|
+
export_queue, export_batch_queue,
|
|
315
|
+
import_queue, import_batch_stream, import_batch_queue,
|
|
316
|
+
import_flat_to_batch,
|
|
317
|
+
copy_queue, copy_batch_queue,
|
|
318
|
+
copy_normal_to_batch, copy_batch_to_normal,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Export a queue to a portable flat file (single-message)
|
|
322
|
+
n = export_queue("/dev/shm", "src", "/tmp/export.bin")
|
|
323
|
+
|
|
324
|
+
# Import flat file into a new queue (allow_append=True appends to existing)
|
|
325
|
+
n = import_queue("/tmp/export.bin", "/dev/shm", "restored")
|
|
326
|
+
n = import_queue("/tmp/export.bin", "/dev/shm", "restored", allow_append=True)
|
|
327
|
+
|
|
328
|
+
# Copy queue files directly (avoids intermediate export file)
|
|
329
|
+
n = copy_queue("/dev/shm", "src", "/dev/shm", "copy")
|
|
330
|
+
|
|
331
|
+
# Preserve index space (start_index, appender IDs)
|
|
332
|
+
n = export_queue("/dev/shm", "src", "/tmp/preserve.bin", preserve=True)
|
|
333
|
+
n = import_queue("/tmp/preserve.bin", "/dev/shm", "preserved", preserve=True)
|
|
334
|
+
|
|
335
|
+
# Batch queue transfers
|
|
336
|
+
n = export_batch_queue("/dev/shm", "batch_src", "/tmp/batch_export.bin")
|
|
337
|
+
n = import_batch_queue("/tmp/batch_export.bin", "/dev/shm", "batch_dst")
|
|
338
|
+
n = import_batch_stream("/tmp/batch_export.bin", "/dev/shm", "flat_stream")
|
|
339
|
+
|
|
340
|
+
# Cross-type conversions (flat to batch, batch to flat)
|
|
341
|
+
n = import_flat_to_batch("/tmp/export.bin", "/dev/shm", "batch_queue")
|
|
342
|
+
n = copy_normal_to_batch("/dev/shm", "flat_src", "/dev/shm", "batch_dst")
|
|
343
|
+
n = copy_batch_to_normal("/dev/shm", "batch_src", "/dev/shm", "flat_dst")
|
|
344
|
+
|
|
345
|
+
# All copy/import functions accept:
|
|
346
|
+
# allow_append=True — append to existing queue instead of refusing
|
|
347
|
+
# preserve=True — keep original index space from source
|
|
348
|
+
# mapper=callable — map source appender IDs to destination IDs
|
|
349
|
+
# on_conflict=<enum> — ConflictBehavior.AUTO or .ABORT
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Examples
|
|
353
|
+
|
|
354
|
+
### Batch echo server
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
rm -rf /tmp/request /tmp/response
|
|
358
|
+
python3 examples/batch_echo_server.py /tmp request response &
|
|
359
|
+
python3 examples/batch_latency_bench.py 100000 10000 64 --external
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Internal benchmark (single process)
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
rm -rf /tmp/request /tmp/response
|
|
366
|
+
python3 examples/batch_latency_bench.py 100000 10000 64
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Performance
|
|
370
|
+
|
|
371
|
+
The Python layer adds roughly **1–2 µs** per `poll()` call through the cffi
|
|
372
|
+
callback trampoline (C → Python → C dispatch); the C library itself delivers
|
|
373
|
+
~300 ns median round-trip, so the interpreter dominates the hot path.
|
|
374
|
+
Expected throughput on isolated cores is **100,000–200,000 ops/sec** against
|
|
375
|
+
~2.8M for C. This is by design and not the point: Python is the consumer and
|
|
376
|
+
analysis language here, not the low-latency producer.
|
|
377
|
+
|
|
378
|
+
Reach for the Python bindings when you want:
|
|
379
|
+
|
|
380
|
+
- A Python consumer, projection, or analysis process reading a log that C or
|
|
381
|
+
Rust producers write — the replay/notebook/dashboard side of an
|
|
382
|
+
event-sourced pipeline.
|
|
383
|
+
- Cross-process compatibility with existing C, Rust, or Algoteq Java
|
|
384
|
+
deployments sharing a queue.
|
|
385
|
+
- Replay, export, and offline forensics over recorded queues.
|
|
386
|
+
- Prototyping and testing with the real wire format.
|
|
387
|
+
|
|
388
|
+
For low-latency *production* on the hot path, produce from the C or Rust
|
|
389
|
+
bindings; let Python read.
|
|
390
|
+
|
|
391
|
+
## Compatibility
|
|
392
|
+
|
|
393
|
+
The wire format is identical to the C library and the Rust bindings, and
|
|
394
|
+
compatible with Algoteq's Java implementation (tools4j-derived; **not**
|
|
395
|
+
compatible with upstream tools4j — the formats have diverged). Batch queues
|
|
396
|
+
are a C-ecosystem feature and are not read by Java.
|
|
397
|
+
|
|
398
|
+
## Stability
|
|
399
|
+
|
|
400
|
+
The wire format and feature set are **frozen**: queue files written today
|
|
401
|
+
remain readable by every future version, and the API grows only to complete
|
|
402
|
+
parity with the Algoteq Java reference. The Python bindings expose the
|
|
403
|
+
producer/consumer and replay surface; some low-level batch construction
|
|
404
|
+
primitives remain C/Rust only (see `doc/feature-matrix.md`).
|
|
405
|
+
|
|
406
|
+
## Testing
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
python3 -m unittest discover tests
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Acknowledgements
|
|
413
|
+
|
|
414
|
+
`mmapq-py` wraps Algoteq's mmapq, originally derived from the
|
|
415
|
+
[tools4j/mmap](https://github.com/tools4j/mmap) Java library. The
|
|
416
|
+
event-sourcing architecture follows
|
|
417
|
+
[tools4j/event-sourcing](https://github.com/tools4j/event-sourcing) and
|
|
418
|
+
[elara](https://github.com/tools4j/elara).
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
BSD 3-Clause — see [LICENSE](LICENSE).
|
|
423
|
+
|
|
424
|
+
## Platform
|
|
425
|
+
|
|
426
|
+
Linux only (x86-64; ARM64 untested). Requires naturally aligned lock-free
|
|
427
|
+
atomics over `MAP_SHARED` mmap.
|