1
0

hww hidapi usage: try to mitigate some thread-safety issues

related: #6097
This commit is contained in:
SomberNight
2020-04-17 19:05:56 +02:00
parent 98d2ab5bd6
commit 2cfa3bd6c8
6 changed files with 66 additions and 25 deletions

View File

@@ -30,6 +30,8 @@ import threading
import sys
from typing import (NamedTuple, Any, Union, TYPE_CHECKING, Optional, Tuple,
Dict, Iterable, List, Sequence)
import concurrent
from concurrent import futures
from .i18n import _
from .util import (profiler, DaemonThread, UserCancelled, ThreadJob, UserFacingException)
@@ -321,6 +323,20 @@ class HardwarePluginToScan(NamedTuple):
PLACEHOLDER_HW_CLIENT_LABELS = {None, "", " "}
# hidapi is not thread-safe
# see https://github.com/signal11/hidapi/issues/205#issuecomment-527654560
# https://github.com/libusb/hidapi/issues/45
# https://github.com/signal11/hidapi/issues/45#issuecomment-4434598
# https://github.com/signal11/hidapi/pull/414#issuecomment-445164238
# It is not entirely clear to me, exactly what is safe and what isn't, when
# using multiple threads...
# For now, we use a dedicated thread to enumerate devices (_hid_executor),
# and we synchronize all device opens/closes/enumeration (_hid_lock).
# FIXME there are still probably threading issues with how we use hidapi...
_hid_executor = None # type: Optional[concurrent.futures.Executor]
_hid_lock = threading.Lock()
class DeviceMgr(ThreadJob):
'''Manages hardware clients. A client communicates over a hardware
channel with the device.
@@ -367,9 +383,15 @@ class DeviceMgr(ThreadJob):
# locks: if you need to take multiple ones, acquire them in the order they are defined here!
self._scan_lock = threading.RLock()
self.lock = threading.RLock()
self.hid_lock = _hid_lock
self.config = config
global _hid_executor
if _hid_executor is None:
_hid_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1,
thread_name_prefix='hid_enumerate_thread')
def with_scan_lock(func):
def func_wrapper(self: 'DeviceMgr', *args, **kwargs):
with self._scan_lock:
@@ -636,7 +658,15 @@ class DeviceMgr(ThreadJob):
except ImportError:
return []
hid_list = hid.enumerate(0, 0)
def hid_enumerate():
with self.hid_lock:
return hid.enumerate(0, 0)
hid_list_fut = _hid_executor.submit(hid_enumerate)
try:
hid_list = hid_list_fut.result()
except (concurrent.futures.CancelledError, concurrent.futures.TimeoutError) as e:
return []
devices = []
for d in hid_list: