Infinite task using `loop.create_task` in `main_process_start` is hanging.
lxdlam opened this issue · comments
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
I've using Sanic with discord.py to start both a discord bot and a Sanic server. I'm initializing the discord bot in a function like:
@app.main_process_start
async def start(app: Sanic, loop: AbstractEventLoop) -> None:
loop.create_task(bot.start(CLIENT_TOKEN))
The bot.start
call is a async function which is expected to enter a infinite loop. When I start my server through sanic main:app
, the discord server will never go live, and after sending Ctrl-C, it seems the coroutine is stucked at making connection and prints a error message Fatal error on transport TCPTransport
.
If I change the decorator into @app.before_server_start
, it works with no issue, but I want to make only one global instance of the client, instead of making one each worker. Also, I think the behaviour is weird since I have digged into the code and seen nothing special.
Code snippet
No response
Expected Behavior
No response
How do you run Sanic?
Sanic CLI
Operating System
Fedora 38 - Linux 6.3
Sanic Version
v23.3.0
Additional context
No response
Can you please share the link to your discord bot's code so we can understand what it's trying to do on startup?
Can you please share the link to your discord bot's code so we can understand what it's trying to do on startup?
Sure. It's the minimal snippet:
- bot.py
from discord.ext import commands # discord.py, https://discordpy.readthedocs.io/en/stable/
class Bot(commands.Bot):
def __init__(self):
# ...
async def on_ready(self):
logger.info(f"Discord bot connected. Logged in as {self.user} ({self.user.id})")
bot = Bot()
- routes.py
import sanic # for typing stuff
from sanic import HTTPResponse
app = sanic.Sanic("sanic_server")
@app.route("/")
async def index(_request: sanic.Request) -> HTTPResponse:
return HTTPResponse("<p>Hello World!</p>")
- main.py
from os import environ
from sanic import Sanic
from asyncio import AbstractEventLoop
import routes
from bot import bot
CLIENT_TOKEN = environ.get("DISCORD_TOKEN")
app = Sanic.get_app("sanic_server")
@app.main_process_start
async def start(app: Sanic, loop: AbstractEventLoop) -> None:
loop.create_task(bot.start(CLIENT_TOKEN))
I believe the main process doesn't run in async mode. This test case shows that the task is only executed for one round (until the await
), thus this prints "Running..." only once. If used in server process, it prints the message each second.
import asyncio
from sanic import Sanic
app = Sanic("sanic_server")
async def worker():
while True:
print("Running...")
await asyncio.sleep(1)
@app.main_process_start
async def start(app, loop) -> None:
loop.create_task(worker())
@app.route("/")
async def test(request):
...
As a workaround, spawn a process from main_process_ready
and join it in main_process_stop
. If you need await
, use asyncio.run
within your process for your very own event loop. The main process is not quite designed to run extra tasks by itself.
I see the reason and the workaround is acceptable. Thanks.
See also: #2775
I would not suggest running it in the main process. You should probably run it in its own process.
https://sanic.dev/en/guide/deployment/manager.html#running-custom-processes