Search code examples
pythonmultithreadinghidhidapi

Two threads, one object


I'm writing a Linux driver for a USB HID device in Python. The device has two ways it sends data, both of which are needed: feature reports (synchronous) and input reports (asynchronous). Using the hidapi Cython library I have only one instance of the device to work with, but I need to set up a listener for hid.read() that will run constantly AND allow synchronous methods to be called at will by the application to send feature reports.

Currently I have the listener in one thread and the synchronous calls in another. When I run the program, my synchronous calls are not happening, though they work fine if I never start the listener; so it appears the listener thread is taking over.

Below is the troubling piece of code:

app.py

# app.py
import threading
import time
import myhiddriver

# Code that sets mydevice

class Reader:
  def start(self, device):
    self.requests = myhiddriver.DeviceRequest(device)
    # Wait a bit before firing, for testing
    time.sleep(3)
    self.requests.request_swipe_card()

  def start_listener(self, device):
    self.listener = myhiddriver.DeviceListener(device)

reader = Reader()
thread1 = threading.Thread(target=reader.start, args=(mydevice,))
thread1.daemon = True
thread2 = threading.Thread(target=reader.start_listener, args=(mydevice,))
thread2.daemon = True
thread1.start()
thread2.start()

# Keep this puppy running
while True:
  pass

myhiddriver.py

import threading

LOCK = threading.Lock()

class DeviceRequest:
  def __init__(self, device):
    # Lock it up first
    LOCK.acquire()
    self.device = device
    LOCK.release()

  def request_swipe_card(self):
    # Lock this up too
    LOCK.acquire()
    self.device.set_feature_report(insert data here)
    LOCK.release()

class DeviceListener:
  def __init__(self, device):
    # Lock me up
    LOCK.acquire()
    self.device = device
    self.start_listener()
    LOCK.release()

  def start_listener(self):
    while True:
      # Should I be locking this up?
      LOCK.acquire()
      data = self.device.read(255)
      LOCK.release()
      if data:
        print data
      else:
        pass

My Question: Why is my synchronous call (request_swipe_card) not following through? Or, how can I better architect this to have an endless listener AND the ability to make synchronous calls on the same object?


Solution

  • from the looks of the code, it is because you are locking it up

    When the state is unlocked, acquire() changes the state to locked and returns immediately. When the state is locked, acquire() blocks until a call to release() in another thread changes it to unlocked

    here is the problem:

    class DeviceListener:
      def __init__(self, device):
        LOCK.acquire() # first you lock it up
        self.device = device
        self.start_listener()
        LOCK.release()
    
      def start_listener(self):
        while True: # because of the loop, the lock wouldn't get release even if the LOCK below doesn't exist
          LOCK.acquire() # it end up blocking here and oops, it locked up xD 
          data = self.device.read(255) # so it wouldn't be able to read here
          LOCK.release()
          if data:
            print data
          else:
            pass
    

    and when the request_swipe_card end up being call in the other thread, it end up blocking there too

      def request_swipe_card(self):
        LOCK.acquire() # right here xD
        self.device.set_feature_report(insert data here)
        LOCK.release()