sanic-org / sanic

Accelerate your web app development | Build fast. Run fast.
https://sanic.dev
MIT License
18.11k stars 1.55k forks source link

Unable to run two apps with Application multi-serve in dev mode #2820

Open gmrmn opened 1 year ago

gmrmn commented 1 year ago

Is there an existing issue for this?

Describe the bug

Hello, I am trying to run Sanic as a script with two applications on board via Sanic.serve(). Applications are prepared to serve on different ports and development mode is enabled for both of them (see the snippet and additional context below). When I fire the script and hit the endpoint of the second app I get Server Error:

curl localhost:80/hello
world%
curl localhost:81/metrics
curl: (52) Empty reply from server

The log:

python test_two.py
[2023-09-08 11:42:52 +0300] [5436] [DEBUG] Creating multiprocessing context using 'spawn'
[2023-09-08 11:42:52 +0300] [5436] [DEBUG] Starting a process: Sanic-Server-0-0
[2023-09-08 11:42:52 +0300] [5436] [DEBUG] Starting a process: Sanic-Reloader-0
[2023-09-08 11:42:53 +0300] [5441] [DEBUG] Process ack: Sanic-Server-0-0 [5441]
[2023-09-08 11:42:53 +0300] [5441] [INFO] Starting worker [5441]
[2023-09-08 11:43:06 +0300] [5441] [ERROR] protocol.connection_task uncaught
Traceback (most recent call last):
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 99, in get
    group, param_basket = self.find_route(
  File "", line 22, in find_route
sanic_routing.exceptions.NotFound: Not Found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/server/protocols/http_protocol.py", line 148, in connection_task
    await self.app.dispatch(
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 208, in dispatch
    return await dispatch
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 141, in _dispatch
    raise e
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 137, in _dispatch
    group, handlers, params = self.get(event, condition=condition)
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 112, in get
    raise NotFound(message % tuple(terms))
sanic_routing.exceptions.NotFound: Could not find signal http.lifecycle.begin
Task exception was never retrieved
future: <Task finished name='Task-7' coro=<HttpProtocol.connection_task() done, defined at /Users/test/venv/lib/python3.10/site-packages/sanic/server/protocols/http_protocol.py:139> exception=NotFound('Could not find signal http.lifecycle.complete') created at /Users/test/venv/lib/python3.10/site-packages/sanic/server/protocols/http_protocol.py:265>
source_traceback: Object created at (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/local/Cellar/python@3.10/3.10.12_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/multiprocessing/spawn.py", line 116, in spawn_main
    exitcode = _main(fd, parent_sentinel)
  File "/usr/local/Cellar/python@3.10/3.10.12_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/multiprocessing/spawn.py", line 129, in _main
    return self._bootstrap(parent_sentinel)
  File "/usr/local/Cellar/python@3.10/3.10.12_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/multiprocessing/process.py", line 314, in _bootstrap
    self.run()
  File "/usr/local/Cellar/python@3.10/3.10.12_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/worker/serve.py", line 117, in worker_serve
    return _serve_http_1(
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/server/runners.py", line 264, in _serve_http_1
    _run_server_forever(
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/server/runners.py", line 145, in _run_server_forever
    loop.run_forever()
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/server/protocols/http_protocol.py", line 265, in connection_made
    self._task = self.loop.create_task(self.connection_task())
Traceback (most recent call last):
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 99, in get
    group, param_basket = self.find_route(
  File "", line 22, in find_route
sanic_routing.exceptions.NotFound: Not Found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/server/protocols/http_protocol.py", line 177, in connection_task
    await self.app.dispatch(
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 208, in dispatch
    return await dispatch
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 141, in _dispatch
    raise e
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 137, in _dispatch
    group, handlers, params = self.get(event, condition=condition)
  File "/Users/test/venv/lib/python3.10/site-packages/sanic/signals.py", line 112, in get
    raise NotFound(message % tuple(terms))
sanic_routing.exceptions.NotFound: Could not find signal http.lifecycle.complete

Code snippet

# test/test_two.py
from sanic import Sanic, response

app1 = Sanic("app1")

@app1.get("/hello")
async def get(_):
    return response.text("world")

app2 = Sanic("app2")

@app2.get("/metrics")
async def get(_):
    return response.text("metrics")

if __name__ == "__main__":
    app1.prepare("0.0.0.0", port=80, motd=False, dev=True)
    app2.prepare("0.0.0.0", port=81, motd=False, dev=True)
    Sanic.serve(primary=app1)

Expected Behavior

curl http://localhost:80/hello
world
curl http://localhost:81/metrics
metrics

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 problem appears only when dev=True applied to the first or both apps. These configurations cause exceptions:

app1.prepare("0.0.0.0", port=80, motd=False, dev=True)
app2.prepare("0.0.0.0", port=81, motd=False, dev=True)
# or 
app1.prepare("0.0.0.0", port=80, motd=False, dev=True)
app2.prepare("0.0.0.0", port=81, motd=False)

These don't (works fine):

app1.prepare("0.0.0.0", port=80, motd=False)
app2.prepare("0.0.0.0", port=81, motd=False, dev=True)
# or
app1.prepare("0.0.0.0", port=80, motd=False)
app2.prepare("0.0.0.0", port=81, motd=False)
ahopkins commented 1 year ago

Confirmed the problem. I believe the solution is to give a secondary app startup method, and in that set signal_router.allow_fail_builtin = False.