Skip to content

Commit c7af34c

Browse files
Refer to kernel laucnhing thread instead of assuming the main thread (#1455)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 7193d14 commit c7af34c

File tree

5 files changed

+46
-8
lines changed

5 files changed

+46
-8
lines changed

ipykernel/ipkernel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ async def run_cell(*args, **kwargs):
439439

440440
cm = (
441441
self._cancel_on_sigint
442-
if threading.current_thread() == threading.main_thread()
442+
if threading.current_thread() == self.shell_channel_thread.parent_thread
443443
else self._dummy_context_manager
444444
)
445445
with cm(coro_future):

ipykernel/kernelbase.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,19 +681,19 @@ async def shell_main(self, subshell_id: str | None, msg):
681681
"""Handler of shell messages for a single subshell"""
682682
if self._supports_kernel_subshells:
683683
if subshell_id is None:
684-
assert threading.current_thread() == threading.main_thread()
684+
assert threading.current_thread() == self.shell_channel_thread.parent_thread
685685
asyncio_lock = self._main_asyncio_lock
686686
else:
687687
assert threading.current_thread() not in (
688688
self.shell_channel_thread,
689-
threading.main_thread(),
689+
self.shell_channel_thread.parent_thread,
690690
)
691691
asyncio_lock = self.shell_channel_thread.manager.get_subshell_asyncio_lock(
692692
subshell_id
693693
)
694694
else:
695695
assert subshell_id is None
696-
assert threading.current_thread() == threading.main_thread()
696+
assert threading.current_thread() == self.shell_channel_thread.parent_thread
697697
asyncio_lock = self._main_asyncio_lock
698698

699699
# Whilst executing a shell message, do not accept any other shell messages on the

ipykernel/shellchannel.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import asyncio
6+
from threading import current_thread
67
from typing import Any
78

89
import zmq
@@ -28,13 +29,16 @@ def __init__(
2829
self._manager: SubshellManager | None = None
2930
self._zmq_context = context # Avoid use of self._context
3031
self._shell_socket = shell_socket
32+
# Record the parent thread - the thread that started the app (usually the main thread)
33+
self.parent_thread = current_thread()
3134

3235
self.asyncio_lock = asyncio.Lock()
3336

3437
@property
3538
def manager(self) -> SubshellManager:
3639
# Lazy initialisation.
3740
if self._manager is None:
41+
assert current_thread() == self.parent_thread
3842
self._manager = SubshellManager(
3943
self._zmq_context,
4044
self.io_loop,

ipykernel/subshell_manager.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import typing as t
88
import uuid
99
from functools import partial
10-
from threading import Lock, current_thread, main_thread
10+
from threading import Lock, current_thread
1111

1212
import zmq
1313
from tornado.ioloop import IOLoop
@@ -41,7 +41,7 @@ def __init__(
4141
shell_socket: zmq.Socket[t.Any],
4242
):
4343
"""Initialize the subshell manager."""
44-
assert current_thread() == main_thread()
44+
self._parent_thread = current_thread()
4545

4646
self._context: zmq.Context[t.Any] = context
4747
self._shell_channel_io_loop = shell_channel_io_loop
@@ -127,7 +127,7 @@ def set_on_recv_callback(self, on_recv_callback):
127127
"""Set the callback used by the main shell and all subshells to receive
128128
messages sent from the shell channel thread.
129129
"""
130-
assert current_thread() == main_thread()
130+
assert current_thread() == self._parent_thread
131131
self._on_recv_callback = on_recv_callback
132132
self._shell_channel_to_main.on_recv(IOLoop.current(), partial(self._on_recv_callback, None))
133133

@@ -144,7 +144,7 @@ def subshell_id_from_thread_id(self, thread_id: int) -> str | None:
144144
Only used by %subshell magic so does not have to be fast/cached.
145145
"""
146146
with self._lock_cache:
147-
if thread_id == main_thread().ident:
147+
if thread_id == self._parent_thread.ident:
148148
return None
149149
for id, subshell in self._cache.items():
150150
if subshell.ident == thread_id:

tests/test_start_kernel.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,40 @@ def test_ipython_start_kernel_userns():
5050
assert EXPECTED in text
5151

5252

53+
def test_start_kernel_background_thread():
54+
cmd = dedent(
55+
"""
56+
import threading
57+
import asyncio
58+
from ipykernel.kernelapp import launch_new_instance
59+
60+
def launch():
61+
# Threads don't always have a default event loop so we need to
62+
# create and set a default
63+
loop = asyncio.new_event_loop()
64+
asyncio.set_event_loop(loop)
65+
launch_new_instance()
66+
67+
thread = threading.Thread(target=launch)
68+
thread.start()
69+
thread.join()
70+
"""
71+
)
72+
73+
with setup_kernel(cmd) as client:
74+
client.execute("a = 1")
75+
msg = client.get_shell_msg(timeout=TIMEOUT)
76+
content = msg["content"]
77+
assert content["status"] == "ok"
78+
79+
client.inspect("a")
80+
msg = client.get_shell_msg(timeout=TIMEOUT)
81+
content = msg["content"]
82+
assert content["found"]
83+
text = content["data"]["text/plain"]
84+
assert "1" in text
85+
86+
5387
@pytest.mark.flaky(max_runs=3)
5488
def test_ipython_start_kernel_no_userns():
5589
# Issue #4188 - user_ns should be passed to shell as None, not {}

0 commit comments

Comments
 (0)