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)
results in:
$ 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.