py-sharedmemory 4.1.0__tar.gz → 4.1.1__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.
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/CHANGELOG.md +12 -0
- {py_sharedmemory-4.1.0/py_sharedmemory.egg-info → py_sharedmemory-4.1.1}/PKG-INFO +12 -11
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/README.md +11 -10
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory/__init__.py +1 -1
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory/convert.py +10 -1
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory/factory.py +1 -2
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory/sender.py +14 -1
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1/py_sharedmemory.egg-info}/PKG-INFO +12 -11
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/test_convert.py +9 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/test_processes.py +29 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/test_queue.py +37 -1
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/LICENSE +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/MANIFEST.in +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/benchmarks/__init__.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/benchmarks/benchmark.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/benchmarks/plot.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/benchmarks/results/windows-numpy.json +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/benchmarks/results/windows-numpy.png +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/examples/basic.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/examples/numpy_batches.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/examples/worker_pool.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory/py.typed +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory/receiver.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory.egg-info/SOURCES.txt +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory.egg-info/dependency_links.txt +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory.egg-info/requires.txt +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory.egg-info/top_level.txt +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/pyproject.toml +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/setup.cfg +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/__init__.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/conftest.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/helpers.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/test_benchmark.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/test_objects.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/test_package.py +0 -0
- {py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/tests/test_threads.py +0 -0
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.1.1
|
|
4
|
+
|
|
5
|
+
- Unread messages no longer hold file descriptors on Linux and macOS: the sender unmaps
|
|
6
|
+
each block right after writing it. Unbounded pairs previously failed with
|
|
7
|
+
`Too many open files` after about 120 unread messages under macOS's default limit.
|
|
8
|
+
- Cap outstanding messages at the metadata queue's limit (32,767 on macOS), so a blocking
|
|
9
|
+
`put` on an unbounded pair waits instead of raising `queue.Full`.
|
|
10
|
+
- Explain a freed block in the receiver's `FileNotFoundError`, which happens when the
|
|
11
|
+
sender closes before a message is read.
|
|
12
|
+
|
|
3
13
|
## 4.1.0
|
|
4
14
|
|
|
5
15
|
- Warn with a `RuntimeWarning` when leaving a `with sender:` block or exiting the
|
|
6
16
|
producing process discards unacknowledged messages, so a forgotten
|
|
7
17
|
`wait_for_all_ack()` no longer loses data silently. Explicit `close()` calls and
|
|
8
18
|
exits caused by exceptions stay silent, and closing still never blocks.
|
|
19
|
+
- Collect garbage between tests to avoid Python 3.12 resource-tracker reentrancy errors,
|
|
20
|
+
and add a stress test that checks every byte across consumer processes.
|
|
9
21
|
|
|
10
22
|
## 4.0.0
|
|
11
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-sharedmemory
|
|
3
|
-
Version: 4.1.
|
|
3
|
+
Version: 4.1.1
|
|
4
4
|
Summary: Fast multiprocessing.Queue alternative that moves large Python objects and NumPy arrays through shared memory
|
|
5
5
|
Author: Tobias Würth
|
|
6
6
|
License-Expression: MIT
|
|
@@ -190,9 +190,7 @@ consumer = ctx.Process(target=consume, args=(receiver,))
|
|
|
190
190
|
|
|
191
191
|
`capacity` counts messages that were sent but not yet received and freed. When it is
|
|
192
192
|
reached, `put()` blocks, so a fast producer cannot exhaust memory. Shared-memory use is
|
|
193
|
-
at most about `capacity × message size`. `capacity=0` or `None` means unbounded
|
|
194
|
-
a limit, because unread messages also hold open files on Linux and macOS (see the
|
|
195
|
-
platform notes below).
|
|
193
|
+
at most about `capacity × message size`. `capacity=0` or `None` means unbounded.
|
|
196
194
|
|
|
197
195
|
### Timeouts and non-blocking calls
|
|
198
196
|
|
|
@@ -296,13 +294,9 @@ with `OSError: [Errno 28] No space left on device` or crash with `SIGBUS`.
|
|
|
296
294
|
**Linux:** outside containers, `/dev/shm` usually defaults to half of physical RAM.
|
|
297
295
|
|
|
298
296
|
**macOS:** fully supported and tested on Apple Silicon. The default start method is
|
|
299
|
-
`spawn`, so guard the entry point with `if __name__ == "__main__":`.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
open in the producer until it is received. macOS allows 256 per process by default and
|
|
303
|
-
Linux usually 1,024, so an unbounded pair fails with `OSError: [Errno 24] Too many open
|
|
304
|
-
files` after roughly 120 or 500 unread messages. Set a `capacity` well below that, or
|
|
305
|
-
raise the limit with `ulimit -n`. The sender stays usable after this error.
|
|
297
|
+
`spawn`, so guard the entry point with `if __name__ == "__main__":`. An unbounded pair
|
|
298
|
+
holds at most 32,767 unread messages, the limit of macOS semaphores; beyond that `put`
|
|
299
|
+
waits or raises `queue.Full` like a full bounded pair.
|
|
306
300
|
|
|
307
301
|
**Windows:** shared memory is backed by the page file, and each block disappears once
|
|
308
302
|
the last handle closes, so nothing can leak after a crash.
|
|
@@ -310,6 +304,13 @@ the last handle closes, so nothing can leak after a crash.
|
|
|
310
304
|
**"resource_tracker: There appear to be N leaked shared_memory objects":** fixed in
|
|
311
305
|
3.0.1. Upgrade if you see this warning with the `fork` start method.
|
|
312
306
|
|
|
307
|
+
**`FileNotFoundError: Shared-memory block was freed before it was received`:** the
|
|
308
|
+
producer closed or exited before this message was read. Call `wait_for_all_ack()` before
|
|
309
|
+
closing the sender; the sender also warns when this happens.
|
|
310
|
+
|
|
311
|
+
**`OSError: [Errno 24] Too many open files` with many unread messages:** fixed in 4.1.1,
|
|
312
|
+
where unread messages no longer hold file descriptors.
|
|
313
|
+
|
|
313
314
|
**`RuntimeError: A shared-memory pair supports one producing process`:** a second
|
|
314
315
|
process called `put`. Create a separate pair for each producer process.
|
|
315
316
|
|
|
@@ -140,9 +140,7 @@ consumer = ctx.Process(target=consume, args=(receiver,))
|
|
|
140
140
|
|
|
141
141
|
`capacity` counts messages that were sent but not yet received and freed. When it is
|
|
142
142
|
reached, `put()` blocks, so a fast producer cannot exhaust memory. Shared-memory use is
|
|
143
|
-
at most about `capacity × message size`. `capacity=0` or `None` means unbounded
|
|
144
|
-
a limit, because unread messages also hold open files on Linux and macOS (see the
|
|
145
|
-
platform notes below).
|
|
143
|
+
at most about `capacity × message size`. `capacity=0` or `None` means unbounded.
|
|
146
144
|
|
|
147
145
|
### Timeouts and non-blocking calls
|
|
148
146
|
|
|
@@ -246,13 +244,9 @@ with `OSError: [Errno 28] No space left on device` or crash with `SIGBUS`.
|
|
|
246
244
|
**Linux:** outside containers, `/dev/shm` usually defaults to half of physical RAM.
|
|
247
245
|
|
|
248
246
|
**macOS:** fully supported and tested on Apple Silicon. The default start method is
|
|
249
|
-
`spawn`, so guard the entry point with `if __name__ == "__main__":`.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
open in the producer until it is received. macOS allows 256 per process by default and
|
|
253
|
-
Linux usually 1,024, so an unbounded pair fails with `OSError: [Errno 24] Too many open
|
|
254
|
-
files` after roughly 120 or 500 unread messages. Set a `capacity` well below that, or
|
|
255
|
-
raise the limit with `ulimit -n`. The sender stays usable after this error.
|
|
247
|
+
`spawn`, so guard the entry point with `if __name__ == "__main__":`. An unbounded pair
|
|
248
|
+
holds at most 32,767 unread messages, the limit of macOS semaphores; beyond that `put`
|
|
249
|
+
waits or raises `queue.Full` like a full bounded pair.
|
|
256
250
|
|
|
257
251
|
**Windows:** shared memory is backed by the page file, and each block disappears once
|
|
258
252
|
the last handle closes, so nothing can leak after a crash.
|
|
@@ -260,6 +254,13 @@ the last handle closes, so nothing can leak after a crash.
|
|
|
260
254
|
**"resource_tracker: There appear to be N leaked shared_memory objects":** fixed in
|
|
261
255
|
3.0.1. Upgrade if you see this warning with the `fork` start method.
|
|
262
256
|
|
|
257
|
+
**`FileNotFoundError: Shared-memory block was freed before it was received`:** the
|
|
258
|
+
producer closed or exited before this message was read. Call `wait_for_all_ack()` before
|
|
259
|
+
closing the sender; the sender also warns when this happens.
|
|
260
|
+
|
|
261
|
+
**`OSError: [Errno 24] Too many open files` with many unread messages:** fixed in 4.1.1,
|
|
262
|
+
where unread messages no longer hold file descriptors.
|
|
263
|
+
|
|
263
264
|
**`RuntimeError: A shared-memory pair supports one producing process`:** a second
|
|
264
265
|
process called `put`. Create a separate pair for each producer process.
|
|
265
266
|
|
|
@@ -25,5 +25,5 @@ from .factory import create_shared_memory_pair
|
|
|
25
25
|
from .receiver import SharedMemoryReceiver
|
|
26
26
|
from .sender import SharedMemorySender
|
|
27
27
|
|
|
28
|
-
__version__ = "4.1.
|
|
28
|
+
__version__ = "4.1.1"
|
|
29
29
|
__all__ = ["SharedMemoryReceiver", "SharedMemorySender", "__version__", "create_shared_memory_pair"]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Serialize protocol-5 pickle data through a single shared-memory block."""
|
|
2
2
|
|
|
3
|
+
import errno
|
|
3
4
|
import pickle
|
|
4
5
|
import sys
|
|
5
6
|
from contextlib import suppress
|
|
@@ -60,7 +61,15 @@ def serialize(data: Any) -> tuple[SharedMemory, SharedMemoryMessage]:
|
|
|
60
61
|
def deserialize(message: SharedMemoryMessage) -> Any:
|
|
61
62
|
"""Unpickle with owned local buffers and always close the shared mapping."""
|
|
62
63
|
_validate_message(message)
|
|
63
|
-
|
|
64
|
+
try:
|
|
65
|
+
allocation = SharedMemory(name=message.name, **_ATTACH_OPTIONS)
|
|
66
|
+
except FileNotFoundError as error:
|
|
67
|
+
raise FileNotFoundError(
|
|
68
|
+
errno.ENOENT,
|
|
69
|
+
"Shared-memory block was freed before it was received; the sender probably "
|
|
70
|
+
"closed without calling wait_for_all_ack()",
|
|
71
|
+
message.name,
|
|
72
|
+
) from error
|
|
64
73
|
try:
|
|
65
74
|
if message.size > allocation.size:
|
|
66
75
|
raise ValueError("Shared-memory block is smaller than its metadata")
|
|
@@ -21,8 +21,7 @@ def create_shared_memory_pair(
|
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
23
|
capacity: Maximum messages sent but not yet received and freed; `put` blocks
|
|
24
|
-
when it is reached. `0` or `None` means unbounded
|
|
25
|
-
every unread message also holds two open file descriptors.
|
|
24
|
+
when it is reached. `0` or `None` means unbounded.
|
|
26
25
|
ctx: Multiprocessing context for the underlying queues, such as
|
|
27
26
|
`multiprocessing.get_context("spawn")`. Defaults to the default context;
|
|
28
27
|
use the same context to start the worker processes.
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import sys
|
|
6
7
|
import threading
|
|
7
8
|
import time
|
|
8
9
|
import warnings
|
|
9
10
|
from multiprocessing.queues import Queue
|
|
10
11
|
from multiprocessing.shared_memory import SharedMemory
|
|
12
|
+
from multiprocessing.synchronize import SEM_VALUE_MAX # type: ignore[attr-defined] # not in typeshed
|
|
11
13
|
from multiprocessing.util import Finalize
|
|
12
14
|
from queue import Empty, Full
|
|
13
15
|
from types import TracebackType
|
|
@@ -18,6 +20,14 @@ from .convert import SharedMemoryMessage, release_shared_memory, serialize
|
|
|
18
20
|
if TYPE_CHECKING:
|
|
19
21
|
from multiprocessing.sharedctypes import Synchronized
|
|
20
22
|
|
|
23
|
+
# A POSIX block lives on by name until it is unlinked, so the sender can drop its mapping
|
|
24
|
+
# and file descriptors right after writing. Windows frees a block with its last handle.
|
|
25
|
+
_UNMAP_AFTER_WRITE = sys.platform != "win32"
|
|
26
|
+
|
|
27
|
+
# The metadata queue holds at most SEM_VALUE_MAX unread items (32,767 on macOS); beyond
|
|
28
|
+
# that its put would raise queue.Full even for a blocking put on an unbounded sender.
|
|
29
|
+
_MAX_OUTSTANDING = SEM_VALUE_MAX
|
|
30
|
+
|
|
21
31
|
|
|
22
32
|
def _validate_capacity(capacity: int | None) -> None:
|
|
23
33
|
if capacity is None:
|
|
@@ -215,7 +225,8 @@ class SharedMemorySender:
|
|
|
215
225
|
with self._condition:
|
|
216
226
|
self._ensure_open()
|
|
217
227
|
self._start_ack_thread()
|
|
218
|
-
|
|
228
|
+
limit = min(self.capacity or _MAX_OUTSTANDING, _MAX_OUTSTANDING)
|
|
229
|
+
while self._pending_count >= limit:
|
|
219
230
|
if not block:
|
|
220
231
|
raise Full
|
|
221
232
|
remaining = None if deadline is None else deadline - time.monotonic()
|
|
@@ -235,6 +246,8 @@ class SharedMemorySender:
|
|
|
235
246
|
raise
|
|
236
247
|
try:
|
|
237
248
|
self._ensure_open()
|
|
249
|
+
if _UNMAP_AFTER_WRITE:
|
|
250
|
+
allocation.close()
|
|
238
251
|
self._data_queue.put_nowait(message)
|
|
239
252
|
except BaseException:
|
|
240
253
|
self._release_message(allocation.name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-sharedmemory
|
|
3
|
-
Version: 4.1.
|
|
3
|
+
Version: 4.1.1
|
|
4
4
|
Summary: Fast multiprocessing.Queue alternative that moves large Python objects and NumPy arrays through shared memory
|
|
5
5
|
Author: Tobias Würth
|
|
6
6
|
License-Expression: MIT
|
|
@@ -190,9 +190,7 @@ consumer = ctx.Process(target=consume, args=(receiver,))
|
|
|
190
190
|
|
|
191
191
|
`capacity` counts messages that were sent but not yet received and freed. When it is
|
|
192
192
|
reached, `put()` blocks, so a fast producer cannot exhaust memory. Shared-memory use is
|
|
193
|
-
at most about `capacity × message size`. `capacity=0` or `None` means unbounded
|
|
194
|
-
a limit, because unread messages also hold open files on Linux and macOS (see the
|
|
195
|
-
platform notes below).
|
|
193
|
+
at most about `capacity × message size`. `capacity=0` or `None` means unbounded.
|
|
196
194
|
|
|
197
195
|
### Timeouts and non-blocking calls
|
|
198
196
|
|
|
@@ -296,13 +294,9 @@ with `OSError: [Errno 28] No space left on device` or crash with `SIGBUS`.
|
|
|
296
294
|
**Linux:** outside containers, `/dev/shm` usually defaults to half of physical RAM.
|
|
297
295
|
|
|
298
296
|
**macOS:** fully supported and tested on Apple Silicon. The default start method is
|
|
299
|
-
`spawn`, so guard the entry point with `if __name__ == "__main__":`.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
open in the producer until it is received. macOS allows 256 per process by default and
|
|
303
|
-
Linux usually 1,024, so an unbounded pair fails with `OSError: [Errno 24] Too many open
|
|
304
|
-
files` after roughly 120 or 500 unread messages. Set a `capacity` well below that, or
|
|
305
|
-
raise the limit with `ulimit -n`. The sender stays usable after this error.
|
|
297
|
+
`spawn`, so guard the entry point with `if __name__ == "__main__":`. An unbounded pair
|
|
298
|
+
holds at most 32,767 unread messages, the limit of macOS semaphores; beyond that `put`
|
|
299
|
+
waits or raises `queue.Full` like a full bounded pair.
|
|
306
300
|
|
|
307
301
|
**Windows:** shared memory is backed by the page file, and each block disappears once
|
|
308
302
|
the last handle closes, so nothing can leak after a crash.
|
|
@@ -310,6 +304,13 @@ the last handle closes, so nothing can leak after a crash.
|
|
|
310
304
|
**"resource_tracker: There appear to be N leaked shared_memory objects":** fixed in
|
|
311
305
|
3.0.1. Upgrade if you see this warning with the `fork` start method.
|
|
312
306
|
|
|
307
|
+
**`FileNotFoundError: Shared-memory block was freed before it was received`:** the
|
|
308
|
+
producer closed or exited before this message was read. Call `wait_for_all_ack()` before
|
|
309
|
+
closing the sender; the sender also warns when this happens.
|
|
310
|
+
|
|
311
|
+
**`OSError: [Errno 24] Too many open files` with many unread messages:** fixed in 4.1.1,
|
|
312
|
+
where unread messages no longer hold file descriptors.
|
|
313
|
+
|
|
313
314
|
**`RuntimeError: A shared-memory pair supports one producing process`:** a second
|
|
314
315
|
process called `put`. Create a separate pair for each producer process.
|
|
315
316
|
|
|
@@ -242,6 +242,15 @@ def test_cleanup_tolerates_already_unlinked_blocks():
|
|
|
242
242
|
assert_unlinked(info.name)
|
|
243
243
|
|
|
244
244
|
|
|
245
|
+
def test_freed_block_reports_a_clear_error():
|
|
246
|
+
handle, info = serialize(42)
|
|
247
|
+
release_shared_memory(handle)
|
|
248
|
+
with pytest.raises(FileNotFoundError, match="freed before it was received") as caught:
|
|
249
|
+
deserialize(info)
|
|
250
|
+
assert caught.value.filename == info.name
|
|
251
|
+
assert isinstance(caught.value.__cause__, FileNotFoundError)
|
|
252
|
+
|
|
253
|
+
|
|
245
254
|
def test_cleanup_ignores_names_that_are_already_gone():
|
|
246
255
|
# Windows never raises here because unlink is a no-op, so simulate POSIX.
|
|
247
256
|
handle = Mock()
|
|
@@ -193,6 +193,35 @@ raise SystemExit(process.exitcode)
|
|
|
193
193
|
"""
|
|
194
194
|
|
|
195
195
|
|
|
196
|
+
LOW_FILE_LIMIT = """
|
|
197
|
+
import resource
|
|
198
|
+
|
|
199
|
+
from py_sharedmemory import create_shared_memory_pair
|
|
200
|
+
|
|
201
|
+
# macOS defaults to 256 open files; unread messages must not hold descriptors.
|
|
202
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (256, 256))
|
|
203
|
+
sender, receiver = create_shared_memory_pair(0)
|
|
204
|
+
for index in range(1_000):
|
|
205
|
+
sender.put(index)
|
|
206
|
+
assert [receiver.get(timeout=10) for _ in range(1_000)] == list(range(1_000))
|
|
207
|
+
assert sender.wait_for_all_ack(timeout=10)
|
|
208
|
+
sender.close()
|
|
209
|
+
receiver.close()
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX file-descriptor limits")
|
|
214
|
+
def test_unread_messages_hold_no_file_descriptors():
|
|
215
|
+
result = subprocess.run(
|
|
216
|
+
[sys.executable, "-c", LOW_FILE_LIMIT],
|
|
217
|
+
cwd=Path(__file__).resolve().parents[1],
|
|
218
|
+
capture_output=True,
|
|
219
|
+
text=True,
|
|
220
|
+
timeout=60,
|
|
221
|
+
)
|
|
222
|
+
assert result.returncode == 0, result.stderr
|
|
223
|
+
|
|
224
|
+
|
|
196
225
|
UNREAD_AT_EXIT = """
|
|
197
226
|
from py_sharedmemory import create_shared_memory_pair
|
|
198
227
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import multiprocessing as mp
|
|
2
2
|
import os
|
|
3
3
|
import pickle
|
|
4
|
+
import sys
|
|
4
5
|
import threading
|
|
5
6
|
import time
|
|
6
7
|
import warnings
|
|
7
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
8
9
|
from contextlib import suppress
|
|
10
|
+
from multiprocessing.shared_memory import SharedMemory
|
|
9
11
|
from queue import Empty, Full
|
|
10
12
|
from unittest.mock import Mock
|
|
11
13
|
|
|
@@ -93,6 +95,38 @@ def test_full_nonblocking_and_immediate_timeouts(make_pair, kwargs):
|
|
|
93
95
|
assert receiver.get(timeout=3) == 3
|
|
94
96
|
|
|
95
97
|
|
|
98
|
+
@pytest.mark.parametrize("capacity", [0, None, 5])
|
|
99
|
+
def test_outstanding_messages_never_exceed_the_metadata_queue_limit(
|
|
100
|
+
make_pair, monkeypatch, capacity
|
|
101
|
+
):
|
|
102
|
+
# macOS limits a multiprocessing queue to 32,767 unread items; simulate a tiny limit.
|
|
103
|
+
monkeypatch.setattr(sender_module, "_MAX_OUTSTANDING", 2)
|
|
104
|
+
sender, receiver = make_pair(capacity)
|
|
105
|
+
sender.put(1)
|
|
106
|
+
sender.put(2)
|
|
107
|
+
with pytest.raises(Full):
|
|
108
|
+
sender.put_nowait(3)
|
|
109
|
+
with pytest.raises(Full):
|
|
110
|
+
sender.put(3, timeout=0.05)
|
|
111
|
+
assert receiver.get(timeout=3) == 1
|
|
112
|
+
sender.put(3, timeout=3)
|
|
113
|
+
assert [receiver.get(timeout=3) for _ in range(2)] == [2, 3]
|
|
114
|
+
assert sender.wait_for_all_ack(timeout=3)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.parametrize("unmap", [False, True])
|
|
118
|
+
def test_sender_unmaps_blocks_only_where_names_keep_them_alive(make_pair, monkeypatch, unmap):
|
|
119
|
+
monkeypatch.setattr(sender_module, "_UNMAP_AFTER_WRITE", unmap)
|
|
120
|
+
sender, receiver = make_pair()
|
|
121
|
+
sender.put(42)
|
|
122
|
+
allocation = next(iter(sender._blocks.values()))
|
|
123
|
+
assert (allocation.buf is None) is unmap
|
|
124
|
+
if not (unmap and sys.platform == "win32"):
|
|
125
|
+
# Windows frees an unmapped block at once, which is why it never unmaps.
|
|
126
|
+
assert receiver.get(timeout=3) == 42
|
|
127
|
+
assert sender.wait_for_all_ack(timeout=3)
|
|
128
|
+
|
|
129
|
+
|
|
96
130
|
def test_put_timeout_waits_for_capacity(make_pair):
|
|
97
131
|
sender, _ = make_pair()
|
|
98
132
|
sender.put(1)
|
|
@@ -204,7 +238,9 @@ def test_failed_decode_is_acknowledged_and_next_message_succeeds(make_pair):
|
|
|
204
238
|
sender, receiver = make_pair()
|
|
205
239
|
sender.put(42)
|
|
206
240
|
name = next(iter(sender._blocks))
|
|
207
|
-
|
|
241
|
+
corrupt = SharedMemory(name=name)
|
|
242
|
+
corrupt.buf[0] = ord("!")
|
|
243
|
+
corrupt.close()
|
|
208
244
|
with pytest.raises(pickle.UnpicklingError):
|
|
209
245
|
receiver.get(timeout=3)
|
|
210
246
|
assert sender.wait_for_all_ack(timeout=3)
|
|
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
|
{py_sharedmemory-4.1.0 → py_sharedmemory-4.1.1}/py_sharedmemory.egg-info/dependency_links.txt
RENAMED
|
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
|