Skip to content

Commit

Permalink
Fix crash closing already-closed socket. Refs #1596
Browse files Browse the repository at this point in the history
```
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
```
  • Loading branch information
mnaberez committed Mar 2, 2024
1 parent 4c845a3 commit 9ee5fee
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 2 deletions.
12 changes: 10 additions & 2 deletions supervisor/medusa/asyncore_25.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,16 @@ def recv(self, buffer_size):

def close(self):
self.del_channel()
self.socket.shutdown(socket.SHUT_RDWR)
self.socket.close()

try:
self.socket.shutdown(socket.SHUT_RDWR)
except socket.error:
# must swallow exception from already-closed socket
# (at least with Python 3.11.7 on macOS 14.2.1)
pass

# does not raise if called on already-closed socket
self.socket.close()

# cheap inheritance, used to pass all other attribute
# references to the underlying socket object.
Expand Down
12 changes: 12 additions & 0 deletions supervisor/tests/fixtures/issue-1596.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[supervisord]
loglevel=info ; log level; default info; others: debug,warn,trace
logfile=/tmp/issue-1596.log ; main log file; default $CWD/supervisord.log
pidfile=/tmp/issue-1596.pid ; supervisord pidfile; default supervisord.pid
nodaemon=true ; start in foreground if true; default false
identifier=from_config_file

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[unix_http_server]
file=/tmp/issue-1596.sock ; the path to the socket file
29 changes: 29 additions & 0 deletions supervisor/tests/test_end_to_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,32 @@ def test_pull_request_1578_echo_supervisord_conf(self):
echo_supervisord_conf = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(echo_supervisord_conf.kill, signal.SIGKILL)
echo_supervisord_conf.expect_exact('Sample supervisor config file')

def test_issue_1596_asyncore_close_does_not_crash(self):
"""If the socket is already closed when socket.shutdown(socket.SHUT_RDWR)
is called in the close() method of an asyncore dispatcher, an exception
will be raised (at least with Python 3.11.7 on macOS 14.2.1). If it is
not caught in that method, supervisord will crash."""
filename = resource_filename(__package__, 'fixtures/issue-1596.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
supervisord.expect_exact('supervisord started with pid')

from supervisor.compat import xmlrpclib
from supervisor.xmlrpc import SupervisorTransport

socket_url = 'unix:///tmp/issue-1596.sock'
dummy_url = 'http://transport.ignores.host/RPC2'

# supervisord will crash after close() if it has the bug
t1 = SupervisorTransport('', '', socket_url)
s1 = xmlrpclib.ServerProxy(dummy_url, t1)
s1.system.listMethods()
t1.close()

# this call will only succeed if supervisord did not crash
t2 = SupervisorTransport('', '', socket_url)
s2 = xmlrpclib.ServerProxy(dummy_url, t2)
s2.system.listMethods()
t2.close()

0 comments on commit 9ee5fee

Please sign in to comment.