import weakref import gevent from gevent.event import Event, AsyncResult import gevent.testing as greentest from gevent.testing.timing import AbstractGenericGetTestCase from gevent.testing.timing import AbstractGenericWaitTestCase from gevent.testing.timing import SMALL_TICK from gevent.testing.timing import SMALL_TICK_MAX_ADJ DELAY = SMALL_TICK + SMALL_TICK_MAX_ADJ class TestEventWait(AbstractGenericWaitTestCase): def wait(self, timeout): Event().wait(timeout=timeout) def test_cover(self): str(Event()) class TestGeventWaitOnEvent(AbstractGenericWaitTestCase): def wait(self, timeout): gevent.wait([Event()], timeout=timeout) def test_set_during_wait(self): # https://github.com/gevent/gevent/issues/771 # broke in the refactoring. we must not add new links # while we're running the callback event = Event() def setter(): event.set() def waiter(): s = gevent.spawn(setter) # let the setter set() the event; # when this method returns we'll be running in the Event._notify_links callback # (that is, it switched to us) res = event.wait() self.assertTrue(res) self.assertTrue(event.ready()) s.join() # make sure it's dead # Clear the event. Now we can't wait for the event without # another set to happen. event.clear() self.assertFalse(event.ready()) # Before the bug fix, this would return "immediately" with # event in the result list, because the _notify_links loop would # immediately add the waiter and call it o = gevent.wait((event,), timeout=0.01) self.assertFalse(event.ready()) self.assertNotIn(event, o) gevent.spawn(waiter).join() class TestAsyncResultWait(AbstractGenericWaitTestCase): def wait(self, timeout): AsyncResult().wait(timeout=timeout) class TestWaitAsyncResult(AbstractGenericWaitTestCase): def wait(self, timeout): gevent.wait([AsyncResult()], timeout=timeout) class TestAsyncResultGet(AbstractGenericGetTestCase): def wait(self, timeout): AsyncResult().get(timeout=timeout) class MyException(Exception): pass class TestAsyncResult(greentest.TestCase): def test_link(self): ar = AsyncResult() self.assertRaises(TypeError, ar.rawlink, None) ar.unlink(None) # doesn't raise ar.unlink(None) # doesn't raise str(ar) # cover def test_set_exc(self): log = [] e = AsyncResult() self.assertEqual(e.exc_info, ()) self.assertEqual(e.exception, None) def waiter(): with self.assertRaises(MyException) as exc: e.get() log.append(('caught', exc.exception)) gevent.spawn(waiter) obj = MyException() e.set_exception(obj) gevent.sleep(0) self.assertEqual(log, [('caught', obj)]) def test_set(self): event1 = AsyncResult() timer_exc = MyException('interrupted') # Notice that this test is racy: # After DELAY, we set the event. We also try to immediately # raise the exception with a timer of 0 --- but that depends # on cycling the loop. Hence the fairly large value for DELAY. g = gevent.spawn_later(DELAY, event1.set, 'hello event1') self._close_on_teardown(g.kill) with gevent.Timeout.start_new(0, timer_exc): with self.assertRaises(MyException) as exc: event1.get() self.assertIs(timer_exc, exc.exception) def test_set_with_timeout(self): event2 = AsyncResult() X = object() result = gevent.with_timeout(DELAY, event2.get, timeout_value=X) self.assertIs( result, X, 'Nobody sent anything to event2 yet it received %r' % (result, )) def test_nonblocking_get(self): ar = AsyncResult() self.assertRaises(gevent.Timeout, ar.get, block=False) self.assertRaises(gevent.Timeout, ar.get_nowait) class TestAsyncResultCrossThread(greentest.TestCase): def _makeOne(self): return AsyncResult() def _setOne(self, one): one.set('from main') BG_WAIT_DELAY = 60 def _check_pypy_switch(self): # On PyPy 7.3.3, switching to the main greenlet of a thread from a # different thread silently does nothing. We can't detect the cross-thread # switch, and so this test breaks # https://foss.heptapod.net/pypy/pypy/-/issues/3381 if greentest.PYPY: import sys if sys.pypy_version_info[:3] <= (7, 3, 3): # pylint:disable=no-member self.skipTest("PyPy bug: https://foss.heptapod.net/pypy/pypy/-/issues/3381") @greentest.ignores_leakcheck def test_cross_thread_use(self, timed_wait=False, wait_in_bg=False): # Issue 1739. # AsyncResult has *never* been thread safe, and using it from one # thread to another is not safe. However, in some very careful use cases # that can actually work. # # This test makes sure it doesn't hang in one careful use # scenario. self.assertNotMonkeyPatched() # Need real threads, event objects from threading import Thread as NativeThread from threading import Event as NativeEvent if not wait_in_bg: self._check_pypy_switch() test = self class Thread(NativeThread): def __init__(self): NativeThread.__init__(self) self.daemon = True self.running_event = NativeEvent() self.finished_event = NativeEvent() self.async_result = test._makeOne() self.result = '' def run(self): # Give the loop in this thread something to do g_event = Event() def spin(): while not g_event.is_set(): g_event.wait(DELAY * 2) glet = gevent.spawn(spin) def work(): self.running_event.set() # If we use a timed wait(), the bug doesn't manifest. # This is probably because the loop wakes up to handle the timer, # and notices the callback. # See https://github.com/gevent/gevent/issues/1735 if timed_wait: self.result = self.async_result.wait(test.BG_WAIT_DELAY) else: self.result = self.async_result.wait() if wait_in_bg: # This results in a separate code path worker = gevent.spawn(work) worker.join() del worker else: work() g_event.set() glet.join() del glet self.finished_event.set() gevent.get_hub().destroy(destroy_loop=True) thread = Thread() thread.start() try: thread.running_event.wait() self._setOne(thread.async_result) thread.finished_event.wait(DELAY * 5) finally: thread.join(DELAY * 15) self._check_result(thread.result) def _check_result(self, result): self.assertEqual(result, 'from main') def test_cross_thread_use_bg(self): self.test_cross_thread_use(timed_wait=False, wait_in_bg=True) def test_cross_thread_use_timed(self): self.test_cross_thread_use(timed_wait=True, wait_in_bg=False) def test_cross_thread_use_timed_bg(self): self.test_cross_thread_use(timed_wait=True, wait_in_bg=True) @greentest.ignores_leakcheck def test_cross_thread_use_set_in_bg(self): self.assertNotMonkeyPatched() # Need real threads, event objects from threading import Thread as NativeThread from threading import Event as NativeEvent self._check_pypy_switch() test = self class Thread(NativeThread): def __init__(self): NativeThread.__init__(self) self.daemon = True self.running_event = NativeEvent() self.finished_event = NativeEvent() self.async_result = test._makeOne() self.result = '' def run(self): self.running_event.set() test._setOne(self.async_result) self.finished_event.set() gevent.get_hub().destroy(destroy_loop=True) thread = Thread() glet = None try: glet = gevent.spawn(thread.start) result = thread.async_result.wait(self.BG_WAIT_DELAY) finally: thread.join(DELAY * 15) if glet is not None: glet.join(DELAY) self._check_result(result) @greentest.ignores_leakcheck def test_cross_thread_use_set_in_bg2(self): # Do it again to make sure it works multiple times. self.test_cross_thread_use_set_in_bg() class TestEventCrossThread(TestAsyncResultCrossThread): def _makeOne(self): return Event() def _setOne(self, one): one.set() def _check_result(self, result): self.assertTrue(result) class TestAsyncResultAsLinkTarget(greentest.TestCase): error_fatal = False def test_set(self): g = gevent.spawn(lambda: 1) s1, s2, s3 = AsyncResult(), AsyncResult(), AsyncResult() g.link(s1) g.link_value(s2) g.link_exception(s3) self.assertEqual(s1.get(), 1) self.assertEqual(s2.get(), 1) X = object() result = gevent.with_timeout(DELAY, s3.get, timeout_value=X) self.assertIs(result, X) def test_set_exception(self): def func(): raise greentest.ExpectedException('TestAsyncResultAsLinkTarget.test_set_exception') g = gevent.spawn(func) s1, s2, s3 = AsyncResult(), AsyncResult(), AsyncResult() g.link(s1) g.link_value(s2) g.link_exception(s3) self.assertRaises(greentest.ExpectedException, s1.get) X = object() result = gevent.with_timeout(DELAY, s2.get, timeout_value=X) self.assertIs(result, X) self.assertRaises(greentest.ExpectedException, s3.get) class TestEvent_SetThenClear(greentest.TestCase): N = 1 def test(self): e = Event() waiters = [gevent.spawn(e.wait) for i in range(self.N)] gevent.sleep(0.001) e.set() e.clear() for greenlet in waiters: greenlet.join() class TestEvent_SetThenClear100(TestEvent_SetThenClear): N = 100 class TestEvent_SetThenClear1000(TestEvent_SetThenClear): N = 1000 class TestWait(greentest.TestCase): N = 5 count = None timeout = 1 period = timeout / 100.0 def _sender(self, events, asyncs): while events or asyncs: gevent.sleep(self.period) if events: events.pop().set() gevent.sleep(self.period) if asyncs: asyncs.pop().set() @greentest.skipOnAppVeyor("Not all results have arrived sometimes due to timer issues") def test(self): events = [Event() for _ in range(self.N)] asyncs = [AsyncResult() for _ in range(self.N)] max_len = len(events) + len(asyncs) sender = gevent.spawn(self._sender, events, asyncs) results = gevent.wait(events + asyncs, count=self.count, timeout=self.timeout) if self.timeout is None: expected_len = max_len else: expected_len = min(max_len, self.timeout / self.period) if self.count is None: self.assertTrue(sender.ready(), sender) else: expected_len = min(self.count, expected_len) self.assertFalse(sender.ready(), sender) sender.kill() self.assertEqual(expected_len, len(results), (expected_len, len(results), results)) class TestWait_notimeout(TestWait): timeout = None class TestWait_count1(TestWait): count = 1 class TestWait_count2(TestWait): count = 2 class TestEventBasics(greentest.TestCase): def test_weakref(self): # Event objects should allow weakrefs e = Event() r = weakref.ref(e) self.assertIs(e, r()) del e del r def test_wait_while_notifying(self): # If someone calls wait() on an Event that is # ready, and notifying other waiters, that new # waiter still runs at the end, but this does not # require a trip around the event loop. # See https://github.com/gevent/gevent/issues/1520 event = Event() results = [] def wait_then_append(arg): event.wait() results.append(arg) gevent.spawn(wait_then_append, 1) gevent.spawn(wait_then_append, 2) gevent.idle() self.assertEqual(2, event.linkcount()) check = gevent.get_hub().loop.check() check.start(results.append, 4) event.set() wait_then_append(3) self.assertEqual(results, [1, 2, 3]) # Note that the check event DID NOT run. check.stop() check.close() def test_gevent_wait_twice_when_already_set(self): event = Event() event.set() # First one works fine. result = gevent.wait([event]) self.assertEqual(result, [event]) # Second one used to fail with an AssertionError, # now it passes result = gevent.wait([event]) self.assertEqual(result, [event]) del AbstractGenericGetTestCase del AbstractGenericWaitTestCase if __name__ == '__main__': greentest.main()