Skip to content

Commit 9ee5fee

Browse files
committed
Fix crash closing already-closed socket. Refs #1596
``` error: uncaptured python exception, closing channel <supervisor.http.deferring_http_channel connected 127.0.0.1:58792 at 0x102d3a310 channel#: 0 requests:4> (<class 'OSError'>:[Errno 57] Socket is not connected [/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py|handle_read|89] [/Users/username/git/supervisor/supervisor/medusa/http_server.py|recv|528] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|recv|354] [/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py|handle_close|156] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|close|361]) error: uncaptured python exception, closing channel <supervisor.http.deferring_http_channel connected 127.0.0.1:58792 at 0x102d3a310 channel#: 0 requests:4> (<class 'OSError'>:[Errno 57] Socket is not connected [/Users/username/git/supervisor/supervisor/supervisord.py|runforever|218] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|handle_read_event|392] [/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py|handle_read|91] [/Users/username/git/supervisor/supervisor/medusa/http_server.py|handle_error|546] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|handle_error|422] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|close|361]) Traceback (most recent call last): File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 347, in recv self.handle_close() File "/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py", line 156, in handle_close self.close() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 361, in close self.socket.shutdown(socket.SHUT_RDWR) OSError: [Errno 57] Socket is not connected During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py", line 89, in handle_read data = self.recv (self.ac_in_buffer_size) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/username/git/supervisor/supervisor/medusa/http_server.py", line 528, in recv result = asynchat.async_chat.recv (self, buffer_size) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 354, in recv self.handle_close() File "/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py", line 156, in handle_close self.close() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 361, in close self.socket.shutdown(socket.SHUT_RDWR) OSError: [Errno 57] Socket is not connected During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/username/git/supervisor/supervisor/supervisord.py", line 218, in runforever dispatcher.handle_read_event() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 392, in handle_read_event self.handle_read() File "/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py", line 91, in handle_read self.handle_error() File "/Users/username/git/supervisor/supervisor/medusa/http_server.py", line 546, in handle_error asynchat.async_chat.handle_error (self) File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 422, in handle_error self.close() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 361, in close self.socket.shutdown(socket.SHUT_RDWR) OSError: [Errno 57] Socket is not connected During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/homebrew/bin/supervisord", line 33, in <module> sys.exit(load_entry_point('supervisor', 'console_scripts', 'supervisord')()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/username/git/supervisor/supervisor/supervisord.py", line 373, in main go(options) File "/Users/username/git/supervisor/supervisor/supervisord.py", line 383, in go d.main() File "/Users/username/git/supervisor/supervisor/supervisord.py", line 78, in main self.run() File "/Users/username/git/supervisor/supervisor/supervisord.py", line 94, in run self.runforever() File "/Users/username/git/supervisor/supervisor/supervisord.py", line 224, in runforever combined_map[fd].handle_error() File "/Users/username/git/supervisor/supervisor/medusa/http_server.py", line 546, in handle_error asynchat.async_chat.handle_error (self) File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 422, in handle_error self.close() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 361, in close self.socket.shutdown(socket.SHUT_RDWR) OSError: [Errno 57] Socket is not connected ```
1 parent 4c845a3 commit 9ee5fee

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

supervisor/medusa/asyncore_25.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,16 @@ def recv(self, buffer_size):
358358

359359
def close(self):
360360
self.del_channel()
361-
self.socket.shutdown(socket.SHUT_RDWR)
362-
self.socket.close()
361+
362+
try:
363+
self.socket.shutdown(socket.SHUT_RDWR)
364+
except socket.error:
365+
# must swallow exception from already-closed socket
366+
# (at least with Python 3.11.7 on macOS 14.2.1)
367+
pass
368+
369+
# does not raise if called on already-closed socket
370+
self.socket.close()
363371

364372
# cheap inheritance, used to pass all other attribute
365373
# references to the underlying socket object.
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[supervisord]
2+
loglevel=info ; log level; default info; others: debug,warn,trace
3+
logfile=/tmp/issue-1596.log ; main log file; default $CWD/supervisord.log
4+
pidfile=/tmp/issue-1596.pid ; supervisord pidfile; default supervisord.pid
5+
nodaemon=true ; start in foreground if true; default false
6+
identifier=from_config_file
7+
8+
[rpcinterface:supervisor]
9+
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
10+
11+
[unix_http_server]
12+
file=/tmp/issue-1596.sock ; the path to the socket file

supervisor/tests/test_end_to_end.py

+29
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,32 @@ def test_pull_request_1578_echo_supervisord_conf(self):
426426
echo_supervisord_conf = pexpect.spawn(sys.executable, args, encoding='utf-8')
427427
self.addCleanup(echo_supervisord_conf.kill, signal.SIGKILL)
428428
echo_supervisord_conf.expect_exact('Sample supervisor config file')
429+
430+
def test_issue_1596_asyncore_close_does_not_crash(self):
431+
"""If the socket is already closed when socket.shutdown(socket.SHUT_RDWR)
432+
is called in the close() method of an asyncore dispatcher, an exception
433+
will be raised (at least with Python 3.11.7 on macOS 14.2.1). If it is
434+
not caught in that method, supervisord will crash."""
435+
filename = resource_filename(__package__, 'fixtures/issue-1596.conf')
436+
args = ['-m', 'supervisor.supervisord', '-c', filename]
437+
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
438+
self.addCleanup(supervisord.kill, signal.SIGINT)
439+
supervisord.expect_exact('supervisord started with pid')
440+
441+
from supervisor.compat import xmlrpclib
442+
from supervisor.xmlrpc import SupervisorTransport
443+
444+
socket_url = 'unix:///tmp/issue-1596.sock'
445+
dummy_url = 'http://transport.ignores.host/RPC2'
446+
447+
# supervisord will crash after close() if it has the bug
448+
t1 = SupervisorTransport('', '', socket_url)
449+
s1 = xmlrpclib.ServerProxy(dummy_url, t1)
450+
s1.system.listMethods()
451+
t1.close()
452+
453+
# this call will only succeed if supervisord did not crash
454+
t2 = SupervisorTransport('', '', socket_url)
455+
s2 = xmlrpclib.ServerProxy(dummy_url, t2)
456+
s2.system.listMethods()
457+
t2.close()

0 commit comments

Comments
 (0)