0

The following code snippet has two coroutines each for server and client. The client coroutine has a logic to break the while loop after 10 seconds and server should stop after 15 seconds.

When I run the script this doesn't stop, ideally, it should stop after 15 seconds but this is not happening.

import asyncio
import time
import zmq
import zmq.asyncio

zmq.asyncio.install()

ctx = zmq.asyncio.Context()


server_socket = ctx.socket(zmq.REP)
client_socket = ctx.socket(zmq.REQ)

server_socket.bind("tcp://127.0.0.1:8899")
client_socket.connect("tcp://127.0.0.1:8899")

t0 = time.time()


@asyncio.coroutine
def server_coroutine():

    while True:
        msg = yield from server_socket.recv_string()
        print(msg)
        msg = "Server:: {}".format(msg)
        yield from server_socket.send_string(msg)
        t1 = time.time()
        elapsed_time = t1 - t0
        # print('elapsed time is {}'.format(elapsed_time))
        if elapsed_time > 15:
            print("Breaking Server loop")
            break

@asyncio.coroutine
def client_coroutine():
    counter = 0
    while True:
        yield from asyncio.sleep(2)
        msg = 'Message: {}'.format(counter)
        yield from client_socket.send_string(msg)
        res = yield from client_socket.recv_string()
        print(res)
        t1 = time.time()
        elapsed_time = t1 - t0
        print('elapsed time is {}'.format(elapsed_time))
        if elapsed_time > 10:
            print("Breaking Client loop")
            break
        counter += 1


if __name__ == '__main__':

    loop = asyncio.get_event_loop()

    loop.run_until_complete(asyncio.gather(
        asyncio.ensure_future(server_coroutine()),
        asyncio.ensure_future(client_coroutine())
    ))
Chris Seymour
  • 83,387
  • 30
  • 160
  • 202
Jayendra Parmar
  • 702
  • 12
  • 30

1 Answers1

1

If you run code you will see something like this:

Server:: Message: 4
elapsed time is 10.022311687469482
Breaking Client loop

ok, client_coroutine finished successfully, but what state of server_coroutine at this moment? It stuck at this line msg = yield from server_socket.recv_string() waiting for possibility to recive string from server_socket, but it won't happen since there's already no client to send it! And since your event loop runs until both coroutines done it would run forever.

Here's the simplest fix:

@asyncio.coroutine
def server_coroutine():
    while True:
        msg = yield from server_socket.recv_string()
        if msg == 'CLOSE':  # LOOK HERE 1
            break
        print(msg)
        msg = "Server:: {}".format(msg)
        yield from server_socket.send_string(msg)
        t1 = time.time()
        elapsed_time = t1 - t0
        # print('elapsed time is {}'.format(elapsed_time))
        if elapsed_time > 15:
            print("Breaking Server loop")
            break

@asyncio.coroutine
def client_coroutine():
    counter = 0
    while True:
        yield from asyncio.sleep(2)
        msg = 'Message: {}'.format(counter)
        yield from client_socket.send_string(msg)
        res = yield from client_socket.recv_string()
        print(res)
        t1 = time.time()
        elapsed_time = t1 - t0
        print('elapsed time is {}'.format(elapsed_time))
        if elapsed_time > 10:
            print("Breaking Client loop")
            yield from client_socket.send_string('CLOSE')  # LOOK HERE 2
            break
        counter += 1

Note, this fix is only to demonstrate issue and one possible way to solve it.

In real life I think you would want to do something different: probably, set timeouts to you coroutines to guarantee they won't stuck forever if client/server stops responding.

Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • Thanks, Mikhail is there a way for a large application with many coroutine, where event-loop is waiting/ stuck? – Jayendra Parmar Dec 30 '17 at 01:14
  • @bruce_wayne you should always have timeouts for your operations. In asyncio it's not a big problem: timeouted task can be [cancelled with standard mechanism](https://stackoverflow.com/a/43810272/1113207). Most convenient way to achieve it is to use [asyncio.wait_for](https://docs.python.org/3/library/asyncio-task.html#asyncio.wait_for) coroutine or nice third-party [async-timeout](https://pypi.python.org/pypi/async_timeout) context manager (you can place multiple operations inside it). – Mikhail Gerasimov Dec 30 '17 at 09:40