Let’s explore how a typical synchronous framework handles requests.

When a request arrives, the web server (WSGI server) assigns it to a worker, usually a thread or a process, which handles the request from start to finish.

# a typical synchronous framework routing
@app.route('/hello')
def hello():
    # start

    return 'Hello, World!'
    # finish

This works fine for short requests, where processing times are in milliseconds, and many requests can be handled with relatively few threads.

However, this model struggles with long-lived connections like WebSocket or SSE, as each thread remains occupied for the duration of the connection.

For example, if you have 5 worker threads and all are tied up with long-lived connections, the 6th request must wait until a thread is free.

This problem can be replicated on httpout with wait because httpout is also threaded by nature.

# hello.py (httpout's file-based routing)
# start
import asyncio


async def main():
    # simulate a long-lived connection
    await asyncio.sleep(10)
    print('Done!')


wait(main())
# finish

But not if you use run instead of wait:

# hello.py (httpout's file-based routing)
# start
import asyncio


async def main():
    # simulate a long-lived connection
    await asyncio.sleep(10)
    print('Done!')


run(main())
print('OK')
# finish (worker thread), main() will still run (on the main thread)
# should print the 'OK' first

With run() you can immediately leave the worker thread while it is running main().

This essentially switches the style from threaded to asynchronous to handle long-lived connections - which should explain why the hybrid async-sync in httpout is so powerful in this case.

Note that this does not mean you should use run in all cases, simply use wait if you want to wait.