2

I have my servers classes inherited from BaseServer:

class BaseServer(object):

    def __init__(self, host, port):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        self.instance = asyncio.start_server(self.handle_connection, host = host, port = port)

    async def handle_connection(self, reader: StreamReader, writer: StreamWriter):
        pass

    def start(self):
        # wrapping coroutine into ensure_future to allow it to call from call_soon
        # wrapping into lambda to make it callable
        callback = asyncio.ensure_future(self.instance)
        self.loop.call_soon(lambda: callback)
        self.loop.run_forever()
        self.loop.close()

    def stop(self):
        self.loop.call_soon_threadsafe(self.loop.stop)

    @staticmethod
    def get_instance():
        return BaseServer(None, None)

I need two servers running in own thread to processing requests in parallel. But when I trying to run them as needed, only first server is running. Below how I run them:

if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    async def run():
        pool = ThreadPoolExecutor(max_workers=cpu_count())

        await loop.run_in_executor(pool, Server1.get_instance().start)
        await loop.run_in_executor(pool, Server2.get_instance().start)

    loop.run_until_complete(run())
  1. What am I doing wrong? How to run each server in own thread?
  2. When asyncio.set_event_loop is calling from def __init__ I got next error:

RuntimeError: There is no current event loop in thread 'Thread-1'.

But if I remove asyncio.set_event_loop from def __init__ and move it to def start error disappears. Why this happened?

2
  • 2
    You don't really get anything by running the servers in different threads. Running several servers in the same loop do not prevent the requests to be processed concurrently. Commented Nov 4, 2018 at 15:35
  • @Vincent but running two servers via run_until_complete in the same loop blocks loop for one of servers to process request to another, isn't it ? How to run two servers correctly ? Commented Nov 4, 2018 at 16:30

1 Answer 1

5

Following up on OP's comment:

But running two servers via run_until_complete in the same loop blocks loop for one of servers to process request to another, isn't it ? How to run two servers correctly ?

Here is a modified version of the TCP server example from the asyncio documentation for python 3.5:

# Start server 1
coro1 = asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop)
server1 = loop.run_until_complete(coro1)
print('Serving 1 on {}'.format(server1.sockets[0].getsockname()))

# Start server 2
coro2 = asyncio.start_server(handle_echo, '127.0.0.1', 8889, loop=loop)
server2 = loop.run_until_complete(coro2)
print('Serving 2 on {}'.format(server2.sockets[0].getsockname()))

# Serve requests until Ctrl+C is pressed
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

# Close the servers
server1.close()
loop.run_until_complete(server1.wait_closed())
server2.close()
loop.run_until_complete(server2.wait_closed())

# Close the loop
loop.close()

Note that with python 3.7 additions to asyncio, it would look much nicer:

async def main():
    server1 = await asyncio.start_server(
        handle_echo, '127.0.0.1', 8888)

    addr1 = server1.sockets[0].getsockname()
    print(f'Serving 1 on {addr1}')

    server2 = await asyncio.start_server(
        handle_echo, '127.0.0.1', 8889)

    addr2 = server2.sockets[0].getsockname()
    print(f'Serving 2 on {addr2}')

    async with server1, server2:
        await asyncio.gather(
            server1.serve_forever(), server2.serve_forever())

asyncio.run(main())
Sign up to request clarification or add additional context in comments.

5 Comments

Got it! Thanks a lot
Nice upgrade to the py3.7 idiom. You might want the last line to be something like await asyncio.gather(server1.serve_forever(), server2.serve_forever()) for maximum correctness.
@user4815162342 I was actually wondering about this, why do you think the gather approach is more correct?
gather captures the intention of waiting until both servers stop serving, whereas await server1.serve_forever() makes it look like we're waiting for server1 and don't care about server2. Another option is to use asyncio.wait with FIRST_COMPLETED and cancel the other one, but that's too much typing. In practice there is very little difference along those, since neither server is expected to stop serving in isolation.
Can this be done also with the web app? (aiohttp.web) instead of a simple tcp server? I cannot figure out the snipplet so it works with gather.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.