Skip to content

Commit 179cdab

Browse files
author
fff122
committed
Preserve async shell context across cells
1 parent c742270 commit 179cdab

3 files changed

Lines changed: 24 additions & 17 deletions

File tree

‎ipykernel/ipkernel.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,8 @@ async def run_cell(*args, **kwargs):
438438
**do_execute_args,
439439
)
440440

441-
coro_future = asyncio.ensure_future(coro)
441+
coro_future = asyncio.current_task()
442+
assert coro_future is not None
442443

443444
cm = (
444445
self._cancel_on_sigint
@@ -448,7 +449,7 @@ async def run_cell(*args, **kwargs):
448449
with cm(coro_future):
449450
res = None
450451
try:
451-
res = await coro_future
452+
res = await coro
452453
finally:
453454
shell.events.trigger("post_execute")
454455
if not silent:

‎ipykernel/utils.py‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,27 @@ def _async_in_context(
5252
if context is None:
5353
context = copy_context()
5454

55-
if sys.version_info >= (3, 11):
56-
57-
@wraps(f)
58-
async def run_in_context(*args, **kwargs):
59-
coro = f(*args, **kwargs)
60-
return await asyncio.create_task(coro, context=context)
61-
62-
return run_in_context
63-
64-
# don't need this backport when we require 3.11
65-
# context_holder so we have a modifiable container for later calls
66-
context_holder = [context] # type: ignore[unreachable]
55+
# Keep the context created by one shell request for subsequent requests. This is
56+
# needed because an async cell runs in a task context, and changes made after an
57+
# await would otherwise be discarded when that task finishes.
58+
context_holder = [context]
6759

6860
async def preserve_context(f, *args, **kwargs):
69-
"""call a coroutine, preserving the context after it is called"""
61+
"""Call a coroutine in a persistent ContextVar context."""
7062
try:
7163
return await f(*args, **kwargs)
7264
finally:
73-
# persist changes to the context for future calls
7465
context_holder[0] = copy_context()
7566

67+
if sys.version_info >= (3, 11):
68+
69+
@wraps(f)
70+
async def run_in_context(*args, **kwargs):
71+
coro = preserve_context(f, *args, **kwargs)
72+
return await asyncio.create_task(coro, context=context_holder[0])
73+
74+
return run_in_context
75+
7676
@wraps(f)
7777
async def run_in_context_pre311(*args, **kwargs):
7878
ctx = context_holder[0]

‎tests/test_kernel.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,9 +908,15 @@ def test_context_vars():
908908
stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id)
909909
assert stdout.strip() == "set"
910910

911+
msg_id, _ = execute(
912+
kc=kc,
913+
code="async def produce_result():\n return 'set after await'\nresult = await produce_result(); ctxvar.set(result)",
914+
)
915+
stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id)
916+
911917
msg_id, _ = execute(
912918
kc=kc,
913919
code="print(ctxvar.get())",
914920
)
915921
stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id)
916-
assert stdout.strip() == "set"
922+
assert stdout.strip() == "set after await"

0 commit comments

Comments
 (0)