From 4a7b05be36b96134b4dae3ca385e9bfbb797d531 Mon Sep 17 00:00:00 2001 From: "Andrew Gallant (Ocelot)" Date: Sat, 5 May 2012 02:56:15 -0400 Subject: oh momma. a lot of modifications and it appears to be working. w00t. --- nexgb/cookie.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 nexgb/cookie.go (limited to 'nexgb/cookie.go') diff --git a/nexgb/cookie.go b/nexgb/cookie.go new file mode 100644 index 0000000..502ccbf --- /dev/null +++ b/nexgb/cookie.go @@ -0,0 +1,111 @@ +package xgb + +import ( + "errors" +) + +type cookie struct { + Sequence uint16 + replyChan chan []byte + errorChan chan error + pingChan chan bool +} + +func (c *Conn) newCookie(checked, reply bool) cookie { + cookie := cookie{ + Sequence: c.newSequenceId(), + replyChan: nil, + errorChan: nil, + pingChan: nil, + } + + // There are four different kinds of cookies: + // Checked requests with replies get a reply channel and an error channel. + // Unchecked requests with replies get a reply channel and a ping channel. + // Checked requests w/o replies get a ping channel and an error channel. + // Unchecked requests w/o replies get no channels. + // The reply channel is used to send reply data. + // The error channel is used to send error data. + // The ping channel is used when one of the 'reply' or 'error' channels + // is missing but the other is present. The ping channel is way to force + // the blocking to stop and basically say "the error has been received + // in the main event loop" (when the ping channel is coupled with a reply + // channel) or "the request you made that has no reply was successful" + // (when the ping channel is coupled with an error channel). + if checked { + cookie.errorChan = make(chan error, 1) + if !reply { + cookie.pingChan = make(chan bool, 1) + } + } + if reply { + cookie.replyChan = make(chan []byte, 1) + if !checked { + cookie.pingChan = make(chan bool, 1) + } + } + + return cookie +} + +func (c cookie) reply() ([]byte, error) { + // checked + if c.errorChan != nil { + return c.replyChecked() + } + return c.replyUnchecked() +} + +func (c cookie) replyChecked() ([]byte, error) { + if c.replyChan == nil { + return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + + "is not expecting a *reply* or an error.") + } + if c.errorChan == nil { + return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + + "is not expecting a reply or an *error*.") + } + + select { + case reply := <-c.replyChan: + return reply, nil + case err := <-c.errorChan: + return nil, err + } + panic("unreachable") +} + +func (c cookie) replyUnchecked() ([]byte, error) { + if c.replyChan == nil { + return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " + + "that is not expecting a *reply*.") + } + + select { + case reply := <-c.replyChan: + return reply, nil + case <-c.pingChan: + return nil, nil + } + panic("unreachable") +} + +func (c cookie) Check() error { + if c.replyChan != nil { + return errors.New("Cannot call 'Check' on a cookie that is " + + "expecting a *reply*. Use 'Reply' instead.") + } + if c.errorChan == nil { + return errors.New("Cannot call 'Check' on a cookie that is " + + "not expecting a possible *error*.") + } + + select { + case err := <-c.errorChan: + return err + case <-c.pingChan: + return nil + } + panic("unreachable") +} + -- cgit v1.2.3 From 369ad0d33e51035a3e48436fc85f60130b201437 Mon Sep 17 00:00:00 2001 From: "Andrew Gallant (Ocelot)" Date: Sat, 5 May 2012 18:22:24 -0400 Subject: extensions are working! extensions are working! --- nexgb/cookie.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) (limited to 'nexgb/cookie.go') diff --git a/nexgb/cookie.go b/nexgb/cookie.go index 502ccbf..7f54a22 100644 --- a/nexgb/cookie.go +++ b/nexgb/cookie.go @@ -4,16 +4,28 @@ import ( "errors" ) +// cookie is the internal representation of a cookie, where one is generated +// for *every* request sent by XGB. +// 'cookie' is most frequently used by embedding it into a more specific +// kind of cookie, i.e., 'GetInputFocusCookie'. type cookie struct { + conn *Conn Sequence uint16 replyChan chan []byte errorChan chan error pingChan chan bool } -func (c *Conn) newCookie(checked, reply bool) cookie { - cookie := cookie{ - Sequence: c.newSequenceId(), +// newCookie creates a new cookie with the correct channels initialized +// depending upon the values of 'checked' and 'reply'. Together, there are +// four different kinds of cookies. (See more detailed comments in the +// function for more info on those.) +// Note that a sequence number is not set until just before the request +// corresponding to this cookie is sent over the wire. +func (c *Conn) newCookie(checked, reply bool) *cookie { + cookie := &cookie{ + conn: c, + Sequence: 0, // we add the sequence id just before sending a request replyChan: nil, errorChan: nil, pingChan: nil, @@ -48,6 +60,8 @@ func (c *Conn) newCookie(checked, reply bool) cookie { return cookie } +// reply detects whether this is a checked or unchecked cookie, and calls +// 'replyChecked' or 'replyUnchecked' appropriately. func (c cookie) reply() ([]byte, error) { // checked if c.errorChan != nil { @@ -56,6 +70,10 @@ func (c cookie) reply() ([]byte, error) { return c.replyUnchecked() } +// replyChecked waits for a response on either the replyChan or errorChan +// channels. If the former arrives, the bytes are returned with a nil error. +// If the latter arrives, no bytes are returned (nil) and the error received +// is returned. func (c cookie) replyChecked() ([]byte, error) { if c.replyChan == nil { return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + @@ -75,6 +93,12 @@ func (c cookie) replyChecked() ([]byte, error) { panic("unreachable") } +// replyChecked waits for a response on either the replyChan or pingChan +// channels. If the former arrives, the bytes are returned with a nil error. +// If the latter arrives, no bytes are returned (nil) and a nil error +// is returned. (In the latter case, the corresponding error can be retrieved +// from (Wait|Poll)ForEvent asynchronously.) +// In all honesty, you *probably* don't want to use this method. func (c cookie) replyUnchecked() ([]byte, error) { if c.replyChan == nil { return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " + @@ -90,7 +114,15 @@ func (c cookie) replyUnchecked() ([]byte, error) { panic("unreachable") } -func (c cookie) Check() error { +// check is used for checked requests that have no replies. It is a mechanism +// by which to report "success" or "error" in a synchronous fashion. (Therefore, +// unchecked requests without replies cannot use this method.) +// If the request causes an error, it is sent to this cookie's errorChan. +// If the request was successful, there is no response from the server. +// Thus, pingChan is sent a value when the *next* reply is read. +// If no more replies are being processed, we force a round trip request with +// GetInputFocus. +func (c cookie) check() error { if c.replyChan != nil { return errors.New("Cannot call 'Check' on a cookie that is " + "expecting a *reply*. Use 'Reply' instead.") @@ -100,6 +132,17 @@ func (c cookie) Check() error { "not expecting a possible *error*.") } + // First do a quick non-blocking check to see if we've been pinged. + select { + case err := <-c.errorChan: + return err + case <-c.pingChan: + return nil + default: + } + + // Now force a round trip and try again, but block this time. + c.conn.GetInputFocus().Reply() select { case err := <-c.errorChan: return err -- cgit v1.2.3 From fd30f1512a713ae5b3cbacddfe9f29dfccc82047 Mon Sep 17 00:00:00 2001 From: "Andrew Gallant (Ocelot)" Date: Mon, 7 May 2012 01:00:45 -0400 Subject: added tests --- nexgb/cookie.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'nexgb/cookie.go') diff --git a/nexgb/cookie.go b/nexgb/cookie.go index 7f54a22..8c0774d 100644 --- a/nexgb/cookie.go +++ b/nexgb/cookie.go @@ -9,11 +9,11 @@ import ( // 'cookie' is most frequently used by embedding it into a more specific // kind of cookie, i.e., 'GetInputFocusCookie'. type cookie struct { - conn *Conn - Sequence uint16 + conn *Conn + Sequence uint16 replyChan chan []byte errorChan chan error - pingChan chan bool + pingChan chan bool } // newCookie creates a new cookie with the correct channels initialized @@ -24,11 +24,11 @@ type cookie struct { // corresponding to this cookie is sent over the wire. func (c *Conn) newCookie(checked, reply bool) *cookie { cookie := &cookie{ - conn: c, - Sequence: 0, // we add the sequence id just before sending a request + conn: c, + Sequence: 0, // we add the sequence id just before sending a request replyChan: nil, errorChan: nil, - pingChan: nil, + pingChan: nil, } // There are four different kinds of cookies: @@ -151,4 +151,3 @@ func (c cookie) check() error { } panic("unreachable") } - -- cgit v1.2.3 From 0c50dc6241fa21712e041cfa2bfb9db4ccaef10a Mon Sep 17 00:00:00 2001 From: "Andrew Gallant (Ocelot)" Date: Thu, 10 May 2012 17:01:42 -0400 Subject: a huge commit. splitting extensions into their own sub-packages. --- nexgb/cookie.go | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) (limited to 'nexgb/cookie.go') diff --git a/nexgb/cookie.go b/nexgb/cookie.go index 8c0774d..0f32990 100644 --- a/nexgb/cookie.go +++ b/nexgb/cookie.go @@ -4,11 +4,11 @@ import ( "errors" ) -// cookie is the internal representation of a cookie, where one is generated +// Cookie is the internal representation of a cookie, where one is generated // for *every* request sent by XGB. // 'cookie' is most frequently used by embedding it into a more specific // kind of cookie, i.e., 'GetInputFocusCookie'. -type cookie struct { +type Cookie struct { conn *Conn Sequence uint16 replyChan chan []byte @@ -22,8 +22,10 @@ type cookie struct { // function for more info on those.) // Note that a sequence number is not set until just before the request // corresponding to this cookie is sent over the wire. -func (c *Conn) newCookie(checked, reply bool) *cookie { - cookie := &cookie{ +// This function should not be used. It is exported for use in the extension +// sub-packages. +func (c *Conn) NewCookie(checked, reply bool) *Cookie { + cookie := &Cookie{ conn: c, Sequence: 0, // we add the sequence id just before sending a request replyChan: nil, @@ -60,21 +62,23 @@ func (c *Conn) newCookie(checked, reply bool) *cookie { return cookie } -// reply detects whether this is a checked or unchecked cookie, and calls +// Reply detects whether this is a checked or unchecked cookie, and calls // 'replyChecked' or 'replyUnchecked' appropriately. -func (c cookie) reply() ([]byte, error) { +// This should not be used. It is exported for use in extension sub-packages. +func (c Cookie) Reply() ([]byte, error) { // checked if c.errorChan != nil { - return c.replyChecked() + return c.ReplyChecked() } - return c.replyUnchecked() + return c.ReplyUnchecked() } -// replyChecked waits for a response on either the replyChan or errorChan +// ReplyChecked waits for a response on either the replyChan or errorChan // channels. If the former arrives, the bytes are returned with a nil error. // If the latter arrives, no bytes are returned (nil) and the error received // is returned. -func (c cookie) replyChecked() ([]byte, error) { +// This should not be used. It is exported for use in extension sub-packages. +func (c Cookie) ReplyChecked() ([]byte, error) { if c.replyChan == nil { return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + "is not expecting a *reply* or an error.") @@ -93,13 +97,14 @@ func (c cookie) replyChecked() ([]byte, error) { panic("unreachable") } -// replyChecked waits for a response on either the replyChan or pingChan +// ReplyChecked waits for a response on either the replyChan or pingChan // channels. If the former arrives, the bytes are returned with a nil error. // If the latter arrives, no bytes are returned (nil) and a nil error // is returned. (In the latter case, the corresponding error can be retrieved // from (Wait|Poll)ForEvent asynchronously.) // In all honesty, you *probably* don't want to use this method. -func (c cookie) replyUnchecked() ([]byte, error) { +// This should not be used. It is exported for use in extension sub-packages. +func (c Cookie) ReplyUnchecked() ([]byte, error) { if c.replyChan == nil { return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " + "that is not expecting a *reply*.") @@ -114,7 +119,7 @@ func (c cookie) replyUnchecked() ([]byte, error) { panic("unreachable") } -// check is used for checked requests that have no replies. It is a mechanism +// Check is used for checked requests that have no replies. It is a mechanism // by which to report "success" or "error" in a synchronous fashion. (Therefore, // unchecked requests without replies cannot use this method.) // If the request causes an error, it is sent to this cookie's errorChan. @@ -122,7 +127,8 @@ func (c cookie) replyUnchecked() ([]byte, error) { // Thus, pingChan is sent a value when the *next* reply is read. // If no more replies are being processed, we force a round trip request with // GetInputFocus. -func (c cookie) check() error { +// This should not be used. It is exported for use in extension sub-packages. +func (c Cookie) Check() error { if c.replyChan != nil { return errors.New("Cannot call 'Check' on a cookie that is " + "expecting a *reply*. Use 'Reply' instead.") @@ -142,7 +148,7 @@ func (c cookie) check() error { } // Now force a round trip and try again, but block this time. - c.conn.GetInputFocus().Reply() + c.conn.Sync() select { case err := <-c.errorChan: return err -- cgit v1.2.3 From 58bb2572c5d1e88689aa1b30dc55b702acf57f4f Mon Sep 17 00:00:00 2001 From: "Andrew Gallant (Ocelot)" Date: Sat, 26 May 2012 18:22:25 -0400 Subject: Doc touchups. --- nexgb/cookie.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nexgb/cookie.go') diff --git a/nexgb/cookie.go b/nexgb/cookie.go index 0f32990..e75e53c 100644 --- a/nexgb/cookie.go +++ b/nexgb/cookie.go @@ -16,7 +16,7 @@ type Cookie struct { pingChan chan bool } -// newCookie creates a new cookie with the correct channels initialized +// NewCookie creates a new cookie with the correct channels initialized // depending upon the values of 'checked' and 'reply'. Together, there are // four different kinds of cookies. (See more detailed comments in the // function for more info on those.) -- cgit v1.2.3 From 08275ebda825e8f6f03e3329793b72e34affad2f Mon Sep 17 00:00:00 2001 From: "Andrew Gallant (Ocelot)" Date: Tue, 5 Jun 2012 00:15:14 -0400 Subject: Doc fixes and stop exporting ReplyChecked and ReplyUnchecked --- nexgb/cookie.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) (limited to 'nexgb/cookie.go') diff --git a/nexgb/cookie.go b/nexgb/cookie.go index e75e53c..6b1e4fb 100644 --- a/nexgb/cookie.go +++ b/nexgb/cookie.go @@ -22,8 +22,9 @@ type Cookie struct { // function for more info on those.) // Note that a sequence number is not set until just before the request // corresponding to this cookie is sent over the wire. -// This function should not be used. It is exported for use in the extension -// sub-packages. +// +// Unless you're building requests from bytes by hand, this method should +// not be used. func (c *Conn) NewCookie(checked, reply bool) *Cookie { cookie := &Cookie{ conn: c, @@ -64,21 +65,25 @@ func (c *Conn) NewCookie(checked, reply bool) *Cookie { // Reply detects whether this is a checked or unchecked cookie, and calls // 'replyChecked' or 'replyUnchecked' appropriately. -// This should not be used. It is exported for use in extension sub-packages. +// +// Unless you're building requests from bytes by hand, this method should +// not be used. func (c Cookie) Reply() ([]byte, error) { // checked if c.errorChan != nil { - return c.ReplyChecked() + return c.replyChecked() } - return c.ReplyUnchecked() + return c.replyUnchecked() } -// ReplyChecked waits for a response on either the replyChan or errorChan +// replyChecked waits for a response on either the replyChan or errorChan // channels. If the former arrives, the bytes are returned with a nil error. // If the latter arrives, no bytes are returned (nil) and the error received // is returned. -// This should not be used. It is exported for use in extension sub-packages. -func (c Cookie) ReplyChecked() ([]byte, error) { +// +// Unless you're building requests from bytes by hand, this method should +// not be used. +func (c Cookie) replyChecked() ([]byte, error) { if c.replyChan == nil { return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + "is not expecting a *reply* or an error.") @@ -97,14 +102,16 @@ func (c Cookie) ReplyChecked() ([]byte, error) { panic("unreachable") } -// ReplyChecked waits for a response on either the replyChan or pingChan +// replyUnchecked waits for a response on either the replyChan or pingChan // channels. If the former arrives, the bytes are returned with a nil error. // If the latter arrives, no bytes are returned (nil) and a nil error // is returned. (In the latter case, the corresponding error can be retrieved // from (Wait|Poll)ForEvent asynchronously.) // In all honesty, you *probably* don't want to use this method. -// This should not be used. It is exported for use in extension sub-packages. -func (c Cookie) ReplyUnchecked() ([]byte, error) { +// +// Unless you're building requests from bytes by hand, this method should +// not be used. +func (c Cookie) replyUnchecked() ([]byte, error) { if c.replyChan == nil { return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " + "that is not expecting a *reply*.") @@ -127,7 +134,9 @@ func (c Cookie) ReplyUnchecked() ([]byte, error) { // Thus, pingChan is sent a value when the *next* reply is read. // If no more replies are being processed, we force a round trip request with // GetInputFocus. -// This should not be used. It is exported for use in extension sub-packages. +// +// Unless you're building requests from bytes by hand, this method should +// not be used. func (c Cookie) Check() error { if c.replyChan != nil { return errors.New("Cannot call 'Check' on a cookie that is " + -- cgit v1.2.3 From 5451e59f884298fdf97ef4420a7bc1001093c3b7 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sun, 26 Apr 2015 19:04:54 -0400 Subject: 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. --- nexgb/cookie.go | 3 --- 1 file changed, 3 deletions(-) (limited to 'nexgb/cookie.go') 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") } -- cgit v1.2.3