christopheraue/ruby-evdev

handle_event blocks main thread even when looped in its own thread

Opened this issue · 1 comments

alxx commented

Hi, even if I loop in a separate thread, handle_event will block the main one. Here's my code:

require 'evdev'

re = Evdev.new('/dev/input/event0')

re.on(:REL_X) { |val| puts "val: #{val}" }

Thread.new do
  loop do
    begin
      re.handle_event
    rescue Evdev::AwaitEvent
      Kernel.select([re.event_channel])
      retry
    end
  end
end

i = 0
until i > 50
  puts i
  i += 1
  sleep 0.2
end

The way I figure, it should count to 50, during which if I move my rotary encoder it should display the value (given my system configuration, -1 or 1). Instead, the main thread is blocked and the counter only increases if I unblock it by feeding it events (ie rotating my rotary encoder).

$ ruby re-test.rb 
WARN: Unresolved specs during Gem::Specification.reset:
      linux_input (~> 1.0)
WARN: Clearing out unresolved specs.
Please report a bug if this causes problems.
0
1
val: 1
val: 1
2
val: 1
3
val: -1
val: -1
4
val: -1
5
val: -1
6
val: 1
val: 1
val: 1
7
val: 1
8
val: -1
val: -1
val: -1
9
val: -1
10
val: 1
val: 1
val: 1
val: 1
^C^C^C^C^C # then I moved the rotary encoder and fed it an event
Traceback (most recent call last):
re-test.rb: Interrupt

It even traps SIGINT so I can't stop my program with Ctrl-C until I unblock handle_event by rotating the encoder.

I've tried all possible parameters for handle_event: the default (:blocking) but also :normal, :sync and :force_sync.

How can I listen to events while I'm doing other stuff?

Try replacing the loop in your Thread:

require 'evdev'

re = Evdev.new('/dev/input/event0')

re.on(:REL_X) { |val| puts "val: #{val}" }

Thread.new do
  loop do
    while re.events_pending? do
      re.handle_event
    end
    Kernel.select([re.event_channel])
  end
end

i = 0
until i > 50
  puts i
  i += 1
  sleep 0.2
end

I completely removed the rescue for Evdev::AwaitEvent, because as far as I can tell it never happens. handle_event seems to completely block other threads so I only call it if I know I have events_pending?. Kernel.select seems to wait, but does not block other threads. I think this does what you are looking for.

[Update: Replaced if with while. I think that should do it.]