From: Brian Candler Date: 2004-09-03T03:54:55+09:00 Subject: Bug? Serial ports and threads don't mix [repost, looks like it didn't make it through the first time] Hello, I have come across a strange problem with using serial ports under Ruby, which comes to light when you try to use a ruby thread (even if this is the only thread which is active). The attached program demonstrates the problem; it sends three bytes down the serial port, then waits for three bytes to be received and displays them. At the end, you'll see two very similar ways of starting the object. If you use a = SerialTest.new('/dev/ttyS0') a.test then it works; but if you use t = Thread.new do b = SerialTest.new('/dev/ttyS0') b.test end t.join then it doesn't, although clearly the two progams should behave identically. Note that I'm using ruby-termios, but only to set the serial port to the desired speed characteristics; otherwise I'm just using Ruby's native I/O. What seems to happen in the second case is that no data is actually sent down the serial port, until you terminate the program with ^C. At that point the three bytes are sent. It seems to be blocking, wrongly, waiting for the port to become available. You can see I've included two different bits of code to open the serial port. The first came from the 'examples' directory in ruby-termios; for some reason it opens non-blocking and then puts blocking mode back on. The second just does a straightforward open in read-write mode. However the program seems to fail in the same way regardless of which you use. This is causing me major headaches, in particular because constructs like timeout(5) do ... end don't work - the timeout library works by starting the block within a new thread, and the port hangs when I try to perform I/O. Am I doing something stupid here? Or is there something in the ruby core which isn't behaving as advertised? Or something fundamental about opening a device in read-write mode? I was using ruby-1.8.1, but I have just upgraded to ruby-1.8.2-preview2 and the problem is still there just the same. Thanks... Brian Candler. --- 8< ------------------------------------------------------------------ #!/usr/local/bin/ruby -w # Program to demonstrate issues with serial ports and threads in Ruby # Tested under Red Hat 9.0 # ruby 1.8.1 (2003-12-25) [i686-linux] # with ruby-termios-0.9.4 # Either use a loopback cable (DB9: link pins 2-3), or connect with a # null-modem cable to another machine running a terminal emulator # set to 9600 8N1, and look for the three characters it sends (and send # three characters back to it) require 'fcntl' require 'termios' class SerialTest def initialize(devname) # Open port (version 1, taken from ruby-termios examples) @dev = open(devname, File::RDWR | File::NONBLOCK) mode = @dev.fcntl(Fcntl::F_GETFL, 0) @dev.fcntl(Fcntl::F_SETFL, mode & ~File::NONBLOCK) # Open port (version 2) #@dev = File::open(devname, File::RDWR) tio = Termios::new_termios tio.iflag = 0 tio.oflag = 0 tio.cflag = Termios::CS8 | Termios::CREAD | Termios::CLOCAL tio.lflag = 0 tio.cc[Termios::VTIME] = 0 tio.cc[Termios::VMIN] = 1 tio.ispeed = tio.ospeed = Termios::B9600 Termios::flush(@dev, Termios::TCIOFLUSH) Termios::setattr(@dev, Termios::TCSANOW, tio) end MSG_SEND = "XXX" def test sleep 0.5 @dev.write(MSG_SEND) STDERR.puts "Sent #{MSG_SEND.inspect}" r = @dev.read(3) STDERR.puts "Read #{r.inspect}" end def close @dev.close end end # This works: #a = SerialTest.new('/dev/ttyS0') #a.test #exit # But this doesn't t = Thread.new do b = SerialTest.new('/dev/ttyS0') b.test end t.join