Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions sanic/mixins/startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1206,14 +1206,18 @@ def serve(

@staticmethod
def _get_process_states(worker_state) -> list[str]:
return [
state
for s in worker_state.values()
if (
(state := s.get("state"))
and state not in ("TERMINATED", "FAILED", "COMPLETED", "NONE")
)
]
try:
return [
state
for s in worker_state.values()
if (
(state := s.get("state"))
and state
not in ("TERMINATED", "FAILED", "COMPLETED", "NONE")
)
]
Comment thread
ahopkins marked this conversation as resolved.
except (BrokenPipeError, ConnectionResetError, EOFError):
return []

@classmethod
def serve_single(cls, primary: Sanic | None = None) -> None:
Expand Down
44 changes: 41 additions & 3 deletions sanic/server/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,34 @@ def _setup_system_signals(
)


def _run_shutdown_coro(loop, coro):
"""Run a shutdown coroutine, handling the case where the loop was stopped.

When loop.stop() is called during run_forever(), asyncio sets an internal
flag that causes the first run_until_complete() to fail. For standard
asyncio, we clear the _stopping flag. For uvloop (which doesn't expose
this flag), the first attempt may fail but subsequent attempts succeed.
"""
# Clear asyncio's stopped state if accessible
if hasattr(loop, "_stopping"):
loop._stopping = False

try:
loop.run_until_complete(coro())
except (RuntimeError, KeyboardInterrupt):
# RuntimeError: loop was stopped (uvloop behavior)
# KeyboardInterrupt: signal arrived during select (asyncio behavior)
# Try once more - this handles uvloop's behavior where the first
# run_until_complete after stop() fails but subsequent calls succeed.
if hasattr(loop, "_stopping"):
loop._stopping = False
try:
loop.run_until_complete(coro())
except (RuntimeError, KeyboardInterrupt):
# If it still fails, the loop is truly unusable
pass
Comment thread
ahopkins marked this conversation as resolved.


def _run_server_forever(loop, before_stop, after_stop, cleanup, unix):
pid = os.getpid()
try:
Expand All @@ -184,12 +212,19 @@ def _run_server_forever(loop, before_stop, after_stop, cleanup, unix):
finally:
server_logger.info("Stopping worker [%s]", pid)

loop.run_until_complete(before_stop())
for _signal in [SIGINT, SIGTERM]:
try:
loop.remove_signal_handler(_signal)
except (NotImplementedError, OSError):
Comment thread
ahopkins marked this conversation as resolved.
pass

_run_shutdown_coro(loop, before_stop)

if cleanup:
cleanup()

loop.run_until_complete(after_stop())
_run_shutdown_coro(loop, after_stop)

remove_unix_socket(unix)
loop.close()
server_logger.info("Worker complete [%s]", pid)
Expand Down Expand Up @@ -296,7 +331,10 @@ def _cleanup():
else:
conn.abort()

app.set_serving(False)
try:
app.set_serving(False)
except (BrokenPipeError, ConnectionResetError, EOFError):
Comment thread
ahopkins marked this conversation as resolved.
pass
Comment thread
ahopkins marked this conversation as resolved.

_setup_system_signals(app, run_multiple, register_sys_signals, loop)
loop.run_until_complete(app._server_event("init", "after"))
Expand Down
12 changes: 10 additions & 2 deletions sanic/worker/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ def exit(self):
if not self.is_alive():
try:
del self.worker_state[self.name]
except ConnectionRefusedError:
except (
BrokenPipeError,
ConnectionRefusedError,
ConnectionResetError,
EOFError,
):
logger.debug("Monitor process has already exited.")
except KeyError:
logger.debug("Could not find worker state to delete.")
Expand All @@ -108,7 +113,10 @@ def terminate(self):
self.name,
self.pid,
)
self.set_state(ProcessState.TERMINATED, force=True)
try:
self.set_state(ProcessState.TERMINATED, force=True)
except (BrokenPipeError, ConnectionResetError, EOFError):
pass
Comment thread
ahopkins marked this conversation as resolved.
Comment thread
ahopkins marked this conversation as resolved.
try:
os.kill(self.pid, SIGINT)
except (KeyError, AttributeError, ProcessLookupError):
Expand Down
Loading