Serial interface timeout, again...

1,010 views
Skip to first unread message

drago....@gmail.com

unread,
Aug 31, 2013, 5:19:34 AM8/31/13
to golan...@googlegroups.com
Hello, I am sorry for bringing this topic again, but I really need to clear the things up..
First let me introduce my use case:
   I have a serial interface, which communicate with the device. The go program send something to the interface, the device responds. Since the communication is done through long wires, sometimes the device gets distorted message (CRC doesn't match), so it doesn't respond. While sending the program has to wait for a specific timeout, and repeat the message to the device.
I have read past nuts discussions but the solution..."Send to serial interface, make receiver function in separate goroutine return its data through a channel, select this channel with time.After() and you are done", doesn't sounds right.    
Doesn't the reading goroutine leaks? What about subsequent Read(), it will be concurrent with the dangling previous Read()?
Now I am thinking to fork one of the "Serial" packages, and add Poll() function to serial interface. Is this sounds right to you?

Thanks and best regards,

Dave Cheney

unread,
Aug 31, 2013, 5:32:52 AM8/31/13
to drago....@gmail.com, golang-nuts
Can you please post some code, that should be more illustrative that a
freeform description.
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.

drago....@gmail.com

unread,
Aug 31, 2013, 6:02:47 AM8/31/13
to golan...@googlegroups.com, drago....@gmail.com
Hi Dave,
Here is sample code ( the buffers are not populated). This is the "solution" I found digging this issue. I don't think it is right.

package main

import(
"io"
"time"
)

type Serialer interface {
io.ReadWriteCloser
}

func main() {
var rs232 Serialer
for {
var req []byte /// request message
rs232.Write(req)
repChan:=make(chan []byte)
go func() {
var rep []byte
/// Read will block idefinetly, and there is no way to kill this 
/// goroutine.
_,err:=rs232.Read(rep)
if err!=nil {
repChan<-nil
}
repChan<-rep
}()
select {
case <-repChan:
/// use the reply message
case <-time.After(1*time.Second):
/// Timeout
}
}
}

Erwin

unread,
Aug 31, 2013, 6:24:24 AM8/31/13
to drago....@gmail.com, golang-nuts
I had a similar problem like yours, with the serial read potentially blocking. I have written an rs232 package that supports timeouts and also a way to poll (check how many bytes are available to read). I haven't published it yet, i'm on my phone right now, but i can send you the source later if you're interested.  
By the way, can't you shorten your cabling, or use an USB serial adapter to get reliable communications?



drago....@gmail.com

unread,
Aug 31, 2013, 6:36:14 AM8/31/13
to golan...@googlegroups.com, drago....@gmail.com
Great notnot, 
I will appreciate your source code very much. You can send it on my email, or upload to github, etc.
The fact is that RS232 is transformed to industrial RS485, and the devices are rather exotic. Dropouts happen often, because of interference, faulty opto-isulator, storm, etc.
But the problem is not the communication medium, it is that Serial communication really needs its timeout logic.

Dave Cheney

unread,
Aug 31, 2013, 7:45:13 AM8/31/13
to drago....@gmail.com, golan...@googlegroups.com, drago....@gmail.com
The solution that the net package uses is to close the underlying fd, releasing any blocked reads or writes. Will this work in your case?


drago....@gmail.com

unread,
Aug 31, 2013, 7:48:10 AM8/31/13
to golan...@googlegroups.com, drago....@gmail.com
Can you point me where to look? Thanks.

Dave Cheney

unread,
Aug 31, 2013, 7:52:02 AM8/31/13
to drago....@gmail.com, golan...@googlegroups.com, drago....@gmail.com
src/pkg/net/fd_unix.go


drago....@gmail.com

unread,
Aug 31, 2013, 8:06:43 AM8/31/13
to golan...@googlegroups.com, drago....@gmail.com
Yes, I guess it should work. Basically I need polling on a fd for something to read, just as in networking.
Can I use this code in some non-hackish way?

Kyle Lemons

unread,
Aug 31, 2013, 12:02:32 PM8/31/13
to Dragomir Ivanov, golang-nuts
On Sat, Aug 31, 2013 at 5:06 AM, <drago....@gmail.com> wrote:
Yes, I guess it should work. Basically I need polling on a fd for something to read, just as in networking.
Can I use this code in some non-hackish way?

As far as I know there's no way to set a timeout on a read syscall.  You'd have to use AIO, I think, and instead of setting a deadline for read() you'd set a deadline on how long it takes to say that there is data available.

Dave Cheney

unread,
Aug 31, 2013, 9:53:23 PM8/31/13
to drago....@gmail.com, golan...@googlegroups.com, drago....@gmail.com
I think you are making this far too hard for yourself. 

Spawn a goroutine to do the Read call, return any data over a channel. If the Read returns an error, close the channel. 

I'd you want to unblock the reader, close the fd that Read is blocked on. 

Iif that is not sufficient, then go down the route of using something like select(2). 

Dave

Dave Cheney

unread,
Aug 31, 2013, 9:55:30 PM8/31/13
to Kyle Lemons, Dragomir Ivanov, golang-nuts


On 01/09/2013, at 2:02, Kyle Lemons <kev...@google.com> wrote:

On Sat, Aug 31, 2013 at 5:06 AM, <drago....@gmail.com> wrote:
Yes, I guess it should work. Basically I need polling on a fd for something to read, just as in networking.
Can I use this code in some non-hackish way?

As far as I know there's no way to set a timeout on a read syscall.  You'd have to use AIO, I think, and instead of setting a deadline for read() you'd set a deadline on how long it takes to say that there is data available.

The classic pattern is to use select(2) which will give you a timeout across all the fds in the wait set. 

Kyle Lemons

unread,
Sep 1, 2013, 3:20:16 AM9/1/13
to Dave Cheney, Dragomir Ivanov, golang-nuts
I was thinking, possibly erroneously, that there are some very sharp edges when reading from a fopen'd file (as opposed to a network socket) using at least some subset of select/epoll/kqueue.  It has been a long time since I had to do this, though, so my instruction is probably not a great guide.

Dragomir Ivanov

unread,
Sep 1, 2013, 8:29:57 AM9/1/13
to Dave Cheney, golan...@googlegroups.com
Well, that sounds very reasonable. I didn't know I could close the fd from another goroutine and it will unblock concurrent Read(). I will test it ASAP, and will use it if it works. Thanks Dave, sometimes one have to think out of the box.

Dragomir Ivanov

unread,
Sep 1, 2013, 1:31:52 PM9/1/13
to Dave Cheney, golan...@googlegroups.com
I just made an example. It doesn't work.
I couldn't do it with the "official" serial library ( it gave EOF when trying to read ), so I used Erwins. Probably the it is interface setup issue, I will dig it later. I will provide the source to the rs232 if needed, but basically here are the important functions:

type Port struct {
options Options
file    *os.File
}

// Open will open the named serial port device with the given options.
// Open returns an *rs232.Error which implements the built-in error interface
// but additionally allows access to specific error codes. See Error.
//
// See type Options for valid parameter ranges.
func Open(name string, opt Options) (port *Port, err error) {
// validate options
mrate, mformat, err := validateOptions(&opt)
if err != nil {
return nil, err
}

// open special device file
// O_NOCTTY: if the file is a tty, don't make it the controlling terminal.
file, err := os.OpenFile(
name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666)
if err != nil {
if os.IsNotExist(err) {
return nil, &Error{ERR_DEVICE, err.Error()}
} else if os.IsPermission(err) {
return nil, &Error{ERR_ACCESS, err.Error()}
} else {
return nil, &Error{ERR_PARAMS, err.Error()}
}
}
fd := file.Fd()

// timeout settings
vmin := uint8(0)
if opt.Timeout == 0 {
vmin = 1
}

// termios settings
err = setTermios(fd, mrate, mformat, vmin, opt.Timeout)
if err != nil {
file.Close()
return nil, err
}

// set device file to blocking mode
err = syscall.SetNonblock(int(fd), false)
if err != nil {
file.Close()
return nil, &Error{ERR_PARAMS, err.Error()}
}

return &Port{opt, file}, nil
}

func (p *Port) Close() error {
return p.file.Close()
}

func (p *Port) Read(b []byte) (n int, err error) {
return p.file.Read(b)
}


Here is my code:

package main

import (
"fmt"
"runtime"
"serial-test/rs232"
"time"
)

func main() {
ops := rs232.Options{}
ops.BitRate = 9600
ops.DataBits = 8
ops.StopBits = 2
ops.Parity = 0
ops.Timeout = 0 /// Not using timeout functionality 
port, err := rs232.Open("/dev/pts/5", ops)
if err != nil {
fmt.Println(err)
return
}

for {
fmt.Println("NumGoroutine=", runtime.NumGoroutine())
repChan := make(chan []byte)

go func() {
rep := make([]byte, 5)

n, err := port.Read(rep)
if err != nil {
repChan <- nil
fmt.Println(err)
} else {
fmt.Println(n)
repChan <- rep
}

fmt.Println("DOES Exit")
}()

select {
case rep := <-repChan:
/// use the reply message
if rep != nil {
fmt.Print("Data: ")
fmt.Println(rep)
}

case <-time.After(5 * time.Second):
port.Close()
close(repChan)
fmt.Println("Timeout")

var err error
port, err = rs232.Open("/dev/pts/5", ops)
if err != nil {
fmt.Println(err)
return
}
}
}
return
}


On Sun, Sep 1, 2013 at 4:53 AM, Dave Cheney <da...@cheney.net> wrote:

Dragomir Ivanov

unread,
Sep 1, 2013, 1:33:33 PM9/1/13
to Dave Cheney, golan...@googlegroups.com
The output of the code fragment above is:
NumGoroutine= 2
Timeout
NumGoroutine= 4
Timeout
NumGoroutine= 5
Timeout
NumGoroutine= 6
Timeout
NumGoroutine= 7
Timeout
NumGoroutine= 8

Dave Cheney

unread,
Sep 1, 2013, 4:46:33 PM9/1/13
to Dragomir Ivanov, golan...@googlegroups.com
Does the rs232.Open function return a *os.File? It does not look like it. 

You should reread the contract for io.Reader, specifically you need to handle the n bytes of data before handling the error, otherwise you loose data. 

Also, only the goroutine that owns repChan may close it, or your program will panic. This does not happen yet as your port.Read always blocks, which is why the goroutine count goes up. 

Dragomir Ivanov

unread,
Sep 1, 2013, 5:20:54 PM9/1/13
to Dave Cheney, golan...@googlegroups.com
1) Well Open returns Port struct, which contains *os.File. I guess at this R&D point complying to specific interface is not an issue. Later this can be fixed.
2) This is just prototype to test your idea. Handling the buffers the right way is reserved for the actual implementation.
3) Closing the repChan explicitly can be safely omitted. 
Is there anything else which can prevent Read from being unblocked?

Dave Cheney

unread,
Sep 1, 2013, 6:14:52 PM9/1/13
to Dragomir Ivanov, golan...@googlegroups.com
Ill try to work up a prototype using select(2) 

Dragomir Ivanov

unread,
Sep 1, 2013, 6:16:50 PM9/1/13
to Dave Cheney, golan...@googlegroups.com
Dave, select will probably work. I was hoping that I missed something in my prototype.
Your idea looked really Golang-way...and I liked it.

Dave Cheney

unread,
Sep 1, 2013, 8:33:16 PM9/1/13
to Dragomir Ivanov, golan...@googlegroups.com
Looks like there are some problems with closing a file descriptor from
another thread. POSIX only guarentees this will unblock all waiting
threads (i mean thread, not goroutine) if they are socket fds. I'm
trying the select route now.

Dragomir Ivanov

unread,
Sep 2, 2013, 5:18:31 AM9/2/13
to Dave Cheney, golan...@googlegroups.com
Please, write here about your findings.

distributed

unread,
Sep 2, 2013, 7:53:32 AM9/2/13
to golan...@googlegroups.com, Dave Cheney, drago....@gmail.com
What I typically do in these cases is this. I spawn a "read satellite" goroutine which runs concurrently with my main code, reads from the serial port and sends its read data over a channel to my main code. Usually the read satellite does not fill the channel with "raw bytes" from the serial port, but with some kind of abstracted data, typically one "frame" or "message" that was received from the serial port. The difference between my read satellite and your code is that I only ever have _one_ goroutine running and thus I don't need to care a lot about shutting down the reader. Except of course during shutdown, but not continually during normal operation ;) With a read satellite set up like this, I believe you can just do a select over the read satellite's result channel and the timeout and you'll be fine. Of course the read satellite has to be able to handle all your messages to some degree since it now is the only thing that is responsible and allowed to read from the port.
Reply all
Reply to author
Forward
0 new messages