@@ -40,8 +40,9 @@ def register_integration(*toolkitnames):
4040 You can provide alternative names for the same toolkit.
4141
4242 The decorated function should take a single argument, the IPython kernel
43- instance, arrange for the event loop to call ``kernel.do_one_iteration()``
44- at least every ``kernel._poll_interval`` seconds, and start the event loop.
43+ instance, arrange for the event loop to yield the asyncio loop when a
44+ message is received by the main shell zmq stream or at least every
45+ ``kernel._poll_interval`` seconds, and start the event loop.
4546
4647 :mod:`ipykernel.eventloops` provides and registers such functions
4748 for a few common event loops.
@@ -68,6 +69,15 @@ def exit_decorator(exit_func):
6869 return decorator
6970
7071
72+ def get_shell_stream (kernel ):
73+ # Return the zmq stream that receives messages for the main shell.
74+ if kernel ._supports_kernel_subshells :
75+ manager = kernel .shell_channel_thread .manager
76+ socket_pair = manager .get_shell_channel_to_subshell_pair (None )
77+ return socket_pair .to_stream
78+ return kernel .shell_stream
79+
80+
7181def _notify_stream_qt (kernel ):
7282 import operator
7383 from functools import lru_cache
@@ -87,17 +97,20 @@ def exit_loop():
8797 kernel ._qt_notifier .setEnabled (False )
8898 kernel .app .qt_event_loop .quit ()
8999
90- def process_stream_events ( ):
100+ def process_stream_events_wrap ( shell_stream , * args , ** kwargs ):
91101 """fall back to main loop when there's a socket event"""
92102 # call flush to ensure that the stream doesn't lose events
93103 # due to our consuming of the edge-triggered FD
94104 # flush returns the number of events consumed.
95105 # if there were any, wake it up
96- if kernel . shell_stream .flush (limit = 1 ):
106+ if shell_stream .flush (limit = 1 ):
97107 exit_loop ()
98108
109+ shell_stream = get_shell_stream (kernel )
110+ process_stream_events = partial (process_stream_events_wrap , shell_stream )
111+
99112 if not hasattr (kernel , "_qt_notifier" ):
100- fd = kernel . shell_stream .getsockopt (zmq .FD )
113+ fd = shell_stream .getsockopt (zmq .FD )
101114 kernel ._qt_notifier = QtCore .QSocketNotifier (
102115 fd , enum_helper ("QtCore.QSocketNotifier.Type" ).Read , kernel .app .qt_event_loop
103116 )
@@ -177,9 +190,11 @@ def loop_wx(kernel):
177190 # Wx uses milliseconds
178191 poll_interval = int (1000 * kernel ._poll_interval )
179192
180- def wake ():
193+ shell_stream = get_shell_stream (kernel )
194+
195+ def wake (shell_stream ):
181196 """wake from wx"""
182- if kernel . shell_stream .flush (limit = 1 ):
197+ if shell_stream .flush (limit = 1 ):
183198 kernel .app .ExitMainLoop ()
184199 return
185200
@@ -201,7 +216,7 @@ def on_timer(self, event):
201216 # wx.Timer to defer back to the tornado event loop.
202217 class IPWxApp (wx .App ): # type:ignore[misc]
203218 def OnInit (self ):
204- self .frame = TimerFrame (wake )
219+ self .frame = TimerFrame (partial ( wake , shell_stream ) )
205220 self .frame .Show (False )
206221 return True
207222
@@ -248,14 +263,14 @@ def __init__(self, app):
248263
249264 def exit_loop ():
250265 """fall back to main loop"""
251- app .tk .deletefilehandler (kernel . shell_stream .getsockopt (zmq .FD ))
266+ app .tk .deletefilehandler (shell_stream .getsockopt (zmq .FD ))
252267 app .quit ()
253268 app .destroy ()
254269 del kernel .app_wrapper
255270
256- def process_stream_events ( * a , ** kw ):
271+ def process_stream_events_wrap ( shell_stream , * a , ** kw ):
257272 """fall back to main loop when there's a socket event"""
258- if kernel . shell_stream .flush (limit = 1 ):
273+ if shell_stream .flush (limit = 1 ):
259274 exit_loop ()
260275
261276 # allow for scheduling exits from the loop in case a timeout needs to
@@ -268,9 +283,10 @@ def _schedule_exit(delay):
268283
269284 # For Tkinter, we create a Tk object and call its withdraw method.
270285 kernel .app_wrapper = BasicAppWrapper (app )
271- app .tk .createfilehandler (
272- kernel .shell_stream .getsockopt (zmq .FD ), READABLE , process_stream_events
273- )
286+ shell_stream = get_shell_stream (kernel )
287+ process_stream_events = partial (process_stream_events_wrap , shell_stream )
288+
289+ app .tk .createfilehandler (shell_stream .getsockopt (zmq .FD ), READABLE , process_stream_events )
274290 # schedule initial call after start
275291 app .after (0 , process_stream_events )
276292
@@ -283,15 +299,19 @@ def _schedule_exit(delay):
283299
284300 nest_asyncio .apply ()
285301
286- doi = kernel .do_one_iteration
287302 # Tk uses milliseconds
288303 poll_interval = int (1000 * kernel ._poll_interval )
289304
305+ shell_stream = get_shell_stream (kernel )
306+
290307 class TimedAppWrapper :
291- def __init__ (self , app , func ):
308+ def __init__ (self , app , shell_stream ):
292309 self .app = app
310+ self .shell_stream = shell_stream
293311 self .app .withdraw ()
294- self .func = func
312+
313+ async def func (self ):
314+ self .shell_stream .flush (limit = 1 )
295315
296316 def on_timer (self ):
297317 loop = asyncio .get_event_loop ()
@@ -305,16 +325,18 @@ def start(self):
305325 self .on_timer () # Call it once to get things going.
306326 self .app .mainloop ()
307327
308- kernel .app_wrapper = TimedAppWrapper (app , doi )
328+ kernel .app_wrapper = TimedAppWrapper (app , shell_stream )
309329 kernel .app_wrapper .start ()
310330
311331
312332@loop_tk .exit
313333def loop_tk_exit (kernel ):
314334 """Exit the tk loop."""
315335 try :
336+ kernel .app_wrapper .app .quit ()
316337 kernel .app_wrapper .app .destroy ()
317338 del kernel .app_wrapper
339+ kernel .eventloop = None
318340 except (RuntimeError , AttributeError ):
319341 pass
320342
@@ -359,6 +381,7 @@ def loop_cocoa(kernel):
359381 from ._eventloop_macos import mainloop , stop
360382
361383 real_excepthook = sys .excepthook
384+ shell_stream = get_shell_stream (kernel )
362385
363386 def handle_int (etype , value , tb ):
364387 """don't let KeyboardInterrupts look like crashes"""
@@ -377,7 +400,7 @@ def handle_int(etype, value, tb):
377400 # don't let interrupts during mainloop invoke crash_handler:
378401 sys .excepthook = handle_int
379402 mainloop (kernel ._poll_interval )
380- if kernel . shell_stream .flush (limit = 1 ):
403+ if shell_stream .flush (limit = 1 ):
381404 # events to process, return control to kernel
382405 return
383406 except BaseException :
@@ -415,13 +438,14 @@ def loop_asyncio(kernel):
415438 loop ._should_close = False # type:ignore[attr-defined]
416439
417440 # pause eventloop when there's an event on a zmq socket
418- def process_stream_events (stream ):
441+ def process_stream_events (shell_stream ):
419442 """fall back to main loop when there's a socket event"""
420- if stream .flush (limit = 1 ):
443+ if shell_stream .flush (limit = 1 ):
421444 loop .stop ()
422445
423- notifier = partial (process_stream_events , kernel .shell_stream )
424- loop .add_reader (kernel .shell_stream .getsockopt (zmq .FD ), notifier )
446+ shell_stream = get_shell_stream (kernel )
447+ notifier = partial (process_stream_events , shell_stream )
448+ loop .add_reader (shell_stream .getsockopt (zmq .FD ), notifier )
425449 loop .call_soon (notifier )
426450
427451 while True :
0 commit comments