Skip to content

Commit

Permalink
Fix await blocked by context exit
Browse files Browse the repository at this point in the history
There is a blocking issue caused by an await call inside a Promise Context.
It was easily fixed by draining the context queue on accessing the future.

Please check the test_await_in_context test in tests/test_awaitable_35.py to see an example showcasing the issue.
  • Loading branch information
syrusakbary committed Apr 25, 2017
1 parent 6474f80 commit 26488d5
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 1 deletion.
8 changes: 7 additions & 1 deletion promise/promise.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,12 @@ def future(self):
return self._future

def __iter__(self):
return iterate_promise(self)
if self._trace:
# If we wait, we drain the queue of the
# callbacks waiting on the context exit
# so we avoid a blocking state
self._trace.drain_queue()
return iterate_promise(self._target())

__await__ = __iter__

Expand Down Expand Up @@ -643,6 +648,7 @@ def _try_convert_to_promise(cls, obj, context=None):

if iscoroutine(obj):
obj = ensure_future(obj)
_type = obj.__class__

if is_future_like(_type):
def executor(resolve, reject):
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
collect_ignore.append('test_awaitable.py')
if version_info[:2] < (3, 5):
collect_ignore.append('test_awaitable_35.py')
collect_ignore.append('test_dataloader_awaitable_35.py')
14 changes: 14 additions & 0 deletions tests/test_awaitable_35.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from asyncio import sleep, Future, wait, FIRST_COMPLETED
from pytest import mark
from promise.context import Context
from promise import Promise, is_thenable


Expand Down Expand Up @@ -31,3 +32,16 @@ async def test_promisify_future():
future = Future()
future.set_result(True)
assert await Promise.resolve(future)


@mark.asyncio
async def test_await_in_context():
async def inner():
with Context():
promise = Promise.resolve(True).then(lambda x: x)
return await promise

result = await inner()
assert result == True


44 changes: 44 additions & 0 deletions tests/test_dataloader_awaitable_35.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from pytest import mark
from promise import Promise
from promise.dataloader import DataLoader


def id_loader(**options):
load_calls = []

resolve = options.pop('resolve', Promise.resolve)

def fn(keys):
load_calls.append(keys)
return resolve(keys)

identity_loader = DataLoader(fn, **options)
return identity_loader, load_calls


@mark.asyncio
async def test_await_dataloader():
identity_loader, load_calls = id_loader()
async def load_one_then_two(identity_loader):
one = identity_loader.load('load1')
two = identity_loader.load('load2')
return await Promise.all([one, two])

result = await load_one_then_two(identity_loader)
assert result == ['load1', 'load2']
assert load_calls == [['load1'], ['load2']]


@mark.asyncio
async def test_await_dataloader_safe_promise():
identity_loader, load_calls = id_loader()

@Promise.safe
async def load_one_then_two(identity_loader):
one = identity_loader.load('load1')
two = identity_loader.load('load2')
return await Promise.all([one, two])

result = await load_one_then_two(identity_loader)
assert result == ['load1', 'load2']
assert load_calls == [['load1'], ['load2']]

0 comments on commit 26488d5

Please sign in to comment.