Skip to content

Commit 8098eb9

Browse files
author
Yury Selivanov
committed
More APIs; proper checks for threads, closing, running etc
1 parent cdf7adf commit 8098eb9

10 files changed

Lines changed: 174 additions & 45 deletions

File tree

‎uvloop/async_.pxd‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ cdef class Async:
99
uv.uv_async_t *handle
1010
object callback
1111

12-
cdef send(self)
12+
cdef void send(self)

‎uvloop/async_.pyx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ cdef class Async:
2020
def __dealloc__(self):
2121
PyMem_Free(self.handle)
2222

23-
cdef send(self):
23+
cdef void send(self):
2424
uv.uv_async_send(self.handle)
2525

2626

‎uvloop/idle.pxd‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33

44
from . cimport uv
5+
from .loop cimport Loop
56

67

78
cdef class Idle:
89
cdef:
910
uv.uv_idle_t *handle
1011
object callback
1112
int running
13+
Loop loop
1214

13-
cdef stop(self)
14-
cdef start(self)
15+
cdef void stop(self)
16+
cdef void start(self)

‎uvloop/idle.pyx‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,28 @@ cdef class Idle:
1818
uv.uv_idle_init(loop.loop, self.handle)
1919

2020
self.running = 0
21+
self.loop = loop
2122

2223
def __dealloc__(self):
2324
try:
2425
self.stop()
2526
finally:
2627
PyMem_Free(self.handle)
2728

28-
cdef stop(self):
29+
cdef void stop(self):
2930
if self.running == 1:
3031
uv.uv_idle_stop(self.handle)
3132
self.running = 0
3233

33-
cdef start(self):
34+
cdef void start(self):
3435
if self.running == 0:
3536
uv.uv_idle_start(self.handle, cb_idle_callback)
3637
self.running = 1
3738

3839

3940
cdef void cb_idle_callback(uv.uv_idle_t* handle):
4041
cdef Idle idle = <Idle> handle.data
41-
idle.callback()
42+
try:
43+
idle.callback()
44+
except BaseException as ex:
45+
idle.loop._handle_uvcb_exception(ex)

‎uvloop/loop.pxd‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,23 @@ from libc.stdint cimport uint64_t
1313
cdef class Loop:
1414
cdef:
1515
uv.uv_loop_t *loop
16-
int closed
16+
int _closed
1717
int _debug
18+
long _thread_id
19+
int _running
20+
21+
object _ready
22+
int _ready_len
1823

1924
Async handler_async
2025
Idle handler_idle
2126
Signal handler_sigint
2227

23-
object last_error
24-
object callbacks
25-
object partial
26-
object _default_task_constructor
28+
object _last_error
29+
30+
object _make_partial
31+
object _asyncio
32+
object _asyncio_Task
2733

2834
cdef object __weakref__
2935

@@ -34,3 +40,8 @@ cdef class Loop:
3440

3541
cdef _call_soon(self, object callback)
3642
cdef _call_later(self, uint64_t delay, object callback)
43+
44+
cdef _handle_uvcb_exception(self, object ex)
45+
46+
cdef _check_closed(self)
47+
cdef _check_thread(self)

‎uvloop/loop.pyx‎

Lines changed: 123 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ from libc.stdint cimport uint64_t
2020

2121
from cpython.mem cimport PyMem_Malloc, PyMem_Free
2222
from cpython.exc cimport PyErr_CheckSignals, PyErr_Occurred
23+
from cpython.pythread cimport PyThread_get_thread_ident
2324

2425

2526
@cython.no_gc_clear
@@ -28,8 +29,10 @@ cdef class Loop:
2829
self.loop = <uv.uv_loop_t*> \
2930
PyMem_Malloc(sizeof(uv.uv_loop_t))
3031
self.loop.data = <void*> self
31-
self.closed = 0
32+
self._closed = 0
3233
self._debug = 0
34+
self._thread_id = 0
35+
self._running = 0
3336

3437
uv.uv_loop_init(self.loop)
3538

@@ -39,8 +42,8 @@ cdef class Loop:
3942
# the policy won't be properly destroyed, hence the
4043
# loop won't be properly destroyed, hence some warnings
4144
# might not be shown at all.
42-
asyncio = __import__('asyncio')
43-
self._default_task_constructor = asyncio.Task
45+
self._asyncio = __import__('asyncio')
46+
self._asyncio_Task = self._asyncio.Task
4447

4548
def __dealloc__(self):
4649
try:
@@ -56,80 +59,134 @@ cdef class Loop:
5659
self.handler_sigint = Signal(self, self._on_sigint, signal.SIGINT)
5760
self.handler_sigint.start()
5861

59-
self.last_error = None
62+
self._last_error = None
6063

61-
self.callbacks = collections.deque()
64+
self._ready = collections.deque()
65+
self._ready_len = 0
6266

63-
self.partial = functools.partial
67+
self._make_partial = functools.partial
6468

6569
def _on_wake(self):
66-
if len(self.callbacks) > 0:
70+
if self._ready_len > 0 and not self.handler_idle.running:
6771
self.handler_idle.start()
6872

6973
def _on_sigint(self):
70-
self.last_error = KeyboardInterrupt()
74+
self._last_error = KeyboardInterrupt()
7175
self._stop()
7276

7377
def _on_idle(self):
7478
cdef int i, ntodo
75-
cdef object popleft = self.callbacks.popleft
79+
cdef object popleft = self._ready.popleft
7680

77-
ntodo = len(self.callbacks)
81+
ntodo = len(self._ready)
7882
for i from 0 <= i < ntodo:
7983
handler = <Handle> popleft()
8084
if handler.cancelled == 0:
8185
handler._run()
8286

83-
if len(self.callbacks) == 0:
87+
self._ready_len = len(self._ready)
88+
if self._ready_len == 0 and self.handler_idle.running:
8489
self.handler_idle.stop()
8590

8691
cdef _stop(self):
8792
uv.uv_stop(self.loop)
8893

8994
cdef _run(self, uv.uv_run_mode mode):
90-
if self.closed:
95+
if self._closed == 1:
9196
raise RuntimeError('unable to start the loop; it was closed')
9297

98+
if self._running == 1:
99+
raise RuntimeError('Event loop is running.')
100+
101+
self._thread_id = PyThread_get_thread_ident()
102+
self._running = 1
93103
uv.uv_run(self.loop, mode)
104+
self._thread_id = 0
105+
self._running = 0
94106

95-
if self.last_error is not None:
96-
raise self.last_error
107+
if self._last_error is not None:
108+
last_error = self._last_error
109+
self.close()
110+
raise last_error
97111

98112
cdef _close(self):
99-
if self.closed == 0:
113+
if self._running == 1:
114+
raise RuntimeError("Cannot close a running event loop")
115+
if self._closed == 0:
116+
self._closed = 1
117+
118+
self._ready.clear()
119+
self._ready_len = 0
120+
self._last_error = None
121+
100122
uv.uv_loop_close(self.loop)
101-
self.closed = 1
102123

103124
cdef uint64_t _time(self):
104125
return uv.uv_now(self.loop)
105126

106127
cdef _call_soon(self, object callback):
107128
handle = Handle(self, callback)
108-
self.callbacks.append(handle)
109-
self.handler_idle.start()
129+
self._ready.append(handle)
130+
self._ready_len += 1;
131+
if not self.handler_idle.running:
132+
self.handler_idle.start()
110133
return handle
111134

112135
cdef _call_later(self, uint64_t delay, object callback):
113136
return TimerHandle(self, callback, delay)
114137

138+
cdef _handle_uvcb_exception(self, object ex):
139+
if isinstance(ex, Exception):
140+
self.call_exception_handler({'exception': ex})
141+
else:
142+
# BaseException
143+
self._last_error = ex
144+
# Exit ASAP
145+
self._stop()
146+
147+
cdef _check_closed(self):
148+
if self._closed == 1:
149+
raise RuntimeError('Event loop is closed')
150+
151+
cdef _check_thread(self):
152+
if self._thread_id == 0:
153+
return
154+
cdef long thread_id = PyThread_get_thread_ident()
155+
if thread_id != self._thread_id:
156+
raise RuntimeError(
157+
"Non-thread-safe operation invoked on an event loop other "
158+
"than the current one")
159+
115160
# Public API
116161

162+
def __repr__(self):
163+
return ('<%s running=%s closed=%s debug=%s>'
164+
% (self.__class__.__name__, self.is_running(),
165+
self.is_closed(), self.get_debug()))
166+
117167
def call_soon(self, callback, *args):
168+
self._check_closed()
169+
if self._debug == 1:
170+
self._check_thread()
118171
if len(args):
119-
callback = self.partial(callback, *args)
172+
callback = self._make_partial(callback, *args)
120173
return self._call_soon(callback)
121174

122175
def call_soon_threadsafe(self, callback, *args):
176+
self._check_closed()
123177
if len(args):
124-
callback = self.partial(callback, *args)
178+
callback = self._make_partial(callback, *args)
125179
handle = self._call_soon(callback)
126180
self.handler_async.send()
127181
return handle
128182

129183
def call_later(self, delay, callback, *args):
184+
self._check_closed()
185+
if self._debug == 1:
186+
self._check_thread()
130187
cdef uint64_t when = <uint64_t>(delay * 1000)
131188
if len(args):
132-
callback = self.partial(callback, *args)
189+
callback = self._make_partial(callback, *args)
133190
return self._call_later(when, callback)
134191

135192
def time(self):
@@ -139,6 +196,7 @@ cdef class Loop:
139196
self._call_soon(lambda: self._stop())
140197

141198
def run_forever(self):
199+
self._check_closed()
142200
self._run(uv.UV_RUN_DEFAULT)
143201

144202
def close(self):
@@ -156,11 +214,53 @@ cdef class Loop:
156214
else:
157215
self._debug = 0
158216

217+
def is_running(self):
218+
if self._running == 0:
219+
return False
220+
else:
221+
return True
222+
223+
def is_closed(self):
224+
if self._closed == 0:
225+
return False
226+
else:
227+
return True
228+
159229
def create_task(self, coro):
160-
return self._default_task_constructor(coro, loop=self)
230+
self._check_closed()
231+
232+
return self._asyncio_Task(coro, loop=self)
233+
234+
def run_until_complete(self, future):
235+
self._check_closed()
236+
237+
new_task = not isinstance(future, self._asyncio.Future)
238+
future = self._asyncio.ensure_future(future, loop=self)
239+
if new_task:
240+
# An exception is raised if the future didn't complete, so there
241+
# is no need to log the "destroy pending task" message
242+
future._log_destroy_pending = False
243+
244+
done_cb = lambda fut: self.stop()
245+
246+
future.add_done_callback(done_cb)
247+
try:
248+
self.run_forever()
249+
except:
250+
if new_task and future.done() and not future.cancelled():
251+
# The coroutine raised a BaseException. Consume the exception
252+
# to not log a warning, the caller doesn't have access to the
253+
# local task.
254+
future.exception()
255+
raise
256+
future.remove_done_callback(done_cb)
257+
if not future.done():
258+
raise RuntimeError('Event loop stopped before Future completed.')
259+
260+
return future.result()
161261

162262
def call_exception_handler(self, context):
163-
print("!!! EXCEPTION HANDLER !!!", context)
263+
print("!!! EXCEPTION HANDLER !!!", context, flush=True)
164264

165265

166266
@cython.internal

‎uvloop/signal.pxd‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
from . cimport uv
5+
from .loop cimport Loop
56

67

78
cdef class Signal:
@@ -10,6 +11,7 @@ cdef class Signal:
1011
object callback
1112
int running
1213
int signum
14+
Loop loop
1315

14-
cdef stop(self)
15-
cdef start(self)
16+
cdef void stop(self)
17+
cdef void start(self)

0 commit comments

Comments
 (0)