Using https://pypi.org/project/cproto/, attached to Chrome running headless in a Docker container, I find that it gets stuck from time to time (the example quoted below is not reliable -- you may need to run it a few times):
$ python3
Python 3.6.8 (default, Jan 14 2019, 11:02:34)
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cproto
>>> cp = cproto.CProto() # localhost:9222 == my Chrome container
>>> cp.close()
>>> exit()
^CException ignored in: <module 'threading' from '/usr/lib/python3.6/threading.py'>
Traceback (most recent call last):
File "/usr/lib/python3.6/threading.py", line 1294, in _shutdown
t.join()
File "/usr/lib/python3.6/threading.py", line 1056, in join
self._wait_for_tstate_lock()
File "/usr/lib/python3.6/threading.py", line 1072, in _wait_for_tstate_lock
elif lock.acquire(block, timeout):
KeyboardInterrupt
$
This also happens with Chrome running normally, with a UI, outside of Docker.
Irrespective of whatever nastiness Chrome might be dealing with, it seems a bit "off" that the cproto library should get itself in a knot like this.
Is there any way of forcibly quitting cproto? (Have I done something wrong above, or is this a bug?)
It looks like some non-daemon threads remain running after the main thread exits. This work around works for me (didn't find the root cause yet, need more investigation):
diff -ur cproto.orig/core/websocket.py cproto/core/websocket.py
--- cproto.orig/core/websocket.py 2021-01-04 01:24:53.000000000 +0000
+++ cproto/core/websocket.py 2021-03-10 04:10:32.228436532 +0000
@@ -38,8 +38,16 @@
def connect(self, *args, **kwargs):
super(self.__class__, self).connect(*args, **kwargs)
- threading.Thread(target=self._read_messages).start()
- threading.Thread(target=self._process_events).start()
+ # threading.Thread(target=self._read_messages).start()
+ # threading.Thread(target=self._process_events).start()
+
+ t1 = threading.Thread(target=self._read_messages)
+ t1.setDaemon(True)
+ t1.start()
+
+ t2 = threading.Thread(target=self._process_events)
+ t2.setDaemon(True)
+ t2.start()
def close(self):
# Terminates blocking `get` call on events processing thread