3
3
# Copyright (c) IPython Development Team.
4
4
# Distributed under the terms of the Modified BSD License.
5
5
6
+ from __future__ import annotations
7
+
6
8
import atexit
7
9
import contextvars
8
10
import io
15
17
from collections import defaultdict , deque
16
18
from io import StringIO , TextIOBase
17
19
from threading import Event , Thread , local
18
- from typing import Any , Callable , Optional
20
+ from typing import Any , Callable
19
21
20
22
import zmq
21
23
from anyio import create_task_group , run , sleep , to_thread
25
27
# Globals
26
28
# -----------------------------------------------------------------------------
27
29
28
- MASTER = 0
29
- CHILD = 1
30
+ _PARENT = 0
31
+ _CHILD = 1
30
32
31
33
PIPE_BUFFER_SIZE = 1000
32
34
@@ -87,9 +89,16 @@ def __init__(self, socket, pipe=False):
87
89
Whether this process should listen for IOPub messages
88
90
piped from subprocesses.
89
91
"""
90
- self .socket = socket
92
+ # ensure all of our sockets as sync zmq.Sockets
93
+ # don't create async wrappers until we are within the appropriate coroutines
94
+ self .socket : zmq .Socket [bytes ] | None = zmq .Socket (socket )
95
+ if self .socket .context is None :
96
+ # bug in pyzmq, shadow socket doesn't always inherit context attribute
97
+ self .socket .context = socket .context # type:ignore[unreachable]
98
+ self ._context = socket .context
99
+
91
100
self .background_socket = BackgroundSocket (self )
92
- self ._master_pid = os .getpid ()
101
+ self ._main_pid = os .getpid ()
93
102
self ._pipe_flag = pipe
94
103
if pipe :
95
104
self ._setup_pipe_in ()
@@ -106,8 +115,7 @@ def __init__(self, socket, pipe=False):
106
115
107
116
def _setup_event_pipe (self ):
108
117
"""Create the PULL socket listening for events that should fire in this thread."""
109
- ctx = self .socket .context
110
- self ._pipe_in0 = ctx .socket (zmq .PULL )
118
+ self ._pipe_in0 = self ._context .socket (zmq .PULL , socket_class = zmq .Socket )
111
119
self ._pipe_in0 .linger = 0
112
120
113
121
_uuid = b2a_hex (os .urandom (16 )).decode ("ascii" )
@@ -141,8 +149,8 @@ def _event_pipe(self):
141
149
event_pipe = self ._local .event_pipe
142
150
except AttributeError :
143
151
# new thread, new event pipe
144
- ctx = zmq . Context ( self . socket . context )
145
- event_pipe = ctx . socket (zmq .PUSH )
152
+ # create sync base socket
153
+ event_pipe = self . _context . socket (zmq .PUSH , socket_class = zmq . Socket )
146
154
event_pipe .linger = 0
147
155
event_pipe .connect (self ._event_interface )
148
156
self ._local .event_pipe = event_pipe
@@ -161,9 +169,11 @@ async def _handle_event(self):
161
169
Whenever *an* event arrives on the event stream,
162
170
*all* waiting events are processed in order.
163
171
"""
172
+ # create async wrapper within coroutine
173
+ pipe_in = zmq .asyncio .Socket (self ._pipe_in0 )
164
174
try :
165
175
while True :
166
- await self . _pipe_in0 .recv ()
176
+ await pipe_in .recv ()
167
177
# freeze event count so new writes don't extend the queue
168
178
# while we are processing
169
179
n_events = len (self ._events )
@@ -177,12 +187,12 @@ async def _handle_event(self):
177
187
178
188
def _setup_pipe_in (self ):
179
189
"""setup listening pipe for IOPub from forked subprocesses"""
180
- ctx = self .socket . context
190
+ ctx = self ._context
181
191
182
192
# use UUID to authenticate pipe messages
183
193
self ._pipe_uuid = os .urandom (16 )
184
194
185
- self ._pipe_in1 = ctx .socket (zmq .PULL )
195
+ self ._pipe_in1 = ctx .socket (zmq .PULL , socket_class = zmq . Socket )
186
196
self ._pipe_in1 .linger = 0
187
197
188
198
try :
@@ -199,6 +209,8 @@ def _setup_pipe_in(self):
199
209
200
210
async def _handle_pipe_msgs (self ):
201
211
"""handle pipe messages from a subprocess"""
212
+ # create async wrapper within coroutine
213
+ self ._async_pipe_in1 = zmq .asyncio .Socket (self ._pipe_in1 )
202
214
try :
203
215
while True :
204
216
await self ._handle_pipe_msg ()
@@ -209,8 +221,8 @@ async def _handle_pipe_msgs(self):
209
221
210
222
async def _handle_pipe_msg (self , msg = None ):
211
223
"""handle a pipe message from a subprocess"""
212
- msg = msg or await self ._pipe_in1 .recv_multipart ()
213
- if not self ._pipe_flag or not self ._is_master_process ():
224
+ msg = msg or await self ._async_pipe_in1 .recv_multipart ()
225
+ if not self ._pipe_flag or not self ._is_main_process ():
214
226
return
215
227
if msg [0 ] != self ._pipe_uuid :
216
228
print ("Bad pipe message: %s" , msg , file = sys .__stderr__ )
@@ -225,14 +237,14 @@ def _setup_pipe_out(self):
225
237
pipe_out .connect ("tcp://127.0.0.1:%i" % self ._pipe_port )
226
238
return ctx , pipe_out
227
239
228
- def _is_master_process (self ):
229
- return os .getpid () == self ._master_pid
240
+ def _is_main_process (self ):
241
+ return os .getpid () == self ._main_pid
230
242
231
243
def _check_mp_mode (self ):
232
244
"""check for forks, and switch to zmq pipeline if necessary"""
233
- if not self ._pipe_flag or self ._is_master_process ():
234
- return MASTER
235
- return CHILD
245
+ if not self ._pipe_flag or self ._is_main_process ():
246
+ return _PARENT
247
+ return _CHILD
236
248
237
249
def start (self ):
238
250
"""Start the IOPub thread"""
@@ -265,7 +277,8 @@ def close(self):
265
277
self ._pipe_in0 .close ()
266
278
if self ._pipe_flag :
267
279
self ._pipe_in1 .close ()
268
- self .socket .close ()
280
+ if self .socket is not None :
281
+ self .socket .close ()
269
282
self .socket = None
270
283
271
284
@property
@@ -301,12 +314,12 @@ def _really_send(self, msg, *args, **kwargs):
301
314
return
302
315
303
316
mp_mode = self ._check_mp_mode ()
304
-
305
- if mp_mode != CHILD :
306
- # we are master, do a regular send
317
+ if mp_mode != _CHILD :
318
+ # we are the main parent process, do a regular send
319
+ assert self . socket is not None
307
320
self .socket .send_multipart (msg , * args , ** kwargs )
308
321
else :
309
- # we are a child, pipe to master
322
+ # we are a child, pipe to parent process
310
323
# new context/socket for every pipe-out
311
324
# since forks don't teardown politely, use ctx.term to ensure send has completed
312
325
ctx , pipe_out = self ._setup_pipe_out ()
@@ -379,7 +392,7 @@ class OutStream(TextIOBase):
379
392
flush_interval = 0.2
380
393
topic = None
381
394
encoding = "UTF-8"
382
- _exc : Optional [ Any ] = None
395
+ _exc : Any = None
383
396
384
397
def fileno (self ):
385
398
"""
@@ -477,7 +490,7 @@ def __init__(
477
490
self ._thread_to_parent = {}
478
491
self ._thread_to_parent_header = {}
479
492
self ._parent_header_global = {}
480
- self ._master_pid = os .getpid ()
493
+ self ._main_pid = os .getpid ()
481
494
self ._flush_pending = False
482
495
self ._subprocess_flush_pending = False
483
496
self ._buffer_lock = threading .RLock ()
@@ -569,8 +582,8 @@ def _setup_stream_redirects(self, name):
569
582
self .watch_fd_thread .daemon = True
570
583
self .watch_fd_thread .start ()
571
584
572
- def _is_master_process (self ):
573
- return os .getpid () == self ._master_pid
585
+ def _is_main_process (self ):
586
+ return os .getpid () == self ._main_pid
574
587
575
588
def set_parent (self , parent ):
576
589
"""Set the parent header."""
@@ -674,7 +687,7 @@ def _flush(self):
674
687
ident = self .topic ,
675
688
)
676
689
677
- def write (self , string : str ) -> Optional [ int ]: # type:ignore[override]
690
+ def write (self , string : str ) -> int :
678
691
"""Write to current stream after encoding if necessary
679
692
680
693
Returns
@@ -700,15 +713,15 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override]
700
713
msg = "I/O operation on closed file"
701
714
raise ValueError (msg )
702
715
703
- is_child = not self ._is_master_process ()
716
+ is_child = not self ._is_main_process ()
704
717
# only touch the buffer in the IO thread to avoid races
705
718
with self ._buffer_lock :
706
719
self ._buffers [frozenset (parent .items ())].write (string )
707
720
if is_child :
708
721
# mp.Pool cannot be trusted to flush promptly (or ever),
709
722
# and this helps.
710
723
if self ._subprocess_flush_pending :
711
- return None
724
+ return 0
712
725
self ._subprocess_flush_pending = True
713
726
# We can not rely on self._io_loop.call_later from a subprocess
714
727
self .pub_thread .schedule (self ._flush )
0 commit comments