aboutsummaryrefslogtreecommitdiff
path: root/nexgb/cookie.go
blob: e75e53cb6d4e327627f0c2553ccd4d8ab76fe6bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package xgb

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
}

// 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.
// 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,
		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
}

// 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.
func (c Cookie) Reply() ([]byte, error) {
	// checked
	if c.errorChan != nil {
		return c.ReplyChecked()
	}
	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.
// 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.")
	}
	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")
}

// 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.
// 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*.")
	}

	select {
	case reply := <-c.replyChan:
		return reply, nil
	case <-c.pingChan:
		return nil, nil
	}
	panic("unreachable")
}

// 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.
// 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.")
	}
	if c.errorChan == nil {
		return errors.New("Cannot call 'Check' on a cookie that is " +
			"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.Sync()
	select {
	case err := <-c.errorChan:
		return err
	case <-c.pingChan:
		return nil
	}
	panic("unreachable")
}