I'm just getting started with Go (love it so far), so bear with me
please! I'm doing a lot of work with network I/O, so obviously
handling timeouts and errors is important to me.
I've read the blog post
(http://blog.golang.org/2010/09/go-concurrency-patterns-timing-out-and.html)
around concurrency, and have got that all working fairly nicely with
the code below (irrelevant parts snipped out). However, I have a
couple of questions:
1) Is this the right way to handle timing out when working with
blocking system calls (such as net.Conn.Read)? I appreciate that
setting a socket read timeout would be better, but that's not an
option with this application I'm afraid (the handshake has a timeout,
but everything after that must not have a fixed timeout).
2) It would be nice in getRequestInternal if I could return a
meaningful error back to the calling function (at the moment I'm just
passing back nil if there was an error). If I were calling this method
synchronously then I'd use the multiple return feature, but how can
this same effect be achieved if I'm returning the result back via a
channel? I could create a new type that enclosed a Request and an
os.Error, but that seems inelegant. Any suggestions?
Thanks,
Sam
// The publicly exposed GetRequest function
// Note that the structure of Request is irrelevant (it just has a few
strings and int32s)
func GetRequest(c net.Conn, timeout int64) (req *Request) {
creq := make(chan *Request, 1)
cto := make(chan bool, 1)
go func() {
time.Sleep(timeout)
cto <- true
}()
go getRequestInternal(c, creq)
select {
case req = <- creq:
fmt.Fprintf(os.Stderr, "Got request item back\n")
case <- cto:
fmt.Fprintf(os.Stderr, "Hit timeout\n")
}
return req
}
func getRequestInternal(c net.Conn, cr chan *Request) {
req := new(Request)
// parse the request from c.Read(...)
if ok {
c <- req
} else {
c <- nil
}
}
Can't you just set the timeout before the handshake, and then re-set
it afterward?
Andrew
func readRequestOrTimeout(socket *net.TCPConn) *http.Request {
timeout := time.Tick(10e9) // ten seconds to comply
c := make(chan *http.Request)
select {
case request := <- readRequest(socket, c):
return request
case <- timeout:
socket.Close()
return nil
}
}
func readRequest(socket *net.TCPConn) *http.Request {
for {
// read socket
// loop if not enough data
}
}
Cheers
Dave
oops that should have been
func readRequest(socket *net.TCPConn, c chan *http.Request) [
for {
// read socket
// loop if not enough data
}
c <- request
Thanks, but unless I'm missing something that's fundamentally the same
code as I included in my original message. The key thing I am looking
for is how to sensibly return an os.Error from the readRequest
function (to use your code as an example). Simply returning nil via
the channel is not that great - it doesn't tell the calling function
anything about the nature of the issue - was it a EOF (i.e. clean
close) or some socket error?
Thanks,
Sam
It does look like I ended up implementing your initial program in an
attempt to solve your previous response.
If the goal is to avoid being Solaris'ed does the response state
matter? Either a full request was returned within the timeout window,
or the connection closed and the resources released. If your goal is
to track the detailed error code, then at the moment you'll probably
have to implement a tuple struct to track both the response and an
possible error code.
Another possibility might be returning the tuple at your top level
function, something like this.
func GetRequest(c net.Conn, timeout int64) (req *Request, err os.Error) {
...
select {
case req = <- creq:
fmt.Fprintf(os.Stderr, "Got request item back\n")
case <- cto:
err = fmt.Errorf("Timeout waiting for requiest")
fmt.Fprintf(os.Stderr, "Hit timeout\n")
}
return
}
as the <- cto channel will only fire if there is a timeout.
Cheers
Dave
So yes, it sounds like I'll have to use a tuple struct to wrap the
Request and os.Error objects before passing the result down the
channel. Of course, I'd unwrap these in the GetRequest function and
then make use of multiple return.
I was hoping there may be a more generic way of achieving this, as I
can see myself repeating this pattern in numerous places (basically
anywhere where I need to use I/O timeouts), but it's no major
headache.
Thanks,
Sam
Sent from my BlackBerry® wireless device
You could make the channel a chan interface{}, and then send either a
request value or an os.Error value.
On the other side, you can use a type switch to determine which event
has occurred:
v := <-ch
switch u := v.(type) {
case *Request:
// do something with the request, u
case os.Error:
// do something with the error, u
}
Andrew