Matt
Matt

Reputation: 5684

How do I implement synchronously waiting for a callback in Python 3 asyncio?

Due to some unusual constraints, I need to synchronously wait for a callback URL from another service before returning a response. Currently I have something resembling:

ROUTE = '/operation'
async def post(self):
    ##SOME OPERATIONS##
    post_body = { 'callbackUrl' : 'myservice.com/cb' }
    response = await other_service.post('/endpoint')
    global my_return_value
    my_return_value = None
    while not my_return_value:
        pass
    return self.make_response(my_return_value)

Then I have a way to handle the callback URL something like:

ROUTE = '/cb'
async def post(self):
    ##OPERATIONS###
    global my_return_value
    my_return_value = some_value
    return web.json_response()

The problem with this code is that it forever gets trapped in that while loop forever even if the callback URL gets invoked. I suspect there is a better way to do this, but I'm not sure how to go about it nor how to google for it. Any ideas?

Thanks in advance!

Upvotes: 0

Views: 1497

Answers (1)

Claude
Claude

Reputation: 9980

Just a quick scan, but I think you're trapped in

while not my_return_value:
    pass

Python will be trapped there and not have time to deal with the callback function. What you need is

while not my_return_value:
    await asyncio.sleep(1)

(or you can even do an asyncio.sleep(0) if you don't want the millisecond delay).

An even nicer way would be (and now I'm writing from memory, no guarantees...):

my_return_value = asyncio.get_event_loop().create_future()
await my_return_value
return self.make_response(my_return_value.result())


async def post(self):
    ##OPERATIONS###
    my_return_value.set_result(some_value)
    return web.json_response()

Note however that either way will break very much if there is ever more than one concurrent use of this system. It feels very fragile! Maybe even better:

ROUTE = '/operation'
my_return_value = {}

async def post(self):
    ##SOME OPERATIONS##
    token = "%016x" % random.SystemRandom().randint(0, 2**128)
    post_body = { 'callbackUrl' : 'myservice.com/cb?token='+token }
    response = await other_service.post('/endpoint')
    my_return_value[token] = asyncio.get_event_loop().create_future()
    await my_return_value[token]
    result = my_return_value[token].result()
    del my_return_value[token]
    return self.make_response(result)

async def post(self):
    ##OPERATIONS###
    token = self.arguments("token")
    my_return_value[token].set_result(some_value)
    return web.json_response()

Now cherry on top would be a timer that would cancel the future after a timeout and clean up the entry in my_return_value after a while if the callback does not happen. Also, if you're going with my last suggestion, don't call it my_return_value but something like callback_future_by_token...

Upvotes: 1

Related Questions