aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Gallant <jamslam@gmail.com>2015-04-26 19:04:54 -0400
committerPřemysl Janouch <p@janouch.name>2018-09-08 16:49:24 +0200
commit5451e59f884298fdf97ef4420a7bc1001093c3b7 (patch)
treed205e50500bb56003ce3116b15942d9853d4884a
parentdd00568d44383890cff03d566c014252e096fbfc (diff)
downloadhaven-5451e59f884298fdf97ef4420a7bc1001093c3b7.tar.gz
haven-5451e59f884298fdf97ef4420a7bc1001093c3b7.tar.xz
haven-5451e59f884298fdf97ef4420a7bc1001093c3b7.zip
Remove panics/fatal errors.
Fixes #9. This makes shutdown a little more graceful, but there's more work to be done here. Namely, all outstanding cookies need to be given the error, otherwise they will block forever.
-rw-r--r--nexgb/cookie.go3
-rw-r--r--nexgb/xgb.go65
2 files changed, 28 insertions, 40 deletions
diff --git a/nexgb/cookie.go b/nexgb/cookie.go
index 6b1e4fb..d5cdb29 100644
--- a/nexgb/cookie.go
+++ b/nexgb/cookie.go
@@ -99,7 +99,6 @@ func (c Cookie) replyChecked() ([]byte, error) {
case err := <-c.errorChan:
return nil, err
}
- panic("unreachable")
}
// replyUnchecked waits for a response on either the replyChan or pingChan
@@ -123,7 +122,6 @@ func (c Cookie) replyUnchecked() ([]byte, error) {
case <-c.pingChan:
return nil, nil
}
- panic("unreachable")
}
// Check is used for checked requests that have no replies. It is a mechanism
@@ -164,5 +162,4 @@ func (c Cookie) Check() error {
case <-c.pingChan:
return nil
}
- panic("unreachable")
}
diff --git a/nexgb/xgb.go b/nexgb/xgb.go
index e7f2411..487ae16 100644
--- a/nexgb/xgb.go
+++ b/nexgb/xgb.go
@@ -331,7 +331,10 @@ func (c *Conn) sendRequests() {
// Note that we circumvent the request channel, because we're *in*
// the request channel.
if len(c.cookieChan) == cookieBuffer-1 {
- c.noop()
+ if err := c.noop(); err != nil {
+ // Shut everything down.
+ break
+ }
}
req.cookie.Sequence = c.newSequenceId()
close(req.seq)
@@ -340,26 +343,31 @@ func (c *Conn) sendRequests() {
}
response := make(chan struct{})
c.closing <- response
- c.noop() // Flush the response reading goroutine.
+ c.noop() // Flush the response reading goroutine, ignore error.
<-response
c.conn.Close()
}
// noop circumvents the usual request sending goroutines and forces a round
// trip request manually.
-func (c *Conn) noop() {
+func (c *Conn) noop() error {
cookie := c.NewCookie(true, true)
cookie.Sequence = c.newSequenceId()
c.cookieChan <- cookie
- c.writeBuffer(c.getInputFocusRequest())
+ if err := c.writeBuffer(c.getInputFocusRequest()); err != nil {
+ return err
+ }
cookie.Reply() // wait for the buffer to clear
+ return nil
}
// writeBuffer is a convenience function for writing a byte slice to the wire.
-func (c *Conn) writeBuffer(buf []byte) {
+func (c *Conn) writeBuffer(buf []byte) error {
if _, err := c.conn.Write(buf); err != nil {
- Logger.Printf("Write error: %s", err)
- Logger.Fatal("A write error is unrecoverable. Exiting...")
+ Logger.Printf("A write error is unrecoverable: %s", err)
+ return err
+ } else {
+ return nil
}
}
@@ -377,7 +385,6 @@ func (c *Conn) readResponses() {
var (
err Error
- event Event
seq uint16
replyBytes []byte
)
@@ -391,13 +398,13 @@ func (c *Conn) readResponses() {
}
buf := make([]byte, 32)
- err, event, seq = nil, nil, 0
-
+ err, seq = nil, 0
if _, err := io.ReadFull(c.conn, buf); err != nil {
- Logger.Println("A read error is unrecoverable.")
- panic(err)
+ Logger.Printf("A read error is unrecoverable: %s", err)
+ c.eventChan <- err
+ c.Close()
+ continue
}
-
switch buf[0] {
case 0: // This is an error
// Use the constructor function for this error (that is auto
@@ -423,8 +430,10 @@ func (c *Conn) readResponses() {
biggerBuf := make([]byte, byteCount)
copy(biggerBuf[:32], buf)
if _, err := io.ReadFull(c.conn, biggerBuf[32:]); err != nil {
- Logger.Printf("Read error: %s", err)
- Logger.Fatal("A read error is unrecoverable. Exiting...")
+ Logger.Printf("A read error is unrecoverable: %s", err)
+ c.eventChan <- err
+ c.Close()
+ continue
}
replyBytes = biggerBuf
} else {
@@ -445,25 +454,7 @@ func (c *Conn) readResponses() {
"for event with number %d.", evNum)
continue
}
-
- event = newEventFun(buf)
-
- // Put the event into the queue.
- // FIXME: I'm not sure if using a goroutine here to guarantee
- // a non-blocking send is the right way to go. I should implement
- // a proper dynamic queue.
- // I am pretty sure this also loses a guarantee of events being
- // processed in order of being received.
- select {
- case c.eventChan <- event:
- default:
- go func() {
- println("overflowing...")
- c.eventChan <- event
- }()
- }
-
- // No more processing for events.
+ c.eventChan <- newEventFun(buf)
continue
}
@@ -535,15 +526,16 @@ func processEventOrError(everr eventOrError) (Event, Error) {
Logger.Printf("Invalid event/error type: %T", everr)
return nil, nil
}
- panic("unreachable")
}
// WaitForEvent returns the next event from the server.
// It will block until an event is available.
-// WaitForEvent returns either an Event or an Error. (Returning neither or both
+// WaitForEvent returns either an Event or an Error. (Returning both
// is a bug.) Note than an Error here is an X error and not an XGB error. That
// is, X errors are sometimes completely expected (and you may want to ignore
// them in some cases).
+//
+// If both the event and error are nil, then the connection has been closed.
func (c *Conn) WaitForEvent() (Event, Error) {
return processEventOrError(<-c.eventChan)
}
@@ -559,5 +551,4 @@ func (c *Conn) PollForEvent() (Event, Error) {
default:
return nil, nil
}
- panic("unreachable")
}