Skip to content

Commit ff8f34f

Browse files
author
Michael Elsdörfer
authored
Merge branch 'master' into asyncgen
2 parents e1feef8 + 3a06dcf commit ff8f34f

11 files changed

Lines changed: 172 additions & 36 deletions

File tree

debian/changelog

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
trio-asyncio (0.7.0-1) unstable; urgency=medium
2+
3+
* Enable Python 3.5 compatibility
4+
* allow parallel and nested native asyncio loops
5+
* minor bug fixes
6+
7+
-- Matthias Urlichs <matthias@urlichs.de> Tue, 27 Mar 2018 17:03:35 +0200
8+
19
trio-asyncio (0.6.0-1) unstable; urgency=medium
210

311
* Export loop.synchronize()

tests/aiotest/test_timer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ def world(loop):
3737
result.append("World")
3838
loop.stop()
3939

40-
loop.call_later(0.001, hello)
41-
loop.call_later(0.050, world, loop)
40+
loop.call_later(0.1, hello)
41+
loop.call_later(0.5, world, loop)
4242

43-
await trio.sleep(0.030)
43+
await trio.sleep(0.3)
4444
assert result == ["Hello"]
4545

4646
await loop.wait_stopped()

tests/conftest.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,15 @@ def pytest_pyfunc_call(pyfuncitem):
4848
pyfuncitem.obj = pytest.mark.trio(pyfuncitem.obj)
4949

5050

51-
asyncio.set_event_loop_policy(trio_asyncio.TrioPolicy())
51+
_old_policy = asyncio.get_event_loop_policy()
52+
_new_policy = trio_asyncio.TrioPolicy()
53+
asyncio.set_event_loop_policy(_new_policy)
54+
55+
56+
@pytest.fixture
57+
def old_policy():
58+
asyncio.set_event_loop_policy(_old_policy)
59+
try:
60+
yield _old_policy
61+
finally:
62+
asyncio.set_event_loop_policy(_new_policy)

tests/python/test_events.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,9 +1460,7 @@ def connect():
14601460
write_transport._pipe = None
14611461

14621462
@unittest.skipUnless(sys.platform != 'win32', "Don't support pipes for Windows")
1463-
# select, poll and kqueue don't support character devices (PTY) on Mac OS X
1464-
# older than 10.6 (Snow Leopard)
1465-
@support.requires_mac_ver(10, 6)
1463+
@unittest.skipIf(sys.platform == 'darwin', 'test hangs on MacOS')
14661464
# Issue #20495: The test hangs on FreeBSD 7.2 but pass on FreeBSD 9
14671465
@support.requires_freebsd_version(8)
14681466
def test_read_pty_output(self):
@@ -1600,9 +1598,7 @@ def reader(data):
16001598
self.assertEqual('CLOSED', proto.state)
16011599

16021600
@unittest.skipUnless(sys.platform != 'win32', "Don't support pipes for Windows")
1603-
# select, poll and kqueue don't support character devices (PTY) on Mac OS X
1604-
# older than 10.6 (Snow Leopard)
1605-
@support.requires_mac_ver(10, 6)
1601+
@unittest.skipIf(sys.platform == 'darwin', 'test may hang on MacOS')
16061602
def test_bidirectional_pty(self):
16071603
master, read_slave = os.openpty()
16081604
write_slave = os.dup(read_slave)

tests/test_concurrent.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import trio
2+
import trio_asyncio
3+
import asyncio
4+
import pytest
5+
6+
# Tests for concurrent or nested loops
7+
8+
9+
@pytest.mark.trio
10+
async def test_parallel():
11+
loops = [None, None]
12+
async with trio.open_nursery() as n:
13+
14+
async def gen_loop(i, task_status=trio.TASK_STATUS_IGNORED):
15+
task_status.started()
16+
async with trio_asyncio.open_loop() as loop:
17+
loops[i] = loop
18+
19+
assert not isinstance(asyncio._get_running_loop(), trio_asyncio.TrioEventLoop)
20+
await n.start(gen_loop, 0)
21+
await n.start(gen_loop, 1)
22+
23+
assert isinstance(loops[0], trio_asyncio.TrioEventLoop)
24+
assert isinstance(loops[1], trio_asyncio.TrioEventLoop)
25+
assert loops[0] is not loops[1]
26+
27+
28+
@pytest.mark.trio
29+
async def test_nested():
30+
loops = [None, None]
31+
async with trio.open_nursery() as n:
32+
33+
async def gen_loop(i, task_status=trio.TASK_STATUS_IGNORED):
34+
task_status.started()
35+
async with trio_asyncio.open_loop() as loop:
36+
loops[i] = loop
37+
if i > 0:
38+
await n.start(gen_loop, i - 1)
39+
40+
assert not isinstance(asyncio._get_running_loop(), trio_asyncio.TrioEventLoop)
41+
await n.start(gen_loop, 1)
42+
assert not isinstance(asyncio._get_running_loop(), trio_asyncio.TrioEventLoop)
43+
assert isinstance(loops[0], trio_asyncio.TrioEventLoop)
44+
assert isinstance(loops[1], trio_asyncio.TrioEventLoop)
45+
assert loops[0] is not loops[1]
46+
47+
48+
async def _test_same_task():
49+
loops = [None, None]
50+
assert isinstance(asyncio.get_event_loop_policy(), trio_asyncio.TrioPolicy)
51+
52+
def get_loop(i):
53+
loops[i] = (asyncio.get_event_loop(), asyncio.get_event_loop_policy())
54+
55+
async with trio.open_nursery():
56+
async with trio_asyncio.open_loop() as loop1:
57+
async with trio_asyncio.open_loop() as loop2:
58+
loop1.call_later(0.1, get_loop, 0)
59+
loop2.call_later(0.1, get_loop, 1)
60+
await trio.sleep(0.2)
61+
62+
assert isinstance(asyncio.get_event_loop_policy(), trio_asyncio.TrioPolicy)
63+
assert not isinstance(asyncio._get_running_loop(), trio_asyncio.TrioEventLoop)
64+
assert isinstance(loops[0][0], trio_asyncio.TrioEventLoop)
65+
assert isinstance(loops[1][0], trio_asyncio.TrioEventLoop)
66+
assert isinstance(loops[1][1], trio_asyncio.TrioPolicy)
67+
assert loops[0][0] is not loops[1][0]
68+
assert loops[0][1] is loops[1][1]
69+
assert loops[0][1] is asyncio.get_event_loop_policy()
70+
71+
72+
def test_same_task(old_policy):
73+
assert not isinstance(asyncio.get_event_loop_policy(), trio_asyncio.TrioPolicy)
74+
trio.run(_test_same_task)

tests/test_misc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ def do_not_run():
149149
raise Exception("should not run")
150150

151151
async def cancel_sleep():
152-
h = loop.call_later(0.1, do_not_run)
153-
await asyncio.sleep(0.05, loop=loop)
152+
h = loop.call_later(0.2, do_not_run)
153+
await asyncio.sleep(0.1, loop=loop)
154154
h.cancel()
155-
await asyncio.sleep(0.2, loop=loop)
155+
await asyncio.sleep(0.3, loop=loop)
156156

157157
await loop.run_asyncio(cancel_sleep)
158158
assert owch == 0

trio_asyncio/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# This file is imported from __init__.py and exec'd from setup.py
22

3-
__version__ = "0.6.0"
3+
__version__ = "0.7.0"

trio_asyncio/async_.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import trio
2-
import asyncio
32

43
from async_generator import async_generator, yield_, asynccontextmanager
54

@@ -105,8 +104,12 @@ def _main_loop_exit(self):
105104
super()._main_loop_exit()
106105
self._thread = None
107106

107+
from .loop import _current_loop
108+
outer_loop = _current_loop.loop
109+
108110
async with trio.open_nursery() as nursery:
109111
loop = TrioEventLoop(queue_len=queue_len)
112+
_current_loop.loop = loop
110113
try:
111114
loop._closed = False
112115
await loop._main_loop_init(nursery)
@@ -119,3 +122,4 @@ def _main_loop_exit(self):
119122
await loop._main_loop_exit()
120123
loop.close()
121124
nursery.cancel_scope.cancel()
125+
_current_loop.loop = outer_loop

trio_asyncio/base.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
import sys
3-
import inspect
43
import math
54
import trio
65
import heapq
@@ -646,7 +645,6 @@ async def _main_loop_init(self, nursery):
646645
self._nursery = nursery
647646
self._task = trio.hazmat.current_task()
648647
self._token = trio.hazmat.current_trio_token()
649-
asyncio.events._set_running_loop(self)
650648

651649
async def _main_loop(self, task_status=trio.TASK_STATUS_IGNORED):
652650
"""Run the loop by processing its event queue.
@@ -726,7 +724,6 @@ async def _main_loop_exit(self):
726724
# clean core fields
727725
self._nursery = None
728726
self._task = None
729-
asyncio.events._set_running_loop(None)
730727

731728
def is_running(self):
732729
if self._stopped is None:

trio_asyncio/loop.py

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@
3030
'TrioPolicy',
3131
]
3232

33+
_current_loop = trio.TaskLocal(loop=None, policy=None)
34+
3335

3436
class _TrioPolicy(asyncio.events.BaseDefaultEventLoopPolicy):
3537
_loop_factory = TrioEventLoop
3638

37-
def __init__(self):
38-
super().__init__()
39-
self._trio_local = trio.hazmat.RunLocal(_loop=None, _task=False)
40-
4139
def new_event_loop(self):
4240
try:
4341
trio.hazmat.current_task()
@@ -75,45 +73,94 @@ def get_event_loop(self):
7573
# this creates a new loop in the main task
7674
return super().get_event_loop()
7775
else:
78-
return self._trio_local._loop
76+
return _current_loop.loop
7977

8078
@property
8179
def current_event_loop(self):
8280
"""The currently-running event loop, if one exists."""
8381
try:
84-
return self._trio_local._loop
82+
return _current_loop.loop
8583
except RuntimeError:
8684
# in the main thread this would create a new loop
8785
# return super().get_event_loop()
88-
return self._local._loop
86+
return super().get_event_loop()
8987

9088
def set_event_loop(self, loop):
9189
"""Set the current event loop."""
9290
try:
93-
task = trio.hazmat.current_task()
91+
_current_loop.loop = loop
9492
except RuntimeError:
9593
return super().set_event_loop(loop)
9694

97-
# This test will not trigger if you create a new asyncio event loop
98-
# in a sub-task, which is exactly what we intend to be possible
99-
if self._trio_local._loop is not None and loop is not None and \
100-
self._trio_local._task == task:
101-
raise RuntimeError('You cannot replace an event loop.', self._trio_local._loop, loop)
102-
self._trio_local._loop = loop
103-
self._trio_local._task = task
95+
96+
# We need to monkey-patch asyncio's policy+loop getters to return our
97+
# TrioPolicy+loop whenever we are within Trio.
98+
99+
from asyncio import events as _aio_event
100+
101+
#####
102+
103+
_orig_policy_get = _aio_event.get_event_loop_policy
104+
105+
106+
def _new_policy_get():
107+
try:
108+
policy = _current_loop.policy
109+
except RuntimeError:
110+
return _orig_policy_get()
111+
112+
if policy is None:
113+
policy = TrioPolicy()
114+
_current_loop.policy = policy
115+
return policy
116+
117+
118+
_aio_event.get_event_loop_policy = _new_policy_get
119+
asyncio.get_event_loop_policy = _new_policy_get
120+
121+
#####
122+
123+
_orig_run_get = _aio_event._get_running_loop
124+
125+
126+
def _new_run_get():
127+
try:
128+
return _current_loop.loop
129+
except RuntimeError:
130+
return _orig_run_get()
131+
132+
133+
_aio_event._get_running_loop = _new_run_get
134+
135+
#####
136+
137+
_orig_loop_get = _aio_event.get_event_loop
138+
139+
140+
def _new_loop_get():
141+
try:
142+
return _current_loop.loop
143+
except RuntimeError:
144+
return _orig_loop_get()
145+
146+
147+
_aio_event.get_event_loop = _new_loop_get
148+
asyncio.get_event_loop = _new_loop_get
104149

105150

106151
class TrioPolicy(_TrioPolicy, asyncio.DefaultEventLoopPolicy):
152+
"""This is the loop policy that's active whenever we're in a Trio context."""
153+
107154
def _init_watcher(self):
108155
with asyncio.events._lock:
109156
if self._watcher is None: # pragma: no branch
110157
self._watcher = TrioChildWatcher()
111158
if isinstance(threading.current_thread(), threading._MainThread):
112-
self._watcher.attach_loop(self._trio_local._loop)
159+
self._watcher.attach_loop(_current_loop.loop)
113160

114161
if self._watcher is not None and \
115162
isinstance(threading.current_thread(), threading._MainThread):
116-
self._watcher.attach_loop(self._trio_local._loop)
163+
self._watcher.attach_loop(_current_loop.loop)
117164

118165
def set_child_watcher(self, watcher):
119166
if watcher is not None:

0 commit comments

Comments
 (0)