Skip to content

Commit 1687f8c

Browse files
committed
Fix ENOTSOCK traceback when iopub socket closes during shutdown
Likely fix nbclient downstream test. After a control handler completes, process_control unconditionally publishes an "idle" status. During shutdown this races with IOPubThread.close(): the IO thread has been joined, so _really_send runs on the caller's thread while close() runs on another, and the `if self.closed` check is TOCTOU with the subsequent send_multipart. Catch the resulting ZMQError(ENOTSOCK) (and AttributeError if self.socket has flipped to None) in _really_send and log at debug level instead of letting tornado log an uncaught-callback traceback.
1 parent 4abdde1 commit 1687f8c

1 file changed

Lines changed: 17 additions & 2 deletions

File tree

ipykernel/iostream.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import atexit
88
import contextvars
99
import io
10+
import logging
1011
import os
1112
import sys
1213
import threading
@@ -33,6 +34,8 @@
3334

3435
PIPE_BUFFER_SIZE = 1000
3536

37+
logger = logging.getLogger(__name__)
38+
3639
# -----------------------------------------------------------------------------
3740
# IO classes
3841
# -----------------------------------------------------------------------------
@@ -355,8 +358,20 @@ def _really_send(self, msg, *args, **kwargs):
355358
mp_mode = self._check_mp_mode()
356359

357360
if mp_mode != CHILD:
358-
# we are master, do a regular send
359-
self.socket.send_multipart(msg, *args, **kwargs)
361+
# we are master, do a regular send.
362+
# The closed check above is racy: once the IO thread has been
363+
# joined, _really_send runs on the caller's thread while close()
364+
# may run concurrently on another, so the socket can be closed
365+
# (or become None) between the check and the send. Swallow that
366+
# specific case rather than logging a noisy traceback during
367+
# shutdown.
368+
try:
369+
self.socket.send_multipart(msg, *args, **kwargs)
370+
except (AttributeError, zmq.error.ZMQError) as e:
371+
if isinstance(e, AttributeError) or e.errno == zmq.ENOTSOCK:
372+
logger.debug("IOPub socket closed during send (likely shutdown): %s", e)
373+
return
374+
raise
360375
else:
361376
# we are a child, pipe to master
362377
# new context/socket for every pipe-out

0 commit comments

Comments
 (0)