@@ -20,6 +20,7 @@ from libc.stdint cimport uint64_t
2020
2121from cpython.mem cimport PyMem_Malloc, PyMem_Free
2222from 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
0 commit comments