Closed whitequark closed 11 months ago
After implementing the functionality in the straightforward way (PR coming in a bit) I discovered that you also have to use the internal threading._register_atexit
function to ensure the application always actually exits on exit()
. This is illustrated using the 10 testcases below, folded into a single snippet of code:
# behavior = {} # hangs
# behavior = {"normal-atexit"} # hangs
# behavior = {"threading-atexit"} # EXITS
# behavior = {"normal-atexit", "interrupt-handler"} # hangs
# behavior = {"threading-atexit", "interrupt-handler"} # EXITS
# behavior = {"open-device"} # hangs
# behavior = {"open-device", "normal-atexit"} # hangs
# behavior = {"open-device", "threading-atexit"} # hangs
# behavior = {"open-device", "normal-atexit", "interrupt-handler"} # hangs
# behavior = {"open-device", "threading-atexit", "interrupt-handler"} # EXITS
import threading
import atexit
import usb1
class _PollerThread(threading.Thread):
def __init__(self, context):
super().__init__()
self.done = False
self.context = context
def run(self):
if "normal-atexit" in behavior:
atexit.register(self.stop)
if "threading-atexit" in behavior:
threading._register_atexit(self.stop)
print("before handleEvents loop")
while not self.done:
print("before handleEvents")
self.context.handleEvents()
print("after handleEvents")
print("after handleEvents loop")
def stop(self):
print("setting done")
self.done = True
if "interrupt-handler" in behavior:
print("interrupting event handler")
self.context.interruptEventHandler()
usb_context = usb1.USBContext()
usb_poller = _PollerThread(usb_context)
usb_poller.start()
if "open-device":
print("opening device")
usb_handle = usb_context.openByVendorIDAndProductID(0x20b7, 0x9db1) # any device works
print("exiting")
exit()
So without using threading._register_atexit
the application always hangs, while using it if there is an open device (which is GC'd during the exit process in this particular snippet, causing a libusb event that coincidentally wakes up the process) lets things work without interruptEventHandler
, and interruptEventHandler
, as expected, makes things work regardless.
@vpelletier Thanks a lot! Very quick as usual. May I have a release?
@vpelletier Thanks a lot! Very quick as usual.
Thank you for the clean patch :)
May I have a release?
Yep, working on it.
Done as 3.1.0 .
Thanks! (I think you've made a release with the changelog entry as "unreleased" which is a little amusing to me.)
The recommended way to use multithreading with libusb1 is to use a poller thread. However, this results in a hang when
exit()
is called (https://github.com/GlasgowEmbedded/glasgow/issues/413):As far as I can tell this is not feasible to fix (without adding a non-insignificant delay on shutdown) with python-libusb1 because it does not include a binding for
libusb_interrupt_event_handler()
, included since libusb 1.0.21.Could you please add this function and make a release for the Glasgow Interface Explorer project?