-1

Summarize the problem


I have a flask server (with endpoints and socket events) and a discord bot, both work independently, I want to run them on parallel so I can trigger functions of the bot from a flask endpoint.

Describe what you have tried


For context, this is an example of the endpoint:

@app.route("/submit", methods=["POST"])
async def submit():
    data = request.json
    userid = int(os.getenv("USER_ID"))
    message = f"```Title: {data['title']}\nMessage: {data['message']}```"
    await send_dm(userid, message)
    return data

Where send_dm in its own package looks like this

# notice that this is not a method of a function
# nor a decorated function with properties from the discord library
# it just uses an intance of the commands.Bot class
async def send_dm(userid: int, message: str):
    user = await bot.fetch_user(userid)
    channel = await user.create_dm()
    await channel.send(message)

So to run them on parallel and be able to communicate them with each other I tried:

Attempt 1: multiprocessing module

def main():
    executor = ProcessPoolExecutor(2)
    loop = asyncio.new_event_loop()

    loop.run_in_executor(executor, start_bot)
    loop.run_in_executor(executor, start_server)

    loop.run_forever()

if __name__ == "__main__":
    run()

When the function on the endpoint mentioned executes I get the following error AttributeError: '_MissingSentinel' object has no attribute 'is_set' on concurrent tasks

Attempt 2: threading module

# make them aware of each other
bot.flask_app = app
app.bot = bot
async def main():
    task = threading.Thread(target=start_bot)
    task.start()
    start_server()

if __name__ == "__main__":
    asyncio.run(main())

This approach brings two issues:

  • First is that to run start_bot I use .start() method instead of .run() because according to the this example .run() created its own event pool which would make it unreachable by other processes, and .start() is an async function, so when running this I get the error: RuntimeError: You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly.
  • Second is that even using the run.() function then the same issue arises when executing mentioned endpoint.

Attempt 3: asyncio module

def main():
    executor = ProcessPoolExecutor(2)
    loop = asyncio.new_event_loop()
    boo = loop.run_in_executor(executor, start_bot)
    baa = loop.run_in_executor(executor, start_server)

    loop.run_forever()

if __name__ == "__main__":
    main()

This time I actually get the execute both processes but still cannot call the function I want from the flask endpoint.

I also tried

await asyncio.gather([start_server(), start_bot()])

But same issue as Attempt 2, and I already upgraded the flask[async] module so that is not the issue anymore.

Show some code


To reproduce what I have right now you can either check the full repo here that has only 4 files or this sample should be enough to reproduce.

from server import socketio, app
from bot import bot
from dotenv import load_dotenv
import os
import asyncio
import threading

env_path = os.path.dirname(__file__) + "/.env"
load_dotenv(env_path)

def start_server():
    socketio.run(app)

def start_bot():
    token = os.getenv("BOT_TOKEN")
    bot.run(token)

async def main():
    # this doesn't achieve what I want and is the main part of the problem
    start_server()
    start_bot()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("Program exited")

Did I miss something?, point it out in the comments and I will add it in the edit.

Carepollo
  • 37
  • 1
  • 6

1 Answers1

0

This is what IPC was invented for. There's a dpy helper library that you can use: https://github.com/MiroslavRosenov/better-ipc

Both processes have nothing to do with each other, so you should launch them separately instead of hacking it together in one main.

stijndcl
  • 5,294
  • 1
  • 9
  • 23