def send_exception(g, exc): # note: send_exception(g, exc) can be now done with g.throw(exc). # the purpose of this test is to explicitely check the propagation rules. def crasher(exc): raise exc g1 = Fiber(target=crasher, args=(exc, ), parent=g) g1.switch()
def test_finished_parent(self): def f(): return 42 g = Fiber(f) g.switch() self.assertFalse(g.is_alive()) self.assertRaises(ValueError, Fiber, parent=g)
def f(): try: raise ValueError('fun') except: exc_info = sys.exc_info() t = Fiber(h) t.switch() self.assertEqual(exc_info, sys.exc_info()) del t
def test_kwarg_refs(self): kwargs = {'a': 1234} refcount_before = sys.getrefcount(kwargs) g = Fiber(lambda **x: None, kwargs=kwargs) self.assertEqual(sys.getrefcount(kwargs), refcount_before+1) g.switch() self.assertEqual(sys.getrefcount(kwargs), refcount_before) del g self.assertEqual(sys.getrefcount(kwargs), refcount_before)
def test_arg_refs(self): args = ('a', 'b', 'c') refcount_before = sys.getrefcount(args) g = Fiber(target=lambda *x: None, args=args) self.assertEqual(sys.getrefcount(args), refcount_before+1) g.switch() self.assertEqual(sys.getrefcount(args), refcount_before) del g self.assertEqual(sys.getrefcount(args), refcount_before)
def test_kwarg_refs(self): if not has_refcount: return kwargs = {'a': 1234} refcount_before = sys.getrefcount(kwargs) g = Fiber(lambda **x: None, kwargs=kwargs) self.assertEqual(sys.getrefcount(kwargs), refcount_before + 1) g.switch() self.assertEqual(sys.getrefcount(kwargs), refcount_before) del g self.assertEqual(sys.getrefcount(kwargs), refcount_before)
def test_arg_refs(self): if not has_refcount: return args = ('a', 'b', 'c') refcount_before = sys.getrefcount(args) g = Fiber(target=lambda *x: None, args=args) self.assertEqual(sys.getrefcount(args), refcount_before + 1) g.switch() self.assertEqual(sys.getrefcount(args), refcount_before) del g self.assertEqual(sys.getrefcount(args), refcount_before)
def test_class(self): def f(): try: switch("ok") except RuntimeError: switch("ok") return switch("fail") g = Fiber(f) res = g.switch() self.assertEqual(res, "ok") res = g.throw(RuntimeError) self.assertEqual(res, "ok")
def test_kill(self): def f(): try: switch("ok") switch("fail") except Exception as e: return e g = Fiber(f) res = g.switch() self.assertEqual(res, "ok") res = g.throw(ValueError) self.assertTrue(isinstance(res, ValueError)) self.assertFalse(g.is_alive())
def test_exception(self): seen = [] g1 = Fiber(target=fmain, args=(seen, )) g2 = Fiber(target=fmain, args=(seen, )) g1.switch() g2.switch() g2.parent = g1 self.assertEqual(seen, []) self.assertRaises(SomeError, g2.switch) self.assertEqual(seen, [SomeError])
def test_simple2(self): lst = [] def f(): lst.append(1) current().parent.switch() lst.append(3) g = Fiber(f) lst.append(0) g.switch() lst.append(2) g.switch() lst.append(4) self.assertEqual(lst, list(range(5)))
def test_two_recursive_children(self): lst = [] def f(): lst.append(1) current().parent.switch() def h(): lst.append(1) i = Fiber(f) i.switch() lst.append(1) g = Fiber(h) g.switch() self.assertEqual(len(lst), 3)
def test_exc_state(self): def f(): try: raise ValueError('fun') except: exc_info = sys.exc_info() t = Fiber(h) t.switch() self.assertEqual(exc_info, sys.exc_info()) del t def h(): self.assertEqual(sys.exc_info(), (None, None, None)) g = Fiber(f) g.switch()
def test_instance_dict(self): def f(): current().test = 42 def deldict(g): del g.__dict__ def setdict(g, value): g.__dict__ = value g = Fiber(f) self.assertEqual(g.__dict__, {}) g.switch() self.assertEqual(g.test, 42) self.assertEqual(g.__dict__, {'test': 42}) g.__dict__ = g.__dict__ self.assertEqual(g.__dict__, {'test': 42}) self.assertRaises(AttributeError, deldict, g) self.assertRaises(TypeError, setdict, g, 42)
def test_instance_dict(self): if is_pypy: return def f(): current().test = 42 def deldict(g): del g.__dict__ def setdict(g, value): g.__dict__ = value g = Fiber(f) self.assertEqual(g.__dict__, {}) g.switch() self.assertEqual(g.test, 42) self.assertEqual(g.__dict__, {'test': 42}) g.__dict__ = g.__dict__ self.assertEqual(g.__dict__, {'test': 42}) self.assertRaises(AttributeError, deldict, g) self.assertRaises(TypeError, setdict, g, 42)
def await_all_tasks(cls, tasks): self = cls.get() fb = Fiber.current() def _callback(ts): self._fibers.append(fb) cls.watch_all_tasks(tasks, _callback) self._loop_fiber.switch()
def async (cls, func, *args, **kwargs): self = cls.get() def _f(): func(*args, **kwargs) self._loop_fiber.switch() fb = Fiber(target=_f) self._fibers.append(fb)
def await_all_ps(cls, ps_set): self = cls.get() fb = Fiber.current() def _callback(pss): self._fibers.append(fb) cls.watch_all_ps(ps_set, _callback) self._loop_fiber.switch()
def test_throw_goes_to_original_parent(self): main = fibers.current() def f1(): try: main.switch("f1 ready to catch") except IndexError: return "caught" else: return "normal exit" def f2(): main.switch("from f2") g1 = Fiber(f1) g2 = Fiber(target=f2, parent=g1) self.assertRaises(IndexError, g2.throw, IndexError) self.assertFalse(g2.is_alive()) self.assertTrue(g1.is_alive()) # g1 is skipped because it was not started
def worker(): # main and additional *finished* greenlets ll = current().ll = [] def additional(): ll.append(current()) for i in range(2): Fiber(additional).switch() gg.append(weakref.ref(current()))
def switch(self): if not self._started: self._run(forever=False) return current = Fiber.current() assert current is not self.task, 'Cannot switch to MAIN from MAIN' try: if self.task.parent is not current: current.parent = self.task except ValueError: pass # gets raised if there is a Fiber parent cycle return self.task.switch()
def switch(self): if not self._started: self.run() return current = Fiber.current() assert current is not self.task, 'Cannot switch to MAIN from MAIN' try: if self.task.parent is not current: current.parent = self.task except ValueError: pass # gets raised if there is a Fiber parent cycle return self.task.switch()
def test_finalizer_crash(self): # This test is designed to crash when active greenlets # are made garbage collectable, until the underlying # problem is resolved. How does it work: # - order of object creation is important # - array is created first, so it is moved to unreachable first # - we create a cycle between a greenlet and this array # - we create an object that participates in gc, is only # referenced by a greenlet, and would corrupt gc lists # on destruction, the easiest is to use an object with # a finalizer # - because array is the first object in unreachable it is # cleared first, which causes all references to greenlet # to disappear and causes greenlet to be destroyed, but since # it is still live it causes a switch during gc, which causes # an object with finalizer to be destroyed, which causes stack # corruption and then a crash class object_with_finalizer(object): def __del__(self): pass array = [] parent = current() def greenlet_body(): current().object = object_with_finalizer() try: parent.switch() finally: del current().object g = Fiber(greenlet_body) g.array = array array.append(g) g.switch() del array del g current() gc.collect()
def test_threaded_updatecurrent(self): # FIXME (hangs?) return # released when main thread should execute lock1 = threading.Lock() lock1.acquire() # released when another thread should execute lock2 = threading.Lock() lock2.acquire() class finalized(object): def __del__(self): # happens while in green_updatecurrent() in main greenlet # should be very careful not to accidentally call it again # at the same time we must make sure another thread executes lock2.release() lock1.acquire() # now ts_current belongs to another thread def deallocator(): current().parent.switch() def fthread(): lock2.acquire() current() del g[0] lock1.release() lock2.acquire() current() lock1.release() main = current() g = [Fiber(deallocator)] g[0].bomb = finalized() g[0].switch() t = threading.Thread(target=fthread) t.start() # let another thread grab ts_current and deallocate g[0] lock2.release() lock1.acquire() # this is the corner stone # getcurrent() will notice that ts_current belongs to another thread # and start the update process, which would notice that g[0] should # be deallocated, and that will execute an object's finalizer. Now, # that object will let another thread run so it can grab ts_current # again, which would likely crash the interpreter if there's no # check for this case at the end of green_updatecurrent(). This test # passes if getcurrent() returns correct result, but it's likely # to randomly crash if it's not anyway. self.assertEqual(current(), main) # wait for another thread to complete, just in case t.join()
def run(self, mode=RUN_DEFAULT): if Fiber.current() is not self.task.parent: raise RuntimeError('run() can only be called from MAIN fiber') if not self.task.is_alive(): raise RuntimeError('event loop has already ended') if self._started: raise RuntimeError('event loop was already started') self._started = True self._running = True self._run_mode = mode try: self.task.switch() finally: self._running = False
def sleep(seconds=0): """Yield control to another eligible coroutine until at least *seconds* have elapsed. *seconds* may be specified as an integer, or a float if fractional seconds are desired. """ loop = evergreen.current.loop current = Fiber.current() assert loop.task is not current timer = loop.call_later(seconds, current.switch) try: loop.switch() finally: timer.cancel()
def test_threaded_reparent(self): data = {} created_event = threading.Event() done_event = threading.Event() def foo(): data['g'] = Fiber(lambda: None) created_event.set() done_event.wait() def blank(): current().parent.switch() def setparent(g, value): g.parent = value thread = threading.Thread(target=foo) thread.start() created_event.wait() g = Fiber(blank) g.switch() self.assertRaises(ValueError, setparent, g, data['g']) done_event.set() thread.join()
def await_all_tasks(cls, tasks): """ wait until a set of Tasks is complete. Parameters --- tasks : list of Task """ self = cls._get() fb = Fiber.current() def _callback(): self._fibers.append(fb) cls.watch_all_tasks(tasks, _callback) self._loop_fiber.switch()
def await_task(cls, task): """ wait until a Task is complete. Parameters --- task : Task """ self = cls._get() fb = Fiber.current() def _callback(): self._fibers.append(fb) cls.watch_task(task, _callback) self._loop_fiber.switch()
def await_all_ps(cls, ps_set): """ wait until a set of ParameterSets is complete. While waiting, other co-routines are concurrently executed. Parameters --- ps_set : list of ParameterSet """ self = cls._get() fb = Fiber.current() def _callback(): self._fibers.append(fb) cls.watch_all_ps(ps_set, _callback) self._loop_fiber.switch()
def await_ps(cls, ps): """ wait until parameterSet is complete. During waiting, other co-routines are concurrently executed. Parameters --- ps : ParameterSet """ self = cls._get() fb = Fiber.current() def _callback(): self._fibers.append(fb) cls.watch_ps(ps, _callback) self._loop_fiber.switch()
def test_throw_goes_to_original_parent3(self): main = fibers.current() def f1(): try: main.switch("f1 ready to catch") except IndexError: return "caught" else: return "normal exit" def f2(): main.switch("from f2") g1 = Fiber(f1) g2 = Fiber(target=f2, parent=g1) res = g1.switch() self.assertEqual(res, "f1 ready to catch") res = g2.switch() self.assertEqual(res, "from f2") res = g2.throw(IndexError) self.assertEqual(res, "caught") self.assertFalse(g2.is_alive()) self.assertFalse(g1.is_alive())
def test_val(self): def f(): try: switch("ok") except RuntimeError: val = sys.exc_info()[1] if str(val) == "ciao": switch("ok") return switch("fail") g = Fiber(f) res = g.switch() self.assertEqual(res, "ok") res = g.throw(RuntimeError("ciao")) self.assertEqual(res, "ok") g = Fiber(f) res = g.switch() self.assertEqual(res, "ok") res = g.throw(RuntimeError, "ciao") self.assertEqual(res, "ok")
def do_async(cls, func, *args, **kwargs): """ do coroutine asynchronously. Examples --- Server.do_async( lambda : Task.create(...) ) Parameters --- tasks : list of Task callback : callable """ self = cls._get() def _f(): func(*args, **kwargs) self._loop_fiber.switch() fb = Fiber(target=_f) self._fibers.append(fb)
def __init__(self): if getattr(_tls, 'loop', None) is not None: raise RuntimeError('cannot instantiate more than one event loop per thread') _tls.loop = self self._loop = pyuv.Loop() self._loop.excepthook = self._handle_error self._loop.event_loop = self self._threadpool = ThreadPool(self) self.task = Fiber(self._run_loop) self._destroyed = False self._started = False self._running = False self._fd_map = dict() self._signals = dict() self._timers = set() self._ready = deque() self._ready_processor = pyuv.Idle(self._loop) self._waker = pyuv.Async(self._loop, self._async_cb) self._waker.unref() self._install_signal_checker()
def test_throw_goes_to_original_parent(self): main = fibers.current() def f1(): try: main.switch("f1 ready to catch") except IndexError: return "caught" else: return "normal exit" def f2(): main.switch("from f2") g1 = Fiber(f1) g2 = Fiber(target=f2, parent=g1) self.assertRaises(IndexError, g2.throw, IndexError) self.assertFalse(g2.is_alive()) self.assertTrue( g1.is_alive()) # g1 is skipped because it was not started
class EventLoop(object): def __init__(self): if getattr(_tls, 'loop', None) is not None: raise RuntimeError('cannot instantiate more than one event loop per thread') _tls.loop = self self._loop = pyuv.Loop() self._loop.excepthook = self._handle_error self._loop.event_loop = self self._threadpool = ThreadPool(self) self.task = Fiber(self._run_loop) self._destroyed = False self._started = False self._running = False self._fd_map = dict() self._signals = dict() self._timers = set() self._ready = deque() self._ready_processor = pyuv.Idle(self._loop) self._waker = pyuv.Async(self._loop, self._async_cb) self._waker.unref() self._install_signal_checker() @classmethod def current(cls): """Get the current event loop singleton object. """ try: return _tls.loop except AttributeError: # create loop only for main thread if threading.current_thread().name == 'MainThread': _tls.loop = cls() return _tls.loop raise RuntimeError('there is no event loop created in the current thread') @property def running(self): return self._running def call_soon(self, callback, *args, **kw): handler = Handler(callback, *args, **kw) self._add_callback(handler) return handler def call_from_thread(self, callback, *args, **kw): handler = Handler(callback, *args, **kw) # Here we don't call self._add_callback on purpose, because it's not thread # safe to start pyuv handles. We just append the callback to the queue and # wakeup the loop. This is thread safe because the queue is only processed # in a single place and in a thread safe manner. self._ready.append(handler) self._waker.send() return handler def call_later(self, delay, callback, *args, **kw): if delay <= 0: return self.call_soon(callback, *args, **kw) timer_h = pyuv.Timer(self._loop) handler = Timer(timer_h, callback, *args, **kw) timer_h.handler = handler timer_h.start(self._timer_cb, delay, 0) self._timers.add(timer_h) return handler def call_at(self, when, callback, *args, **kw): return self.call_later(when-self.time(), callback, *args, **kw) def time(self): return _time() def add_reader(self, fd, callback, *args, **kw): handler = Handler(callback, *args, **kw) try: poll_h = self._fd_map[fd] except KeyError: poll_h = self._create_poll_handle(fd) self._fd_map[fd] = poll_h else: if poll_h.read_handler: raise RuntimeError('another reader is already registered for fd {}'.format(fd)) poll_h.pevents |= pyuv.UV_READABLE poll_h.read_handler = handler poll_h.start(poll_h.pevents, self._poll_cb) def remove_reader(self, fd): try: poll_h = self._fd_map[fd] except KeyError: return False else: handler = poll_h.read_handler poll_h.pevents &= ~pyuv.UV_READABLE poll_h.read_handler = None if poll_h.pevents == 0: poll_h.close() del self._fd_map[fd] else: poll_h.start(poll_h.pevents, self._poll_cb) if handler: handler.cancel() return True return False def add_writer(self, fd, callback, *args, **kw): handler = Handler(callback, *args, **kw) try: poll_h = self._fd_map[fd] except KeyError: poll_h = self._create_poll_handle(fd) self._fd_map[fd] = poll_h else: if poll_h.write_handler: raise RuntimeError('another writer is already registered for fd {}'.format(fd)) poll_h.pevents |= pyuv.UV_WRITABLE poll_h.write_handler = handler poll_h.start(poll_h.pevents, self._poll_cb) def remove_writer(self, fd): try: poll_h = self._fd_map[fd] except KeyError: return False else: handler = poll_h.write_handler poll_h.pevents &= ~pyuv.UV_WRITABLE poll_h.write_handler = None if poll_h.pevents == 0: poll_h.close() del self._fd_map[fd] else: poll_h.start(poll_h.pevents, self._poll_cb) if handler: handler.cancel() return True return False def add_signal_handler(self, sig, callback, *args, **kwargs): self._validate_signal(sig) signal_h = pyuv.Signal(self._loop) handler = SignalHandler(signal_h, callback, *args, **kwargs) signal_h.handler = handler signal_h.signum = sig try: signal_h.start(self._signal_cb, sig) signal_h.unref() except Exception as e: signal_h.close() raise RuntimeError(str(e)) else: self._signals.setdefault(sig, set()).add(signal_h) return handler def remove_signal_handler(self, sig): self._validate_signal(sig) try: handles = self._signals.pop(sig) except KeyError: return False for signal_h in handles: del signal_h.handler signal_h.close() return True def switch(self): if not self._started: self.run() return current = Fiber.current() assert current is not self.task, 'Cannot switch to MAIN from MAIN' try: if self.task.parent is not current: current.parent = self.task except ValueError: pass # gets raised if there is a Fiber parent cycle return self.task.switch() def run(self, mode=RUN_DEFAULT): if Fiber.current() is not self.task.parent: raise RuntimeError('run() can only be called from MAIN fiber') if not self.task.is_alive(): raise RuntimeError('event loop has already ended') if self._started: raise RuntimeError('event loop was already started') self._started = True self._running = True self._run_mode = mode try: self.task.switch() finally: self._running = False def run_forever(self): self.run(mode=RUN_FOREVER) def stop(self): if not self._started: raise RuntimeError('event loop has not been started yet') if self._loop: self._loop.stop() def destroy(self): if self._running: raise RuntimeError('destroy() cannot be called while the loop is running') if self._destroyed: raise RuntimeError('Event loop already destroyed') loop = getattr(_tls, 'loop', None) if loop is not self: raise RuntimeError('destroy() can only be called from the same thread were the event loop was created') del _tls.loop, loop self._destroyed = True self._uninstall_signal_checker() self._cleanup_loop() self._loop.event_loop = None self._loop.excepthook = None self._loop = None self._threadpool = None self._ready_processor = None self._waker = None self._fd_map.clear() self._signals.clear() self._timers.clear() self._ready.clear() # internal def _add_callback(self, cb): self._ready.append(cb) if not self._ready_processor.active: self._ready_processor.start(self._process_ready) def _handle_error(self, typ, value, tb): if not issubclass(typ, SystemExit): traceback.print_exception(typ, value, tb) if issubclass(typ, (KeyboardInterrupt, SystemExit, SystemError)): assert Fiber.current() is self.task self.task.parent.throw(typ, value, tb) def _run_loop(self): if self._run_mode == RUN_FOREVER: self._waker.ref() self._loop.run(pyuv.UV_RUN_DEFAULT) def _cleanup_loop(self): def cb(handle): if not handle.closed: handle.close() self._loop.walk(cb) # All handles are now closed, run will not block self._loop.run(pyuv.UV_RUN_NOWAIT) def _create_poll_handle(self, fd): poll_h = pyuv.Poll(self._loop, fd) poll_h.pevents = 0 poll_h.read_handler = None poll_h.write_handler = None return poll_h def _process_ready(self, handle): # Run all queued callbacks ntodo = len(self._ready) for x in range(ntodo): handler = self._ready.popleft() if not handler._cancelled: # loop.excepthook takes care of exception handling handler() if not self._ready: self._ready_processor.stop() def _async_cb(self, handle): if not self._ready_processor.active: self._ready_processor.start(self._process_ready) def _timer_cb(self, timer): assert not timer.handler._cancelled self._add_callback(timer.handler) if not timer.repeat: timer.close() self._timers.remove(timer) del timer.handler def _signal_cb(self, signal_h, signum): self._add_callback(signal_h.handler) def _poll_cb(self, poll_h, events, error): fd = poll_h.fileno() if error is not None: # An error happened, signal both readability and writability and # let the error propagate if poll_h.read_handler is not None: if poll_h.read_handler._cancelled: self.remove_reader(fd) else: self._add_callback(poll_h.read_handler) if poll_h.write_handler is not None: if poll_h.write_handler._cancelled: self.remove_writer(fd) else: self._add_callback(poll_h.write_handler) return old_events = poll_h.pevents modified = False if events & pyuv.UV_READABLE: if poll_h.read_handler is not None: if poll_h.read_handler._cancelled: self.remove_reader(fd) modified = True else: self._add_callback(poll_h.read_handler) else: poll_h.pevents &= ~pyuv.UV_READABLE if events & pyuv.UV_WRITABLE: if poll_h.write_handler is not None: if poll_h.write_handler._cancelled: self.remove_writer(fd) modified = True else: self._add_callback(poll_h.write_handler) else: poll_h.pevents &= ~pyuv.UV_WRITABLE if not modified and old_events != poll_h.pevents: # Rearm the handle poll_h.start(poll_h.pevents, self._poll_cb) def _install_signal_checker(self): self._socketpair = SocketPair() self._signal_checker = None if hasattr(signal, 'set_wakeup_fd') and os.name == 'posix': try: old_wakeup_fd = signal.set_wakeup_fd(self._socketpair.writer_fileno()) if old_wakeup_fd != -1: # Already set, restore it signal.set_wakeup_fd(old_wakeup_fd) self._socketpair.close() self._socketpair = None else: self._signal_checker = pyuv.util.SignalChecker(self._loop, self._socketpair.reader_fileno()) self._signal_checker.start() self._signal_checker.unref() except ValueError: self._socketpair.close() self._socketpair = None def _uninstall_signal_checker(self): if self._signal_checker: self._signal_checker.close() self._signal_checker = None if self._socketpair: self._socketpair.close() self._socketpair = None def _validate_signal(self, sig): if not isinstance(sig, int): raise TypeError('sig must be an int, not {!r}'.format(sig)) if signal is None: raise RuntimeError('Signals are not supported') if not (1 <= sig < signal.NSIG): raise ValueError('sig {} out of range(1, {})'.format(sig, signal.NSIG))
def _handle_error(self, typ, value, tb): if not issubclass(typ, SystemExit): traceback.print_exception(typ, value, tb) if issubclass(typ, (KeyboardInterrupt, SystemExit, SystemError)): assert Fiber.current() is self.task self.task.parent.throw(typ, value, tb)
def test_send_exception(self): seen = [] g1 = Fiber(target=fmain, args=(seen, )) g1.switch() self.assertRaises(KeyError, send_exception, g1, KeyError) self.assertEqual(seen, [KeyError])
def runner(x): g = Fiber(lambda: time.sleep(x)) g.switch()
def creator(): g = Fiber(worker) g.switch() result.append(g)
def _dead_fiber(): g = Fiber(lambda: None) g.switch() return g
def __init__(self, *args, **kwds): self.args = args self.kwds = kwds Fiber.__init__(self, target=self.run)
def h(): lst.append(1) i = Fiber(f) i.switch() lst.append(1)
def test_two_children(self): lst = [] def f(): lst.append(1) current().parent.switch() lst.extend([1, 1]) g = Fiber(f) h = Fiber(f) g.switch() self.assertEqual(len(lst), 1) h.switch() self.assertEqual(len(lst), 2) h.switch() self.assertEqual(len(lst), 4) self.assertEqual(h.is_alive(), False) g.switch() self.assertEqual(len(lst), 6) self.assertEqual(g.is_alive(), False)