greenlet.error not raised switching between threads for main greenlet
Consider this code snippet. It starts a background thread and exposes an attribute that allows switching to the main greenlet of the background thread. The main thread then attempts to call this switch method.
# bad_switch.py from time import sleep as native_sleep from threading import Thread as NativeThread from threading import Event as NativeEvent import greenlet class Thread(NativeThread): def __init__(self): NativeThread.__init__(self) self.switch_to_greenlet = None self.running = NativeEvent() self.daemon = True def run(self): self.switch_to_greenlet = greenlet.getcurrent().switch def bg(): self.running.set() native_sleep(10) glet = greenlet.greenlet(bg) glet.switch() print('T2: Resumed') t = Thread() t.start() t.running.wait() t.switch_to_greenlet() # line 29
Under CPython with any version of greenlet, this results in the main thread raising an exception:
$ python bad_switch.py Traceback (most recent call last): File "/private/tmp/bad_switch.py", line 29, in <module> t.switch_to_greenlet() greenlet.error: cannot switch to a different thread
Under PyPy, however, nothing appears to happen. The main thread raises no exception, but neither is the greenlet resumed ("T2: Resumed" is not printed). I've tested this with PyPy 2.7-7.3.3 (macOS and Linux), PyPy 3.7-7.3.3 (mac only) and pypy-c-jit-101407-060e8d505b33-osx64.
The same thing happens if the
bg greenlet is deleted and the background thread runs all the code itself in the main greenlet.
PyPy does raise the expected error if you attempt to switch to a non-main greenlet in a different thread; defining
run like so:
def run(self): run_main = greenlet.getcurrent().switch def bg(): print("T2G2: running; switching to main") run_main() print("t2g2: resumed") native_sleep(10) glet = greenlet.greenlet(bg) self.switch_to_greenlet = glet.switch glet.switch() print('T2G1: Resumed') self.running.set() native_sleep(10)
$ pypy3 bad_switch.py T2G2: running; switching to main T2G1: Resumed Traceback (most recent call last): File "bad_switch.py", line 35, in <module> t.switch_to_greenlet() File "//pypy3/lib_pypy/greenlet.py", line 54, in switch return self.__switch('switch', (args, kwds)) File "//pypy3/lib_pypy/greenlet.py", line 93, in __switch args, kwds = unbound_method(current, *baseargs, to=target) _continuation.error: inter-thread support is missing
This comes up in the context of gevent, where I've had a bug report that switching between different threads like in the first example just hangs. On CPython, this was a bug where gevent was silently catching and ignoring the
greenlet.error but on PyPy, because there's no error and nothing seems to happen when you
switch(), I don't know how to fix, or even detect, the hang.