sanic-org / sanic

Accelerate your web app development | Build fast. Run fast.

Home Page:https://sanic.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

main_process_stop runs BEFORE before_server_stop when interrupted with SIGINT

arsentyev opened this issue · comments

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

The lifecycle diagram in the docs shows that main_process_stop runs AFTER all before_server_stop listeners, but it actually runs BEFORE, at least when the main process was interrupted with SIGINT.

Code snippet

from sanic import Sanic
from sanic.response import json

app = Sanic("my-hello-world-app")

@app.route('/')
async def test(request):
    return json({'hello': 'world'})

@app.main_process_stop
def main_process_stop(app, loop):
    print ("main_process_stop")

@app.before_server_stop
def before_server_stop(app, loop):
    print ("before_server_stop")


if __name__ == '__main__':
    app.run(fast=True)

Expected Behavior

[2023-08-25 11:04:24 +0400] [5017] [INFO] Received signal SIGINT. Shutting down.
...
before_server_stop
...
before_server_stop
main_process_stop

How do you run Sanic?

As a script (app.run or Sanic.serve)

Operating System

MacOS

Sanic Version

Sanic 23.6.0; Routing 23.6.0

Additional context

The actual output:

[2023-08-25 11:04:24 +0400] [5017] [INFO] Received signal SIGINT. Shutting down.
[2023-08-25 11:04:24 +0400] [5017] [INFO] Server Stopped
main_process_stop
[2023-08-25 11:04:24 +0400] [5029] [INFO] Stopping worker [5029]
...
[2023-08-25 11:04:24 +0400] [5024] [INFO] Stopping worker [5024]
before_server_stop
...
before_server_stop

They are run in different processes and not interdependent. You cannot rely upon the order of them as the OS is responsible for scheduling between them. The events are triggered and executed. You can rely upon the after_server_stop happening after before_server_stop as they are in the same process. Similarly, the reloader stop event also happens in it's own process.

This is neither a bug, nor an expected feature.

What are you trying to do that this would matter?

Thank you for explanation. I agree that worker processes are independent but dependent on the main process. Random execution order between workers and main process is not clear from your docs.

Your WorkManager.run seems to be trying to join to worker processes and wait for them to complete. But actual multiprocessing.Process.join is not called, at least, in case of SIGINT. It looks like a bug. And if you want to dive deeper: calling WorkManager.run should wait for worker processes to complete or not?

What are you trying to do to make it matter?

I'm just trying to clean up some resources shared by worker processes. To do this, I need to wait for them to complete (more precisely all request handlers in all processes). Is there a standard way to do this?

I have been playing with this, and I see what you mean now. And, I have what I think is a better solution in play. Will require some tests and cleaning it up, but the PR is here: #2811