async-kernel 0.22.0__tar.gz → 0.22.2__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.
- {async_kernel-0.22.0 → async_kernel-0.22.2}/CHANGELOG.md +34 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/PKG-INFO +1 -1
- {async_kernel-0.22.0 → async_kernel-0.22.2}/_version.py +2 -2
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/caller.py +5 -4
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/event_loop/zmq_poll.py +49 -31
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/interface/base.py +12 -18
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/interface/callable.py +11 -10
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/messaging/base.py +7 -3
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/messaging/zmq.py +17 -12
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_callable_interface.py +1 -3
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_eventloop_zmq_poll.py +5 -5
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/utils.py +8 -2
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.github/dependabot.yaml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.github/release.yml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.github/workflows/ci.yml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.github/workflows/enforce-label.yml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.github/workflows/new_release.yml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.github/workflows/pre-commit.yml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.github/workflows/publish-docs.yml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.github/workflows/publish-to-pypi.yml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.gitignore +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.pre-commit-config.yaml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.vscode/launch.json +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.vscode/settings.json +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/.vscode/spellright.dict +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/CONTRIBUTING.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/IPYTHON_LICENSE +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/LICENSE +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/README.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/cliff.toml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/about/changelog.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/about/contributing.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/about/index.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/about/license.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/index.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/javascripts/extra.js +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/notebooks/caller.ipynb +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/notebooks/concurrency.ipynb +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/notebooks/custom_kernel.ipynb +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/overrides/main.html +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/caller.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/comm.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/command.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/common.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/debugger.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/event_loop.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/index.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/interface.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/ipshell.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/kernel.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/kernelspec.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/messaging.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/pending.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/shell.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/typing.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/reference/utils.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/stylesheets/extra.css +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/thread_safety.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/usage/commands.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/docs/usage/index.md +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/hatch_build.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/mkdocs.yml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/pyproject.toml +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/__init__.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/__main__.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/comm.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/command.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/common.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/compat/attr_docs.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/compat/json.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/compiler.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/debugger.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/event_loop/__init__.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/event_loop/asyncio_guest.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/event_loop/qt_host.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/event_loop/run.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/event_loop/tk_host.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/interface/__init__.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/interface/ip_app.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/kernel.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/kernelspec.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/messaging/__init__.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/outstream.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/pending.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/py.typed +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/resources/logo-32x32.png +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/resources/logo-64x64.png +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/resources/logo-svg.svg +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/shell/__init__.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/shell/base.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/shell/ipshell.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/typing.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/src/async_kernel/utils.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/__init__.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/conftest.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/references.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_caller.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_comm.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_command.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_common.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_compat.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_connection.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_debugger.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_debugger_static.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_enter_kernel.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_event_loop.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_interface.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_ipapp.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_kernel_ipshell.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_kernelspec.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_message_spec.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_outstream.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_pending.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_subclass.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_typing.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_utils.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_zmq_connection.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/tests/test_zmq_subprocess.py +0 -0
- {async_kernel-0.22.0 → async_kernel-0.22.2}/uv.lock +0 -0
|
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.22.2] - 2026-08-21
|
|
9
|
+
|
|
10
|
+
### <!-- 1 --> 🚀 Features
|
|
11
|
+
|
|
12
|
+
- ZMQPoll._wake minor optimization. [#574](https://github.com/fleming79/async-kernel/pull/574)
|
|
13
|
+
|
|
14
|
+
- Replace BinarySemaphore with threading.Lock for faster performance in heavily used places. [#571](https://github.com/fleming79/async-kernel/pull/571)
|
|
15
|
+
|
|
16
|
+
### <!-- 6 --> 🌀 Miscellaneous
|
|
17
|
+
|
|
18
|
+
- Tweak Interface startup sequence. [#573](https://github.com/fleming79/async-kernel/pull/573)
|
|
19
|
+
|
|
20
|
+
- Alan Fleming Refactor ZMQConnection to send the welcome message directly. [#572](https://github.com/fleming79/async-kernel/pull/572)
|
|
21
|
+
|
|
22
|
+
## [0.22.1] - 2026-08-19
|
|
23
|
+
|
|
24
|
+
### <!-- 0 --> 🏗️ Breaking changes
|
|
25
|
+
|
|
26
|
+
- Update zmq_poll docstrings. [#567](https://github.com/fleming79/async-kernel/pull/567)
|
|
27
|
+
|
|
28
|
+
### <!-- 5 --> 📝 Documentation
|
|
29
|
+
|
|
30
|
+
- ZMQPoll typing and doc fixes. [#568](https://github.com/fleming79/async-kernel/pull/568)
|
|
31
|
+
|
|
32
|
+
### <!-- 6 --> 🌀 Miscellaneous
|
|
33
|
+
|
|
34
|
+
- Prepare for release v0.22.1 [#570](https://github.com/fleming79/async-kernel/pull/570)
|
|
35
|
+
|
|
36
|
+
- Refactor create_interface_messge_callback_handler to only use one connection [#569](https://github.com/fleming79/async-kernel/pull/569)
|
|
37
|
+
|
|
8
38
|
## [0.22.0] - 2026-08-17
|
|
9
39
|
|
|
10
40
|
### <!-- 0 --> 🏗️ Breaking changes
|
|
@@ -37,6 +67,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
37
67
|
|
|
38
68
|
### <!-- 6 --> 🌀 Miscellaneous
|
|
39
69
|
|
|
70
|
+
- Prepare for release v0.22.0 [#566](https://github.com/fleming79/async-kernel/pull/566)
|
|
71
|
+
|
|
40
72
|
- Bump uv from 0.11.32 to 0.12.4 [#562](https://github.com/fleming79/async-kernel/pull/562)
|
|
41
73
|
|
|
42
74
|
- Bump astral-sh/setup-uv from 9.0.0 to 10.0.1 in the actions group [#559](https://github.com/fleming79/async-kernel/pull/559)
|
|
@@ -1529,6 +1561,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1529
1561
|
|
|
1530
1562
|
- Bump the actions group across 1 directory with 2 updates [#3](https://github.com/fleming79/async-kernel/pull/3)
|
|
1531
1563
|
|
|
1564
|
+
[0.22.2]: https://github.com/fleming79/async-kernel/compare/v0.22.1..v0.22.2
|
|
1565
|
+
[0.22.1]: https://github.com/fleming79/async-kernel/compare/v0.22.0..v0.22.1
|
|
1532
1566
|
[0.22.0]: https://github.com/fleming79/async-kernel/compare/v0.21.0..v0.22.0
|
|
1533
1567
|
[0.21.0]: https://github.com/fleming79/async-kernel/compare/v0.20.2..v0.21.0
|
|
1534
1568
|
[0.20.2]: https://github.com/fleming79/async-kernel/compare/v0.20.1..v0.20.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: async-kernel
|
|
3
|
-
Version: 0.22.
|
|
3
|
+
Version: 0.22.2
|
|
4
4
|
Summary: A concurrent python kernel for Jupyter supporting AnyIO, AsyncIO and Trio.
|
|
5
5
|
Project-URL: Homepage, https://fleming79.github.io/async-kernel
|
|
6
6
|
Project-URL: Documentation, https://fleming79.github.io/async-kernel
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.22.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 22,
|
|
21
|
+
__version__ = version = '0.22.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 22, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Concatenate, Generic, Literal,
|
|
|
19
19
|
from weakref import ReferenceType
|
|
20
20
|
|
|
21
21
|
import anyio
|
|
22
|
-
from aiologic import
|
|
22
|
+
from aiologic import CountdownEvent
|
|
23
23
|
from aiologic.lowlevel import async_checkpoint, create_async_event, current_async_library
|
|
24
24
|
from aiologic.meta import await_for, iscoroutinelike
|
|
25
25
|
from typing_extensions import override
|
|
@@ -191,7 +191,7 @@ class Caller:
|
|
|
191
191
|
|
|
192
192
|
_caller_token = contextvars.ContextVar("caller_tokens", default=CALLER_MAIN_THREAD_ID)
|
|
193
193
|
_instances: ClassVar[weakref.WeakValueDictionary[int, Self]] = weakref.WeakValueDictionary()
|
|
194
|
-
_lock: ClassVar =
|
|
194
|
+
_lock: ClassVar = threading.Lock()
|
|
195
195
|
|
|
196
196
|
_thread: threading.Thread
|
|
197
197
|
_caller_id: int
|
|
@@ -219,7 +219,7 @@ class Caller:
|
|
|
219
219
|
_worker_pool: Fixed[Self, deque[Self]] = Fixed(deque)
|
|
220
220
|
|
|
221
221
|
# Private
|
|
222
|
-
_inst_lock:
|
|
222
|
+
_inst_lock: threading.Lock
|
|
223
223
|
_children: set[Self]
|
|
224
224
|
_tasks: set[asyncio.Task]
|
|
225
225
|
_queue_map: dict[int, Pending]
|
|
@@ -394,7 +394,7 @@ class Caller:
|
|
|
394
394
|
thread = threading.current_thread()
|
|
395
395
|
|
|
396
396
|
# Add private objects
|
|
397
|
-
inst._inst_lock =
|
|
397
|
+
inst._inst_lock = threading.Lock()
|
|
398
398
|
inst._children = set()
|
|
399
399
|
inst._tasks = set()
|
|
400
400
|
inst._queue_map = {}
|
|
@@ -949,6 +949,7 @@ class Caller:
|
|
|
949
949
|
pass
|
|
950
950
|
except IndexError:
|
|
951
951
|
caller = self.get()
|
|
952
|
+
caller._name = f"async-kernel worker of {self.id}"
|
|
952
953
|
pen = caller.call_soon(func, *args, **kwargs)
|
|
953
954
|
pen.add_done_callback(_to_thread_on_done)
|
|
954
955
|
return pen
|
|
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, Any, Literal, Self
|
|
|
14
14
|
import zmq
|
|
15
15
|
from aiologic import BinarySemaphore, BusyResourceError
|
|
16
16
|
from typing_extensions import override
|
|
17
|
-
from zmq.backend import zmq_poll
|
|
17
|
+
from zmq.backend import zmq_poll as _zmq_poll
|
|
18
18
|
|
|
19
19
|
from async_kernel import Caller, utils
|
|
20
20
|
from async_kernel.common import Fixed
|
|
@@ -41,7 +41,7 @@ class ZMQPollSocket(zmq.sugar.Socket[bytes]):
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
_zmq_poll_ref: weakref.ref[ZMQPoll]
|
|
44
|
-
lock:
|
|
44
|
+
lock: threading.Lock
|
|
45
45
|
|
|
46
46
|
if TYPE_CHECKING:
|
|
47
47
|
# magic attributes cannot be be stored in `__annotations__`.
|
|
@@ -68,7 +68,7 @@ class ZMQPollSocket(zmq.sugar.Socket[bytes]):
|
|
|
68
68
|
zmq_poll: ZMQPoll,
|
|
69
69
|
copy_threshold: int | None = None,
|
|
70
70
|
) -> None:
|
|
71
|
-
self.lock =
|
|
71
|
+
self.lock = threading.Lock()
|
|
72
72
|
self._zmq_poll_ref = weakref.ref(zmq_poll)
|
|
73
73
|
|
|
74
74
|
if zmq_poll.stopped.done():
|
|
@@ -99,10 +99,13 @@ class ZMQPollSocket(zmq.sugar.Socket[bytes]):
|
|
|
99
99
|
track: bool = False,
|
|
100
100
|
**kwargs,
|
|
101
101
|
) -> MessageTracker | None:
|
|
102
|
-
|
|
102
|
+
self.lock.acquire()
|
|
103
|
+
try:
|
|
103
104
|
if self.closed:
|
|
104
105
|
return None
|
|
105
106
|
return super().send_multipart(msg_parts, flags, copy, track)
|
|
107
|
+
finally:
|
|
108
|
+
self.lock.release()
|
|
106
109
|
|
|
107
110
|
@override
|
|
108
111
|
def close(self, linger=None) -> None:
|
|
@@ -144,19 +147,30 @@ class ZMQPollSocket(zmq.sugar.Socket[bytes]):
|
|
|
144
147
|
self.zmq_poll.execute(super().unsubscribe, topic)
|
|
145
148
|
|
|
146
149
|
|
|
147
|
-
T_key = tuple[
|
|
150
|
+
T_key = tuple[ZMQPollSocket, int]
|
|
148
151
|
|
|
149
152
|
|
|
150
153
|
class ZMQPoll:
|
|
151
154
|
"""A [zmq_poll](https://libzmq.readthedocs.io/en/latest/zmq_poll.html) based event loop.
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
the
|
|
156
|
+
When a `(socket, flags) -> handler` mapping context manager is used [ZMQPoll.event_handler][]
|
|
157
|
+
`handler` is called with the socket and event when the event occurs on the socket. The
|
|
158
|
+
callback occurs in the zmq poll thread. [async_kernel.caller.Caller][] is recommended to
|
|
159
|
+
to scheduling code execution in different threads.
|
|
160
|
+
|
|
161
|
+
Only [ZMQPollSocket][] sockets created using the [ZMQPoll.socket][] factory function are
|
|
162
|
+
allowed.
|
|
163
|
+
|
|
164
|
+
The methods [ZMQPoll.execute][] and the async version [ZMQPoll.aexecute][] are provided
|
|
165
|
+
to executed code in the zmq_poll thread, which is useful when creating and configuring
|
|
166
|
+
sockets to reduce thread switching.
|
|
156
167
|
"""
|
|
157
168
|
|
|
158
169
|
stopped: Fixed[Self, ProtectedPending] = Fixed(ProtectedPending)
|
|
170
|
+
"""Set when the poll thread event has stopped."""
|
|
171
|
+
|
|
159
172
|
sockets: Fixed[Self, set[ZMQPollSocket]] = Fixed(set)
|
|
173
|
+
"""The sockets currently registered with this instance."""
|
|
160
174
|
|
|
161
175
|
def __init__(self) -> None:
|
|
162
176
|
|
|
@@ -178,7 +192,6 @@ class ZMQPoll:
|
|
|
178
192
|
self._handlers: dict[T_key, Callable[[ZMQPollSocket, int], Any]] = {}
|
|
179
193
|
self._count: dict[T_key, tuple[int, Callable[[], Any]] | None] = {}
|
|
180
194
|
self._execute: deque[Pending] = deque[Pending[Any]]()
|
|
181
|
-
self._not_started = False
|
|
182
195
|
self.log = logging.LoggerAdapter(logging.getLogger())
|
|
183
196
|
self._cancellers = deque()
|
|
184
197
|
self._ctx_count = 0
|
|
@@ -235,7 +248,7 @@ class ZMQPoll:
|
|
|
235
248
|
sock.recv()
|
|
236
249
|
|
|
237
250
|
def do_execute() -> None:
|
|
238
|
-
"""Execute pending items added by the `execute` and `
|
|
251
|
+
"""Execute pending items added by the `execute` and `aexecute` methods."""
|
|
239
252
|
while execute:
|
|
240
253
|
md = (pen := execute.popleft()).metadata
|
|
241
254
|
try:
|
|
@@ -244,15 +257,16 @@ class ZMQPoll:
|
|
|
244
257
|
pen.set_exception(e)
|
|
245
258
|
del pen
|
|
246
259
|
|
|
247
|
-
send = context.socket(zmq.SocketType.PAIR)
|
|
248
|
-
wake = context.socket(zmq.SocketType.PAIR)
|
|
260
|
+
send: ZMQPollSocket = context.socket(zmq.SocketType.PAIR) # pyright: ignore[reportAssignmentType]
|
|
261
|
+
wake: ZMQPollSocket = context.socket(zmq.SocketType.PAIR) # pyright: ignore[reportAssignmentType]
|
|
249
262
|
addr = "inproc://async_kernel_zmq_poller_wake"
|
|
250
263
|
sockets = None
|
|
251
264
|
handlers[(wake, zmq.POLLIN)] = on_wake
|
|
252
265
|
|
|
253
266
|
with context, wake, send, wake.bind(addr), send.connect(addr):
|
|
267
|
+
k: T_key
|
|
254
268
|
c: tuple[int, Callable] | None
|
|
255
|
-
started.set_result(send)
|
|
269
|
+
started.set_result(send)
|
|
256
270
|
# The main loop polls the handler keys for events in a loop.
|
|
257
271
|
# It will block until an event occurs.
|
|
258
272
|
try:
|
|
@@ -263,9 +277,9 @@ class ZMQPoll:
|
|
|
263
277
|
do_execute()
|
|
264
278
|
continue
|
|
265
279
|
try:
|
|
266
|
-
for k in
|
|
280
|
+
for k in _zmq_poll(sockets, timeout=-1): # pyright: ignore[reportAssignmentType]
|
|
267
281
|
try:
|
|
268
|
-
handlers[k](*k)
|
|
282
|
+
handlers[k](*k)
|
|
269
283
|
except KeyError:
|
|
270
284
|
sockets = None
|
|
271
285
|
except SystemExit:
|
|
@@ -309,14 +323,16 @@ class ZMQPoll:
|
|
|
309
323
|
send = started.wait_sync()
|
|
310
324
|
|
|
311
325
|
def _wake(sock=send, lock=send.lock) -> None:
|
|
312
|
-
|
|
313
|
-
|
|
326
|
+
lock.acquire()
|
|
327
|
+
sock.send(b"")
|
|
328
|
+
lock.release()
|
|
314
329
|
|
|
315
330
|
self._wake = _wake
|
|
316
331
|
self.stopped.add_done_callback(lambda _: (self := ref()) and self._on_stopped())
|
|
317
332
|
self.log.debug("ZMQPoll event loop started")
|
|
318
333
|
|
|
319
334
|
def validate_socket(self, sock: ZMQPollSocket | Any) -> ZMQPollSocket:
|
|
335
|
+
"""Check `sock` is correctly registered."""
|
|
320
336
|
if sock not in self.sockets:
|
|
321
337
|
msg = f"Invalid socket detected! {sock=}"
|
|
322
338
|
raise ValueError(msg)
|
|
@@ -334,7 +350,7 @@ class ZMQPoll:
|
|
|
334
350
|
return self.validate_socket(self.execute(self._zmq_context.socket, socket_type))
|
|
335
351
|
|
|
336
352
|
def execute(self, func: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
337
|
-
"""Execute `func` in the thread waiting for the result synchronously."""
|
|
353
|
+
"""Execute `func` in the 'zmq_poll' thread waiting for the result synchronously."""
|
|
338
354
|
if hasattr(self, "thread"):
|
|
339
355
|
if threading.current_thread() is self.thread:
|
|
340
356
|
return func(*args, **kwargs)
|
|
@@ -346,17 +362,17 @@ class ZMQPoll:
|
|
|
346
362
|
finally:
|
|
347
363
|
pen.metadata.clear()
|
|
348
364
|
del pen
|
|
349
|
-
msg = f"Unable to execute {func=} in {self}. Execution is only
|
|
365
|
+
msg = f"Unable to execute {func=} in {self}. Execution is only supported while in context."
|
|
350
366
|
raise RuntimeError(msg)
|
|
351
367
|
|
|
352
|
-
async def
|
|
353
|
-
"""Execute `func` in the thread waiting for the result asynchronously."""
|
|
368
|
+
async def aexecute(self, func: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
369
|
+
"""Execute `func` in the 'zmq_poll' thread waiting for the result asynchronously."""
|
|
354
370
|
if hasattr(self, "thread"):
|
|
355
371
|
self._execute.append(pen := Pending[T](func=func, args=args, kwargs=kwargs))
|
|
356
372
|
if not self.stopped.done():
|
|
357
373
|
self._wake()
|
|
358
374
|
return await pen
|
|
359
|
-
msg = f"Unable to execute {func=} in {self}. Execution is only
|
|
375
|
+
msg = f"Unable to execute {func=} in {self}. Execution is only supported while in context."
|
|
360
376
|
raise RuntimeError(msg)
|
|
361
377
|
|
|
362
378
|
@contextmanager
|
|
@@ -370,14 +386,12 @@ class ZMQPoll:
|
|
|
370
386
|
count: tuple[int, Callable[[], Any]] | None = None,
|
|
371
387
|
canceller: Callable[[], Any] | NoValue | None = NoValue,
|
|
372
388
|
) -> Generator[None, Any, None]:
|
|
373
|
-
"""A context manager where `handler` is called with the event number when it occurs for `sock`.
|
|
389
|
+
"""A context manager where `handler` is called in the 'zmq_poll' thread with the event number when it occurs for `sock`.
|
|
374
390
|
|
|
375
|
-
Only one `handler` is allowed per `(socket, flags)` combination.
|
|
376
|
-
inside a caller managed task (call_soon, etc) and the ZMQPoll is stopped, the associated pending
|
|
377
|
-
will be cancelled.
|
|
391
|
+
Only one `handler` is allowed per `(socket, flags)` combination.
|
|
378
392
|
|
|
379
393
|
Args:
|
|
380
|
-
sock: A
|
|
394
|
+
sock: A registered [ZMQPollSocket][].
|
|
381
395
|
handler: A handler to handle the event. The handler is called inside the
|
|
382
396
|
zmq_poll thread. Thread-safe primitives must be used by the handler such
|
|
383
397
|
as [async_kernel.caller.Caller.call_soon][],[async_kernel.caller.Caller.queue_call][], etc.
|
|
@@ -388,12 +402,16 @@ class ZMQPoll:
|
|
|
388
402
|
The callback could be an `event.set` to release the context.
|
|
389
403
|
canceller: A callback to use on the event the poll is stopped. The default cancellation
|
|
390
404
|
behavior is to cancel the pending returned by [async_kernel.caller.Caller.current_pending][].
|
|
391
|
-
Set to None to disable cancellation support. This is
|
|
392
|
-
the context of this
|
|
405
|
+
Set to `None` to disable cancellation support. This is safe when this context manager is
|
|
406
|
+
inside the context of this instance.
|
|
407
|
+
|
|
408
|
+
Raises:
|
|
409
|
+
BusyResourceError: If the `(sock, flags)` combination is already in use.
|
|
410
|
+
RuntimeError: If the default canceller can not be created.
|
|
393
411
|
|
|
394
412
|
Tip:
|
|
395
|
-
The
|
|
396
|
-
|
|
413
|
+
The zmq_poll thread normally disables debugging in the zmq_poll thread so inserting breakpoints
|
|
414
|
+
in the event handler may interfere with debugging.
|
|
397
415
|
"""
|
|
398
416
|
assert not self.stopped.done()
|
|
399
417
|
if canceller is NoValue:
|
|
@@ -12,7 +12,7 @@ import weakref
|
|
|
12
12
|
from typing import TYPE_CHECKING, Any, Generic, Literal, Self, final
|
|
13
13
|
|
|
14
14
|
from aiologic import BinarySemaphore
|
|
15
|
-
from aiologic.lowlevel import AsyncLibraryNotFoundError, current_async_library
|
|
15
|
+
from aiologic.lowlevel import AsyncLibraryNotFoundError, async_checkpoint, current_async_library
|
|
16
16
|
from traitlets import import_item, traitlets
|
|
17
17
|
from traitlets.config import Config, Configurable
|
|
18
18
|
from traitlets.config.application import Application, ClassesType
|
|
@@ -184,9 +184,6 @@ class Interface(StartStopTask, Application, Generic[T_shell_co]):
|
|
|
184
184
|
_connections: tuple[Connection[Self], ...] = ()
|
|
185
185
|
"""The connections to the interface for messaging."""
|
|
186
186
|
|
|
187
|
-
iopub_send_first_connection_only = traitlets.Bool(False).tag(config=True)
|
|
188
|
-
"""Send iopub messages on the first connection only."""
|
|
189
|
-
|
|
190
187
|
_connections_lock = Fixed(BinarySemaphore)
|
|
191
188
|
|
|
192
189
|
shell: Fixed[Self, T_shell_co] = Fixed(lambda c: c["owner"].kernel.main_shell)
|
|
@@ -226,10 +223,6 @@ class Interface(StartStopTask, Application, Generic[T_shell_co]):
|
|
|
226
223
|
|
|
227
224
|
return IPShell
|
|
228
225
|
|
|
229
|
-
@traitlets.default("iopub_send_first_connection_only")
|
|
230
|
-
def _default_iopub_send_first_connection_only(self) -> bool:
|
|
231
|
-
return sys.platform == "emscripten"
|
|
232
|
-
|
|
233
226
|
@classmethod
|
|
234
227
|
@override
|
|
235
228
|
def initialized(cls) -> bool:
|
|
@@ -402,19 +395,22 @@ class Interface(StartStopTask, Application, Generic[T_shell_co]):
|
|
|
402
395
|
await self._pre_start()
|
|
403
396
|
self.log.info("Interface started: %s", self.summary)
|
|
404
397
|
started()
|
|
405
|
-
|
|
398
|
+
# Allow connections to react to being started.
|
|
399
|
+
await async_checkpoint(force=True)
|
|
400
|
+
# Send iopub messages captured during startup.
|
|
401
|
+
del started, self.iopub_send
|
|
402
|
+
while self._iopub_cache:
|
|
403
|
+
self._iopub_cache.reverse()
|
|
404
|
+
args, kwargs = self._iopub_cache.pop()
|
|
405
|
+
self.iopub_send(*args, **kwargs)
|
|
406
406
|
await stop
|
|
407
407
|
|
|
408
408
|
async def _pre_start(self) -> None:
|
|
409
|
-
"""
|
|
409
|
+
"""Perform tasks just prior to setting as started."""
|
|
410
|
+
# Autostart connections waiting until they have started.
|
|
410
411
|
if pending := [import_item(pth)().start().started for pth in self.autostart_connections]:
|
|
411
412
|
self.log.info("Waiting for connections to establish %d", len(pending))
|
|
412
|
-
|
|
413
|
-
del self.iopub_send
|
|
414
|
-
while self._iopub_cache:
|
|
415
|
-
self._iopub_cache.reverse()
|
|
416
|
-
args, kwargs = self._iopub_cache.pop()
|
|
417
|
-
self.iopub_send(*args, **kwargs)
|
|
413
|
+
await self.caller.wait(pending)
|
|
418
414
|
|
|
419
415
|
def update_connections(self, *new: Connection[Self]) -> None:
|
|
420
416
|
"""Update the list of connections.
|
|
@@ -490,8 +486,6 @@ class Interface(StartStopTask, Application, Generic[T_shell_co]):
|
|
|
490
486
|
for c in self._connections:
|
|
491
487
|
try:
|
|
492
488
|
c.iopub_send(msg_type, content, metadata=metadata, parent=parent, ident=ident, buffers=buffers)
|
|
493
|
-
if self.iopub_send_first_connection_only:
|
|
494
|
-
break
|
|
495
489
|
except Exception as e:
|
|
496
490
|
self.log.exception("iopub_send failed for connection %r", c, exc_info=e)
|
|
497
491
|
|
|
@@ -79,38 +79,39 @@ def create_interface_messge_callback_handler(
|
|
|
79
79
|
pack_unpack: tuple[Callable[[Message], T], Callable[[T], Message]] = (pack_json_str, unpack_json),
|
|
80
80
|
) -> Callable[[T, BuffersType], None]:
|
|
81
81
|
""
|
|
82
|
-
|
|
82
|
+
conn: Connection | None = None
|
|
83
83
|
lock = BinarySemaphore()
|
|
84
84
|
session_calls = set()
|
|
85
85
|
pack, unpack = pack_unpack
|
|
86
86
|
|
|
87
87
|
def handle_msg(packed_msg: T, buffers: BuffersType | None = None) -> None:
|
|
88
88
|
"""Handle a packed message."""
|
|
89
|
+
nonlocal conn
|
|
89
90
|
msg: Message = unpack(packed_msg)
|
|
90
91
|
msg["buffers"] = [] if buffers is None else buffers
|
|
91
92
|
session: str = msg["header"]["session"]
|
|
92
93
|
session_calls.add(session)
|
|
93
|
-
conn: Connection | None
|
|
94
94
|
|
|
95
|
-
if
|
|
95
|
+
if conn is None:
|
|
96
96
|
with lock:
|
|
97
|
-
if
|
|
97
|
+
if conn is None:
|
|
98
98
|
conn = Connection(interface.caller, session_id=session)
|
|
99
99
|
|
|
100
|
-
def transmit_msg(msg: Message, ident: list[bytes]) -> None:
|
|
100
|
+
def transmit_msg(msg: Message, ident: list[bytes], conn=conn) -> None:
|
|
101
101
|
"""Pack and send a message."""
|
|
102
|
-
#
|
|
102
|
+
# Jupyterlite
|
|
103
|
+
if parent := msg["parent_header"]:
|
|
104
|
+
msg["header"]["session"] = parent["session"]
|
|
105
|
+
|
|
103
106
|
buffers: BuffersType = msg.pop("buffers") # pyright: ignore[reportAssignmentType]
|
|
104
107
|
reply = send(pack(msg), buffers, blocking_reply := msg["channel"] == Channel.stdin)
|
|
105
108
|
if blocking_reply:
|
|
106
|
-
conn.handle_incoming_msg(unpack(reply), [
|
|
109
|
+
conn.handle_incoming_msg(unpack(reply), [])
|
|
107
110
|
|
|
108
111
|
conn.transmit_msg = transmit_msg
|
|
109
112
|
conn.stopped.add_done_callback(lambda _: delattr(conn, "transmit_msg"))
|
|
110
113
|
conn.start()
|
|
111
|
-
conn.stopping.add_done_callback(lambda _: cache.pop(session))
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
conn.handle_incoming_msg(msg, [conn.bsession])
|
|
115
|
+
conn.handle_incoming_msg(msg, [])
|
|
115
116
|
|
|
116
117
|
return handle_msg
|
|
@@ -74,9 +74,6 @@ class BaseMessage(StartStopTask, LoggingConfigurable, MessageProtocol):
|
|
|
74
74
|
session_id: Fixed[Self, str] = Fixed(lambda c: c["owner"]._session_id)
|
|
75
75
|
"""Used to identify this object as the `session` in a message header."""
|
|
76
76
|
|
|
77
|
-
bsession: Fixed[Self, bytes] = Fixed[Self, bytes](lambda c: c["owner"].session_id.encode())
|
|
78
|
-
"""Used to identfiy this object as the origin of a message."""
|
|
79
|
-
|
|
80
77
|
_pending_messages: Fixed[Self, dict[str, PendingMessage[Any]]] = Fixed(dict)
|
|
81
78
|
"""A mapping of the `msg_id` of message requests to the pending that is resolved with a reply."""
|
|
82
79
|
|
|
@@ -201,6 +198,13 @@ class Connection(HasInterface[T_interface_co], BaseMessage, Generic[T_interface_
|
|
|
201
198
|
|
|
202
199
|
@override
|
|
203
200
|
def handle_incoming_msg(self, msg: Message, ident: list[bytes]) -> None:
|
|
201
|
+
"""The handler for messages received on this connection.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
msg: A new message.
|
|
205
|
+
ident: A list of bytes to route a reply message back to the origin. This can
|
|
206
|
+
be an empty list when there is only one connection, such as [LocalClient][].
|
|
207
|
+
"""
|
|
204
208
|
if msg["header"]["msg_type"].endswith("_reply"):
|
|
205
209
|
self.handle_reply(msg)
|
|
206
210
|
else:
|
|
@@ -158,8 +158,8 @@ class ZMQConnection(ZMQMessage, Connection[T_interface_co], Generic[T_interface_
|
|
|
158
158
|
msg = socket.recv()
|
|
159
159
|
if msg[0] == 1:
|
|
160
160
|
ident = msg[1:]
|
|
161
|
-
|
|
162
|
-
self.
|
|
161
|
+
msg = self.msg(MsgType.iopub_welcome, {"subscription": ident.decode()}, Channel.iopub)
|
|
162
|
+
self.session.send(socket, msg, ident=[ident]) # pyright: ignore[reportArgumentType]
|
|
163
163
|
|
|
164
164
|
def handler(sock, event, channel: Channel, recv=self.session.recv, handle_msg=self.handle_incoming_msg) -> None:
|
|
165
165
|
# Thread: zmq_poll_thread
|
|
@@ -169,6 +169,9 @@ class ZMQConnection(ZMQMessage, Connection[T_interface_co], Generic[T_interface_
|
|
|
169
169
|
|
|
170
170
|
with self.zmq_poll as zpoll:
|
|
171
171
|
await self._bind_sockets()
|
|
172
|
+
self.parent.update_connections(self)
|
|
173
|
+
started()
|
|
174
|
+
await self.parent.started
|
|
172
175
|
with (
|
|
173
176
|
zpoll.event_handler(self._sockets[Channel.control], partial(handler, channel=Channel.control)),
|
|
174
177
|
zpoll.event_handler(self._sockets[Channel.shell], functools.partial(handler, channel=Channel.shell)),
|
|
@@ -176,7 +179,8 @@ class ZMQConnection(ZMQMessage, Connection[T_interface_co], Generic[T_interface_
|
|
|
176
179
|
zpoll.event_handler(self._sockets[Channel.heartbeat], heartbeat_handler),
|
|
177
180
|
zpoll.event_handler(self._sockets[Channel.iopub], iopub_reg_handler),
|
|
178
181
|
):
|
|
179
|
-
await
|
|
182
|
+
await stop
|
|
183
|
+
self.parent.update_connections()
|
|
180
184
|
|
|
181
185
|
async def _bind_sockets(self):
|
|
182
186
|
"""Create, configure and bind all sockets."""
|
|
@@ -211,7 +215,7 @@ class ZMQConnection(ZMQMessage, Connection[T_interface_co], Generic[T_interface_
|
|
|
211
215
|
self._sockets[channel] = socket
|
|
212
216
|
socket.bind(addr)
|
|
213
217
|
|
|
214
|
-
await self.zmq_poll.
|
|
218
|
+
await self.zmq_poll.aexecute(bind_sockets)
|
|
215
219
|
|
|
216
220
|
@override
|
|
217
221
|
def connection_info(self) -> str:
|
|
@@ -277,7 +281,7 @@ class ZMQClient(BaseClient[T_interface_co], ZMQMessage, Generic[T_interface_co])
|
|
|
277
281
|
self._sockets[channel] = socket
|
|
278
282
|
return socket
|
|
279
283
|
|
|
280
|
-
return await self.zmq_poll.
|
|
284
|
+
return await self.zmq_poll.aexecute(open_socket)
|
|
281
285
|
|
|
282
286
|
async def _establish_connection(self, timeout: float | None) -> None:
|
|
283
287
|
# Wait for welcome
|
|
@@ -395,7 +399,7 @@ class ZMQClient(BaseClient[T_interface_co], ZMQMessage, Generic[T_interface_co])
|
|
|
395
399
|
@asynccontextmanager
|
|
396
400
|
@override
|
|
397
401
|
async def iopub_subscribe(
|
|
398
|
-
self, topic=b"", *, timeout: float | None =
|
|
402
|
+
self, topic=b"", *, timeout: float | None = 10.0
|
|
399
403
|
) -> AsyncGenerator[SingleAsyncQueue[Message]]:
|
|
400
404
|
|
|
401
405
|
def forward_messages(sock: ZMQPollSocket, event: int) -> None:
|
|
@@ -414,10 +418,11 @@ class ZMQClient(BaseClient[T_interface_co], ZMQMessage, Generic[T_interface_co])
|
|
|
414
418
|
iopub = await self._connect_socket(Channel.iopub)
|
|
415
419
|
with iopub, self.zmq_poll.event_handler(iopub, forward_messages, canceller=canceller), scope:
|
|
416
420
|
iopub.subscribe(topic)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
421
|
+
if timeout is not None:
|
|
422
|
+
self.log.debug("Waiting for welcome message.")
|
|
423
|
+
if await ready.with_(timeout=timeout):
|
|
424
|
+
self.log.debug("Welcome message received.")
|
|
425
|
+
else:
|
|
426
|
+
msg = f"Welcome message not received after {timeout:0.1f}s!"
|
|
427
|
+
raise TimeoutError(msg)
|
|
423
428
|
yield queue
|
|
@@ -35,9 +35,7 @@ async def test_start_kernel_callable_interface(anyio_backend: Backend):
|
|
|
35
35
|
messages.append(msg)
|
|
36
36
|
return None
|
|
37
37
|
|
|
38
|
-
callable_interface = await start_kernel_callable_interface(
|
|
39
|
-
send=from_interface, stopped=stopped.set, settings={"Interface.iopub_send_first_connection_only": True}
|
|
40
|
-
)
|
|
38
|
+
callable_interface = await start_kernel_callable_interface(send=from_interface, stopped=stopped.set)
|
|
41
39
|
interface = Interface.instance()
|
|
42
40
|
async with LocalClient().start() as client:
|
|
43
41
|
await client.kernel_info()
|
|
@@ -158,15 +158,15 @@ class Test_zmq_Poll:
|
|
|
158
158
|
|
|
159
159
|
async def test_poll_execute_states(self, caller: Caller):
|
|
160
160
|
zmq_poll = ZMQPoll()
|
|
161
|
-
match = "
|
|
161
|
+
match = "Unable to execute"
|
|
162
162
|
# Pre-running
|
|
163
163
|
with pytest.raises(RuntimeError, match=match):
|
|
164
164
|
zmq_poll.execute(lambda: 1 + 1)
|
|
165
165
|
with pytest.raises(RuntimeError, match=match):
|
|
166
|
-
await zmq_poll.
|
|
166
|
+
await zmq_poll.aexecute(lambda: 1 + 1)
|
|
167
167
|
# Running
|
|
168
168
|
with zmq_poll:
|
|
169
|
-
assert (await zmq_poll.
|
|
169
|
+
assert (await zmq_poll.aexecute(lambda: 1 + 1)) == 2
|
|
170
170
|
assert (zmq_poll.execute(lambda: 1 + 1)) == 2
|
|
171
171
|
with pytest.raises(RuntimeError, match="stopped"), zmq_poll:
|
|
172
172
|
None # noqa: B018 # pyright: ignore[reportUnusedExpression]
|
|
@@ -174,11 +174,11 @@ class Test_zmq_Poll:
|
|
|
174
174
|
with pytest.raises(RuntimeError, match=match):
|
|
175
175
|
assert zmq_poll.execute(threading.current_thread) is zmq_poll.thread
|
|
176
176
|
with pytest.raises(RuntimeError, match=match):
|
|
177
|
-
assert await zmq_poll.
|
|
177
|
+
assert await zmq_poll.aexecute(threading.current_thread) is zmq_poll.thread
|
|
178
178
|
|
|
179
179
|
async def test_poll_socket_states(self, caller: Caller):
|
|
180
180
|
zmq_poll = ZMQPoll()
|
|
181
|
-
match = "
|
|
181
|
+
match = "Unable to execute"
|
|
182
182
|
# Pre-running
|
|
183
183
|
with pytest.raises(RuntimeError, match=match):
|
|
184
184
|
zmq_poll.socket(zmq.SocketType.DEALER)
|
|
@@ -4,6 +4,7 @@ import importlib.util
|
|
|
4
4
|
import os
|
|
5
5
|
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
|
+
import anyio
|
|
7
8
|
import pytest
|
|
8
9
|
|
|
9
10
|
import async_kernel.utils
|
|
@@ -66,8 +67,13 @@ async def read_until_msg_type(reader: AsyncGenerator, msg_type: IOPubMsgTypeAlia
|
|
|
66
67
|
Args:
|
|
67
68
|
reader: An async iterator for a queue returned from iopub subscribe.
|
|
68
69
|
msg_type: The type of iopub message to wait for."""
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
try:
|
|
71
|
+
with anyio.fail_after(TIMEOUT):
|
|
72
|
+
while (msg := await anext(reader))["header"]["msg_type"] != msg_type:
|
|
73
|
+
continue
|
|
74
|
+
except TimeoutError as e:
|
|
75
|
+
msg_ = f"Timeout waiting for {msg_type=} {content_checks=}"
|
|
76
|
+
raise TimeoutError(msg_) from e
|
|
71
77
|
check_pub_message(msg, msg_type=msg_type, **content_checks)
|
|
72
78
|
return msg
|
|
73
79
|
|
|
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
|
|
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
|
|
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
|