hparra / ruby-serialport

ruby-serialport is a Ruby library that provides a class for using RS-232 serial ports
http://rubygems.org/gems/serialport
GNU General Public License v2.0
246 stars 58 forks source link

Multithreaded read_timeout? #25

Open stegmannt opened 13 years ago

stegmannt commented 13 years ago

I just ran into the problem with sp.getc not timing out if it's used in a new Thread.

I've tried using Timeout::timeout() but I guess this will break at least on multicore systems, if the block gets killed while getc is processing the input.

Is there any way to workaround/fix this?

hparra commented 13 years ago

I don't know. I use serialport in a multithreaded environment as well. Are you using Ruby 1.9?

stefanlindbohm commented 12 years ago

I'm having the same issue on OS X Lion with both 1.8.7 and 1.9.3. Writes works but reads doesn't seem to work at all, they just get stuck even if there's data to be read.

hparra commented 12 years ago

I had a similar problem in Windows using 1.8, but I haven't experienced it since. Particular examples?

davidkirwan commented 11 years ago

Hi there,

I'm using SerialPort version 1.1.0, on Ruby 1.9.3p327 on an Archlinux machine running the 3.6.6-1 kernel. I'm using the SerialPort gem within a loop running inside a thread:

device = "/dev/ttyUSB0"
speed = 9600
timeout = 100

sp = SerialPort.new(device, speed)
sp.read_timeout = timeout

t = Thread.new do
  while(true)
    value = sp.read
    puts value
  end
end

gets
t.kill
sp.close

It seems the second I attempt to run the SerialPort code within a thread, or attempt to use other ruby classes which make use of threads, the system appears to go into read_timeout and hangs..

I've also tried to use the rufus-scheduler gem to attempt the following:

scheduler.every '1s' do

  device = "/dev/ttyUSB0"
  speed = 9600
  timeout = 100

  sp = SerialPort.new(device, speed)
  sp.read_timeout = timeout 

  value = sp.read
  puts value

end

Not sure what else to try :/ any ideas ?

GarthSnyder commented 11 years ago

I'm seeing a similar lockup on sp.read from a second thread, but in my case it does not appear to matter what the read_timeout value is set to (negative, zero, or positive). The system is Solaris Express 11 running MRI 1.9.2p290, and ruby-serialport compiles and installs without issue.

Here's some code that demonstrates the issue:

require 'serialport'
require 'thread'

rfid = SerialPort.new("/dev/term/0", 9600, 7, 1, SerialPort::NONE)

def read_tags(port)
  while 1
    puts "Selecting..."
    IO::select([port])
    puts "Reading..."
    segment = port.read 
    puts "Segment: #{segment}"
  end
end

Thread.new do
  read_tags rfid
end

while 1
  sleep 100
end

If you remove the Thread.new wrapper around "read_tags rfid", the reading loop works as you'd expect. But as written, with the loop running in a separate thread, the IO::select call works correctly (the thread pauses at the select until the remote serial peer produces output) but the port.read following it hangs indefinitely without ever returning any data. (The select is not necessary to reproduce the problem; it's just there to verify the presence of readable data.)

It does not matter which thread executes which component. Putting the sleep loop into a thread and running read_tags on the main thread produces the same behavior. Whatever the underlying issue is, it seems to be triggered just by the existence of multiple threads.

I'm afraid I don't have experience writing extensions for Ruby, but looking over the C code I get the impression that sp.read is inherited directly from IO and that there's nothing ruby-serialport could be actively doing to create a deadlock. So I'm not convinced that the issue is in ruby-serialport. But since it does affect use of the library and may be part of a pattern, I thought I'd mention it here.

GarthSnyder commented 11 years ago

Regarding the above, everything works fine for me on ruby-2.0.0-p195. There was substantial revision in the IO library implementation in MRI coming into 2.0.0, so I assume that whatever the underlying issue was, it was addressed by those patches. Again, this is under Solaris, so YMMV.

davidkirwan commented 11 years ago

Very interesting, thanks Garth I'll give this a go later today :)

davidkirwan commented 11 years ago

:+1: working now!

GarthSnyder commented 11 years ago

Thanks for the update!

ghost commented 11 years ago

could this issue be closed now then?

GarthSnyder commented 11 years ago

There's probably a legitimate issue in MRI 1.9, but I doubt that ruby-serialport is implicated.

ghost commented 11 years ago

@hparra : it sounds like this can be closed now.

kd-2020 commented 10 years ago

I'm having the same issues. Running windows 7, ruby 2.0.0p481 32bit, serialport-1.3.0. I have tried ruby 1.9 and 2.0 64bit.

My script hangs when I try to write after I set up a read on another thread, unless I set a read timeout, in which case it wont hang but still doesn't read any thing at all.

My code WILL WORK if the serial device (arduino) is looping a char every second or so. It seems to keep the serialport rolling along. It seems like some sort of read or write buffer issue. IE from the arduino side:

void setup() {
  Serial.begin(9600);
}

void loop() {
  //Serial.print("X");  // WITH THESE TWO LINES UNCOMMENTED EVERYTHING WORKS
  //delay(500);         //    Commented out and the serialport gem will hang
  while (Serial.available())
    ruby::rx(Serial.read(), processCmd); // this bit just (for now) just spits back
                                                              //    what it recieves, does work when 
                                                              //    above lines are not commented out.
}

I'm confident the micro controller code isn't the issue, just posting the above to try and make it clear what I'm talking about in the paragraph above.

Here's the test ruby script I'm working with:

class Arduino
  def initialize(port="COM3", baud=9600)
    @serial_port = SerialPort.new(port, baud)#, 8, 1, SerialPort::NONE)
    sleep 5
  end

  def write(cmd)
    @serial_port.syswrite cmd
  end

  def read
    @read_thread = Thread.new do
      loop do
        c = @serial_port.read
        if c
          yield c
        end
        sleep 0.005
      end
    end
  end
end

jig = Arduino.new

jig.read do |c|
  print c
end

puts "Ready..."

for i  in 0..3
  x = gets
  puts "Sending message"
  jig.write "Hello"
  puts "Sent message"
end

gets

Again, with periodic 'x' chars from the arduino I get the 'x's and the expected response, with out the periodic 'x's my ruby script will hang on write.

kd-2020 commented 10 years ago

By the way, just tested my code and it works as expected in Linux.