Bug? Serial ports and threads don't mix

B

Brian Candler

[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::eek:pen(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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top