conkernelclient 0.0.16__tar.gz → 0.0.17__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.
- {conkernelclient-0.0.16/conkernelclient.egg-info → conkernelclient-0.0.17}/PKG-INFO +7 -2
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient/__init__.py +1 -1
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient/_modidx.py +9 -2
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient/core.py +50 -3
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient/ops.py +26 -13
- {conkernelclient-0.0.16 → conkernelclient-0.0.17/conkernelclient.egg-info}/PKG-INFO +7 -2
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient.egg-info/SOURCES.txt +2 -1
- conkernelclient-0.0.17/conkernelclient.egg-info/requires.txt +10 -0
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/pyproject.toml +9 -1
- conkernelclient-0.0.17/tests/test_races.py +76 -0
- conkernelclient-0.0.16/conkernelclient.egg-info/requires.txt +0 -5
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/LICENSE +0 -0
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/MANIFEST.in +0 -0
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/README.md +0 -0
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient.egg-info/dependency_links.txt +0 -0
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient.egg-info/entry_points.txt +0 -0
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient.egg-info/top_level.txt +0 -0
- {conkernelclient-0.0.16 → conkernelclient-0.0.17}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conkernelclient
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.17
|
|
4
4
|
Summary: Concurrent-safe Jupyter KernelClient
|
|
5
5
|
Author-email: Jeremy Howard <github@jhoward.fastmail.fm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,9 +13,14 @@ Requires-Python: >=3.10
|
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: jupyter_client
|
|
16
|
-
Requires-Dist: fastcore
|
|
16
|
+
Requires-Dist: fastcore>=2.1.17
|
|
17
|
+
Requires-Dist: jupywire>=0.0.1
|
|
17
18
|
Provides-Extra: dev
|
|
18
19
|
Requires-Dist: ipykernel; extra == "dev"
|
|
20
|
+
Requires-Dist: ipymini>=0.1.17; extra == "dev"
|
|
21
|
+
Requires-Dist: ipyfuncs>=0.0.1; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-timeout; extra == "dev"
|
|
19
24
|
Dynamic: license-file
|
|
20
25
|
|
|
21
26
|
# conkernelclient
|
|
@@ -14,6 +14,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
14
14
|
'conkernelclient/core.py'),
|
|
15
15
|
'conkernelclient.core.ConKernelClient._fail_pending': ( 'core.html#conkernelclient._fail_pending',
|
|
16
16
|
'conkernelclient/core.py'),
|
|
17
|
+
'conkernelclient.core.ConKernelClient.astop_channels': ( 'core.html#conkernelclient.astop_channels',
|
|
18
|
+
'conkernelclient/core.py'),
|
|
17
19
|
'conkernelclient.core.ConKernelClient.execute': ( 'core.html#conkernelclient.execute',
|
|
18
20
|
'conkernelclient/core.py'),
|
|
19
21
|
'conkernelclient.core.ConKernelClient.start_channels': ( 'core.html#conkernelclient.start_channels',
|
|
@@ -22,6 +24,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
22
24
|
'conkernelclient/core.py'),
|
|
23
25
|
'conkernelclient.core.ConKernelClient.stop_channels': ( 'core.html#conkernelclient.stop_channels',
|
|
24
26
|
'conkernelclient/core.py'),
|
|
27
|
+
'conkernelclient.core.ConKernelClient.wait_for_ready': ( 'core.html#conkernelclient.wait_for_ready',
|
|
28
|
+
'conkernelclient/core.py'),
|
|
25
29
|
'conkernelclient.core.ConKernelManager': ('core.html#conkernelmanager', 'conkernelclient/core.py'),
|
|
26
30
|
'conkernelclient.core.ConKernelManager._transport_encryption_default': ( 'core.html#conkernelmanager._transport_encryption_default',
|
|
27
31
|
'conkernelclient/core.py'),
|
|
@@ -29,7 +33,9 @@ d = { 'settings': { 'branch': 'main',
|
|
|
29
33
|
'conkernelclient.core._send': ('core.html#_send', 'conkernelclient/core.py'),
|
|
30
34
|
'conkernelclient.core.apply_session_patch': ( 'core.html#apply_session_patch',
|
|
31
35
|
'conkernelclient/core.py')},
|
|
32
|
-
'conkernelclient.ops': { 'conkernelclient.ops.ConKernelClient.
|
|
36
|
+
'conkernelclient.ops': { 'conkernelclient.ops.ConKernelClient._pre_ipy': ( 'ops.html#conkernelclient._pre_ipy',
|
|
37
|
+
'conkernelclient/ops.py'),
|
|
38
|
+
'conkernelclient.ops.ConKernelClient.cmd': ('ops.html#conkernelclient.cmd', 'conkernelclient/ops.py'),
|
|
33
39
|
'conkernelclient.ops.ConKernelClient.control_request': ( 'ops.html#conkernelclient.control_request',
|
|
34
40
|
'conkernelclient/ops.py'),
|
|
35
41
|
'conkernelclient.ops.ConKernelClient.ctl': ('ops.html#conkernelclient.ctl', 'conkernelclient/ops.py'),
|
|
@@ -50,6 +56,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
50
56
|
'conkernelclient/ops.py'),
|
|
51
57
|
'conkernelclient.ops.ConKernelClient.iopub_flush': ( 'ops.html#conkernelclient.iopub_flush',
|
|
52
58
|
'conkernelclient/ops.py'),
|
|
59
|
+
'conkernelclient.ops.ConKernelClient.reply': ( 'ops.html#conkernelclient.reply',
|
|
60
|
+
'conkernelclient/ops.py'),
|
|
53
61
|
'conkernelclient.ops.ConKernelClient.shell_request': ( 'ops.html#conkernelclient.shell_request',
|
|
54
62
|
'conkernelclient/ops.py'),
|
|
55
63
|
'conkernelclient.ops.ConKernelClient.user_exprs': ( 'ops.html#conkernelclient.user_exprs',
|
|
@@ -66,7 +74,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
66
74
|
'conkernelclient.ops.iopub_msgs': ('ops.html#iopub_msgs', 'conkernelclient/ops.py'),
|
|
67
75
|
'conkernelclient.ops.iopub_streams': ('ops.html#iopub_streams', 'conkernelclient/ops.py'),
|
|
68
76
|
'conkernelclient.ops.iter_timeout': ('ops.html#iter_timeout', 'conkernelclient/ops.py'),
|
|
69
|
-
'conkernelclient.ops.nb_outputs': ('ops.html#nb_outputs', 'conkernelclient/ops.py'),
|
|
70
77
|
'conkernelclient.ops.parent_id': ('ops.html#parent_id', 'conkernelclient/ops.py'),
|
|
71
78
|
'conkernelclient.ops.parse_expr': ('ops.html#parse_expr', 'conkernelclient/ops.py'),
|
|
72
79
|
'conkernelclient.ops.reconnect': ('ops.html#reconnect', 'conkernelclient/ops.py'),
|
|
@@ -12,6 +12,9 @@ from jupyter_client import AsyncKernelClient, AsyncKernelManager
|
|
|
12
12
|
from jupyter_client.session import Session
|
|
13
13
|
from zmq.error import ZMQError
|
|
14
14
|
from traitlets import Type, default
|
|
15
|
+
from fastcore.utils import patch
|
|
16
|
+
from jupywire.ops import EvalOps
|
|
17
|
+
from queue import Empty
|
|
15
18
|
import asyncio, zmq.asyncio, time, logging
|
|
16
19
|
|
|
17
20
|
# %% ../nbs/00_core.ipynb #737a0fc1
|
|
@@ -45,7 +48,7 @@ def apply_session_patch():
|
|
|
45
48
|
Session.send = _send
|
|
46
49
|
|
|
47
50
|
# %% ../nbs/00_core.ipynb #d6a5fa6a
|
|
48
|
-
class ConKernelClient(AsyncKernelClient):
|
|
51
|
+
class ConKernelClient(EvalOps, AsyncKernelClient):
|
|
49
52
|
def __init__(self, *args, **kwargs):
|
|
50
53
|
apply_session_patch()
|
|
51
54
|
# jupyter_client's `get_connection_info` returns curve keys as str, but the client traits are `Bytes`
|
|
@@ -91,7 +94,6 @@ class ConKernelClient(AsyncKernelClient):
|
|
|
91
94
|
self._fail_pending(exc, skip=mid)
|
|
92
95
|
self._shell_reader_task = asyncio.create_task(_reader())
|
|
93
96
|
await _ready.wait()
|
|
94
|
-
await asyncio.sleep(0.2)
|
|
95
97
|
return self
|
|
96
98
|
|
|
97
99
|
def stop_channels(self):
|
|
@@ -101,7 +103,6 @@ class ConKernelClient(AsyncKernelClient):
|
|
|
101
103
|
if (tk := getattr(self, '_shell_reader_task', None)):
|
|
102
104
|
tk.cancel()
|
|
103
105
|
self._shell_reader_task = None
|
|
104
|
-
time.sleep(0.2)
|
|
105
106
|
|
|
106
107
|
async def _async_recv_reply(self, msg_id, timeout=None, channel="shell"):
|
|
107
108
|
if channel == "control": return await self._async_get_control_msg(timeout=timeout)
|
|
@@ -136,6 +137,52 @@ class ConKernelClient(AsyncKernelClient):
|
|
|
136
137
|
except (AssertionError, OSError, ZMQError) as e: raise DeadKernelError(f"Kernel socket closed: {e}") from e
|
|
137
138
|
await asyncio.sleep(0.01)
|
|
138
139
|
|
|
140
|
+
# %% ../nbs/00_core.ipynb #3771c654
|
|
141
|
+
@patch
|
|
142
|
+
async def wait_for_ready(self:ConKernelClient, timeout=None):
|
|
143
|
+
"Wait for the kernel to be ready: deterministic via `iopub_welcome` (JEP 65) when the kernel sends one, else jupyter_client's probe loop"
|
|
144
|
+
deadline = time.monotonic()+timeout if timeout is not None else None
|
|
145
|
+
def _left():
|
|
146
|
+
if deadline is None: return 1.0
|
|
147
|
+
t = deadline-time.monotonic()
|
|
148
|
+
if t <= 0: raise RuntimeError(f"Kernel didn't respond in {timeout} seconds")
|
|
149
|
+
return min(t, 1.0)
|
|
150
|
+
async def _get(ch):
|
|
151
|
+
while True:
|
|
152
|
+
try: return await ch.get_msg(timeout=_left())
|
|
153
|
+
except Empty:
|
|
154
|
+
if not await self._async_is_alive(): raise RuntimeError("Kernel died before replying to kernel_info")
|
|
155
|
+
msg = None
|
|
156
|
+
while msg is None:
|
|
157
|
+
self.kernel_info()
|
|
158
|
+
try: msg = await self.iopub_channel.get_msg(timeout=_left())
|
|
159
|
+
except Empty:
|
|
160
|
+
if not await self._async_is_alive(): raise RuntimeError("Kernel died before replying to kernel_info")
|
|
161
|
+
if msg['msg_type']=='iopub_welcome':
|
|
162
|
+
mid = self.kernel_info()
|
|
163
|
+
while (reply := await _get(self.shell_channel))['parent_header'].get('msg_id') != mid: pass
|
|
164
|
+
self._handle_kernel_info_reply(reply)
|
|
165
|
+
while not (msg['msg_type']=='status' and msg['parent_header'].get('msg_id')==mid and msg['content']['execution_state']=='idle'):
|
|
166
|
+
msg = await _get(self.iopub_channel)
|
|
167
|
+
else:
|
|
168
|
+
while (reply := await _get(self.shell_channel))['msg_type'] != 'kernel_info_reply': pass
|
|
169
|
+
self._handle_kernel_info_reply(reply)
|
|
170
|
+
while True:
|
|
171
|
+
try: await self.iopub_channel.get_msg(timeout=0.2)
|
|
172
|
+
except Empty: break
|
|
173
|
+
|
|
174
|
+
# %% ../nbs/00_core.ipynb #967c10a1
|
|
175
|
+
@patch
|
|
176
|
+
async def astop_channels(self:ConKernelClient):
|
|
177
|
+
"Stop channels and cancel the background shell-reply reader, awaiting its exit"
|
|
178
|
+
self._fail_pending(RuntimeError("Shell channels stopped before reply"))
|
|
179
|
+
if (tk := getattr(self, '_shell_reader_task', None)):
|
|
180
|
+
tk.cancel()
|
|
181
|
+
try: await tk
|
|
182
|
+
except asyncio.CancelledError: pass
|
|
183
|
+
self._shell_reader_task = None
|
|
184
|
+
super(ConKernelClient, self).stop_channels()
|
|
185
|
+
|
|
139
186
|
# %% ../nbs/00_core.ipynb #b828c222
|
|
140
187
|
class ConKernelManager(AsyncKernelManager):
|
|
141
188
|
client_class,client_factory = ConKernelClient,Type(ConKernelClient)
|
|
@@ -5,12 +5,14 @@ Docs: https://AnswerDotAI.github.io/conkernelclient/ops.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_ops.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['default_timeout', 'output_types', 'parent_id', 'iter_timeout', '
|
|
9
|
-
'
|
|
8
|
+
__all__ = ['default_timeout', 'output_types', 'parent_id', 'iter_timeout', 'iopub_msgs', 'iopub_streams', 'EvalError',
|
|
9
|
+
'parse_expr', 'run_kernel', 'reconnect']
|
|
10
10
|
|
|
11
11
|
# %% ../nbs/01_ops.ipynb #23936f1c
|
|
12
12
|
from fastcore.utils import *
|
|
13
|
-
from .
|
|
13
|
+
from fastcore.ansi import strip_ansi
|
|
14
|
+
from fastcore.nbio import msgs2outs, preferred_out
|
|
15
|
+
from .core import ConKernelClient, ConKernelManager, DeadKernelError
|
|
14
16
|
from contextlib import asynccontextmanager
|
|
15
17
|
from ast import literal_eval
|
|
16
18
|
from queue import Empty
|
|
@@ -52,15 +54,6 @@ async def iopub_flush(self:ConKernelClient, timeout=0.1):
|
|
|
52
54
|
# %% ../nbs/01_ops.ipynb #add288fe
|
|
53
55
|
output_types = {'stream', 'execute_result', 'display_data', 'error'}
|
|
54
56
|
|
|
55
|
-
def nb_outputs(msgs):
|
|
56
|
-
"Convert iopub `msgs` to nbformat-style output dicts, dropping non-output messages"
|
|
57
|
-
res = []
|
|
58
|
-
for m in msgs:
|
|
59
|
-
if (mt := m['msg_type']) not in output_types: continue
|
|
60
|
-
d = dict(output_type=mt, **m['content'])
|
|
61
|
-
d.pop('transient', None)
|
|
62
|
-
res.append(d)
|
|
63
|
-
return res
|
|
64
57
|
|
|
65
58
|
# %% ../nbs/01_ops.ipynb #ebdbd6eb
|
|
66
59
|
def iopub_msgs(msgs, msg_type=None):
|
|
@@ -92,7 +85,7 @@ async def exec_ok(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
|
92
85
|
async def exec_outs(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
93
86
|
"Execute `code` and return just its nbformat-style outputs"
|
|
94
87
|
reply, outputs = await self.exec_drain(code, timeout=timeout, **kw)
|
|
95
|
-
return
|
|
88
|
+
return msgs2outs(outputs)
|
|
96
89
|
|
|
97
90
|
# %% ../nbs/01_ops.ipynb #77ab9da4
|
|
98
91
|
class EvalError(Exception):
|
|
@@ -117,6 +110,26 @@ async def eval_expr(self:ConKernelClient, expr:str, code:str='', timeout=default
|
|
|
117
110
|
if r.get('status') != 'ok': raise EvalError(f"{r.get('ename')}: {r.get('evalue')}")
|
|
118
111
|
return parse_expr(r['data']['text/plain'])
|
|
119
112
|
|
|
113
|
+
# %% ../nbs/01_ops.ipynb #d9bbaa8d
|
|
114
|
+
@patch
|
|
115
|
+
def reply(self:ConKernelClient,
|
|
116
|
+
code, # A string of code in the kernel's language.
|
|
117
|
+
user_expressions:NoneType=None, # A dict mapping names to expressions to be evaluated in the user's dict
|
|
118
|
+
allow_stdin:NoneType=None, # Flag for whether the kernel can send stdin requests to frontends.
|
|
119
|
+
cts_typ:str='code', timeout:int=None, msg_id:NoneType=None, priority:bool=False, **kw
|
|
120
|
+
):
|
|
121
|
+
if priority: assert self.priority, 'no priority subshell configured'
|
|
122
|
+
return self.execute(code, user_expressions=user_expressions, allow_stdin=allow_stdin, reply=True,
|
|
123
|
+
cts_typ=cts_typ, timeout=timeout or getattr(self, 'default_timeout', default_timeout), msg_id=msg_id, subsh_id=self.priority if priority else None, **kw)
|
|
124
|
+
|
|
125
|
+
# %% ../nbs/01_ops.ipynb #5de02acb
|
|
126
|
+
from jupywire.ops import EvalOps, EvalException, try_eval
|
|
127
|
+
|
|
128
|
+
@patch
|
|
129
|
+
def _pre_ipy(self:ConKernelClient):
|
|
130
|
+
if not self.shell_channel.is_alive(): raise DeadKernelError()
|
|
131
|
+
|
|
132
|
+
|
|
120
133
|
# %% ../nbs/01_ops.ipynb #aafc1ab4
|
|
121
134
|
@patch
|
|
122
135
|
def shell_request(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conkernelclient
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.17
|
|
4
4
|
Summary: Concurrent-safe Jupyter KernelClient
|
|
5
5
|
Author-email: Jeremy Howard <github@jhoward.fastmail.fm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,9 +13,14 @@ Requires-Python: >=3.10
|
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: jupyter_client
|
|
16
|
-
Requires-Dist: fastcore
|
|
16
|
+
Requires-Dist: fastcore>=2.1.17
|
|
17
|
+
Requires-Dist: jupywire>=0.0.1
|
|
17
18
|
Provides-Extra: dev
|
|
18
19
|
Requires-Dist: ipykernel; extra == "dev"
|
|
20
|
+
Requires-Dist: ipymini>=0.1.17; extra == "dev"
|
|
21
|
+
Requires-Dist: ipyfuncs>=0.0.1; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-timeout; extra == "dev"
|
|
19
24
|
Dynamic: license-file
|
|
20
25
|
|
|
21
26
|
# conkernelclient
|
|
@@ -11,4 +11,5 @@ conkernelclient.egg-info/SOURCES.txt
|
|
|
11
11
|
conkernelclient.egg-info/dependency_links.txt
|
|
12
12
|
conkernelclient.egg-info/entry_points.txt
|
|
13
13
|
conkernelclient.egg-info/requires.txt
|
|
14
|
-
conkernelclient.egg-info/top_level.txt
|
|
14
|
+
conkernelclient.egg-info/top_level.txt
|
|
15
|
+
tests/test_races.py
|
|
@@ -17,12 +17,17 @@ classifiers = [
|
|
|
17
17
|
]
|
|
18
18
|
dependencies = [
|
|
19
19
|
'jupyter_client',
|
|
20
|
-
'fastcore',
|
|
20
|
+
'fastcore>=2.1.17',
|
|
21
|
+
'jupywire>=0.0.1',
|
|
21
22
|
]
|
|
22
23
|
|
|
23
24
|
[project.optional-dependencies]
|
|
24
25
|
dev = [
|
|
25
26
|
"ipykernel",
|
|
27
|
+
"ipymini>=0.1.17",
|
|
28
|
+
"ipyfuncs>=0.0.1",
|
|
29
|
+
"pytest",
|
|
30
|
+
"pytest-timeout",
|
|
26
31
|
]
|
|
27
32
|
|
|
28
33
|
[project.urls]
|
|
@@ -44,3 +49,6 @@ allowed_cell_metadata_keys = ['solveit_ai']
|
|
|
44
49
|
|
|
45
50
|
[tool.chkstyle]
|
|
46
51
|
skip_paths = ["_modidx.py"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
timeout = 120
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Race-condition regression tests: adversarial timing on the shell send path.
|
|
2
|
+
|
|
3
|
+
`LockedSendChannel` serializes shell sends under a lock with a random 10-15ms sleep,
|
|
4
|
+
widening the window between the sync zmq send path and the async receive side. The burst
|
|
5
|
+
test then keeps many replies pending at once, so the background reader races every send.
|
|
6
|
+
The parked-recv test is the minimal repro of the edge-triggered FD bug the Session patch
|
|
7
|
+
fixes: a recv parked in poll(None) must still wake after a sync send consumes the FD edge.
|
|
8
|
+
"""
|
|
9
|
+
import asyncio, functools, random, threading, time
|
|
10
|
+
from queue import Empty
|
|
11
|
+
|
|
12
|
+
from jupyter_client import AsyncKernelManager
|
|
13
|
+
from jupyter_client.channels import AsyncZMQSocketChannel
|
|
14
|
+
from traitlets import Type
|
|
15
|
+
|
|
16
|
+
from conkernelclient import ConKernelClient, ConKernelManager
|
|
17
|
+
from conkernelclient.core import apply_session_patch
|
|
18
|
+
from conkernelclient.ops import run_kernel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def locked_sleep(f):
|
|
22
|
+
@functools.wraps(f)
|
|
23
|
+
def _f(self, *args, **kwargs):
|
|
24
|
+
with self.lock:
|
|
25
|
+
time.sleep(random.uniform(0.01, 0.015))
|
|
26
|
+
return f(self, *args, **kwargs)
|
|
27
|
+
return _f
|
|
28
|
+
|
|
29
|
+
class LockedSendChannel(AsyncZMQSocketChannel):
|
|
30
|
+
lock = threading.RLock()
|
|
31
|
+
@locked_sleep
|
|
32
|
+
def send(self, msg): return super().send(msg)
|
|
33
|
+
|
|
34
|
+
class LockedClient(ConKernelClient):
|
|
35
|
+
shell_channel_class = LockedSendChannel
|
|
36
|
+
|
|
37
|
+
class LockedKernelManager(ConKernelManager):
|
|
38
|
+
client_class,client_factory = LockedClient,Type(LockedClient)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_burst_replies_under_send_jitter():
|
|
42
|
+
"20 pending replies at once, every send jittered: the reader must route each reply to its waiter."
|
|
43
|
+
async def _run():
|
|
44
|
+
async with run_kernel(kernel_name="ipymini", manager_cls=LockedKernelManager) as (km, kc):
|
|
45
|
+
reps = [kc.execute(f"x{i} = {i}; x{i}", reply=True, timeout=30) for i in range(20)]
|
|
46
|
+
for i, r in enumerate(await asyncio.gather(*reps)): assert r["content"]["status"] == "ok", (i, r["content"])
|
|
47
|
+
asyncio.run(_run())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_parked_recv_survives_sync_send():
|
|
51
|
+
"A recv parked in poll(None) must wake with the right reply after sync sends while the loop was busy."
|
|
52
|
+
async def _run():
|
|
53
|
+
apply_session_patch()
|
|
54
|
+
km = AsyncKernelManager()
|
|
55
|
+
await km.start_kernel()
|
|
56
|
+
kc = km.client()
|
|
57
|
+
kc.start_channels()
|
|
58
|
+
await kc.wait_for_ready()
|
|
59
|
+
try:
|
|
60
|
+
# Drain replies to any extra kernel_info requests from a slow start,
|
|
61
|
+
# so the parked recv below can only receive the execute reply.
|
|
62
|
+
try:
|
|
63
|
+
while True: await kc.get_shell_msg(timeout=0.3)
|
|
64
|
+
except Empty: pass
|
|
65
|
+
|
|
66
|
+
fut = asyncio.create_task(kc.get_shell_msg(timeout=None))
|
|
67
|
+
await asyncio.sleep(0.1)
|
|
68
|
+
mid = kc.execute("1+1") # loop busy below, so the reply buffers and sets the FD edge
|
|
69
|
+
time.sleep(0.4)
|
|
70
|
+
kc.execute("2+2") # sync send consumes the edge via process_commands
|
|
71
|
+
reply = await asyncio.wait_for(fut, 3) # TimeoutError here is the regression
|
|
72
|
+
assert reply["parent_header"]["msg_id"] == mid
|
|
73
|
+
finally:
|
|
74
|
+
kc.stop_channels()
|
|
75
|
+
await km.shutdown_kernel(now=True)
|
|
76
|
+
asyncio.run(_run())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{conkernelclient-0.0.16 → conkernelclient-0.0.17}/conkernelclient.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|