Reputation: 5684
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
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