|
@@ -26,6 +26,7 @@ const (
|
26
|
26
|
TimerUnregistered TimerState = iota // client is unregistered
|
27
|
27
|
TimerActive // client is actively sending commands
|
28
|
28
|
TimerIdle // client is idle, we sent PING and are waiting for PONG
|
|
29
|
+ TimerDead // client was terminated
|
29
|
30
|
)
|
30
|
31
|
|
31
|
32
|
type IdleTimer struct {
|
|
@@ -35,10 +36,11 @@ type IdleTimer struct {
|
35
|
36
|
registerTimeout time.Duration
|
36
|
37
|
idleTimeout time.Duration
|
37
|
38
|
quitTimeout time.Duration
|
|
39
|
+ client *Client
|
38
|
40
|
|
39
|
41
|
// mutable
|
40
|
|
- client *Client
|
41
|
|
- lastSeen time.Time
|
|
42
|
+ state TimerState
|
|
43
|
+ timer *time.Timer
|
42
|
44
|
}
|
43
|
45
|
|
44
|
46
|
// NewIdleTimer sets up a new IdleTimer using constant timeouts.
|
|
@@ -56,91 +58,74 @@ func NewIdleTimer(client *Client) *IdleTimer {
|
56
|
58
|
// it will eventually be stopped.
|
57
|
59
|
func (it *IdleTimer) Start() {
|
58
|
60
|
it.Lock()
|
59
|
|
- it.lastSeen = time.Now()
|
60
|
|
- it.Unlock()
|
61
|
|
- go it.mainLoop()
|
|
61
|
+ defer it.Unlock()
|
|
62
|
+ it.state = TimerUnregistered
|
|
63
|
+ it.resetTimeout()
|
62
|
64
|
}
|
63
|
65
|
|
64
|
|
-func (it *IdleTimer) mainLoop() {
|
65
|
|
- state := TimerUnregistered
|
66
|
|
- var lastPinged time.Time
|
67
|
|
-
|
68
|
|
- for {
|
69
|
|
- it.Lock()
|
70
|
|
- client := it.client
|
71
|
|
- lastSeen := it.lastSeen
|
72
|
|
- it.Unlock()
|
73
|
|
-
|
74
|
|
- if client == nil {
|
75
|
|
- return
|
76
|
|
- }
|
77
|
|
-
|
78
|
|
- now := time.Now()
|
79
|
|
- idleTime := now.Sub(lastSeen)
|
80
|
|
- var nextSleep time.Duration
|
81
|
|
-
|
82
|
|
- if state == TimerUnregistered {
|
83
|
|
- if client.Registered() {
|
84
|
|
- // transition to active, process new deadlines below
|
85
|
|
- state = TimerActive
|
86
|
|
- } else {
|
87
|
|
- nextSleep = it.registerTimeout - idleTime
|
88
|
|
- }
|
89
|
|
- } else if state == TimerIdle {
|
90
|
|
- if lastSeen.After(lastPinged) {
|
91
|
|
- // new pong came in after we transitioned to TimerIdle,
|
92
|
|
- // transition back to active and process deadlines below
|
93
|
|
- state = TimerActive
|
94
|
|
- } else {
|
95
|
|
- nextSleep = 0
|
96
|
|
- }
|
97
|
|
- }
|
98
|
|
-
|
99
|
|
- if state == TimerActive {
|
100
|
|
- nextSleep = it.idleTimeout - idleTime
|
101
|
|
- if nextSleep <= 0 {
|
102
|
|
- state = TimerIdle
|
103
|
|
- lastPinged = now
|
104
|
|
- client.Ping()
|
105
|
|
- // grant the client at least quitTimeout to respond
|
106
|
|
- nextSleep = it.quitTimeout
|
107
|
|
- }
|
108
|
|
- }
|
109
|
|
-
|
110
|
|
- if nextSleep <= 0 {
|
111
|
|
- // ran out of time, hang them up
|
112
|
|
- client.Quit(it.quitMessage(state))
|
113
|
|
- client.destroy()
|
114
|
|
- return
|
115
|
|
- }
|
|
66
|
+func (it *IdleTimer) Touch() {
|
|
67
|
+ // ignore touches from unregistered clients
|
|
68
|
+ if !it.client.Registered() {
|
|
69
|
+ return
|
|
70
|
+ }
|
116
|
71
|
|
117
|
|
- time.Sleep(nextSleep)
|
|
72
|
+ it.Lock()
|
|
73
|
+ defer it.Unlock()
|
|
74
|
+ // a touch transitions TimerUnregistered or TimerIdle into TimerActive
|
|
75
|
+ if it.state != TimerDead {
|
|
76
|
+ it.state = TimerActive
|
|
77
|
+ it.resetTimeout()
|
118
|
78
|
}
|
119
|
79
|
}
|
120
|
80
|
|
121
|
|
-// Touch registers activity (e.g., sending a command) from an client.
|
122
|
|
-func (it *IdleTimer) Touch() {
|
123
|
|
- it.Lock()
|
124
|
|
- client := it.client
|
125
|
|
- it.Unlock()
|
|
81
|
+func (it *IdleTimer) processTimeout() {
|
|
82
|
+ var previousState TimerState
|
|
83
|
+ func() {
|
|
84
|
+ it.Lock()
|
|
85
|
+ defer it.Unlock()
|
|
86
|
+ previousState = it.state
|
|
87
|
+ // TimerActive transitions to TimerIdle, all others to TimerDead
|
|
88
|
+ if it.state == TimerActive {
|
|
89
|
+ // send them a ping, give them time to respond
|
|
90
|
+ it.state = TimerIdle
|
|
91
|
+ it.resetTimeout()
|
|
92
|
+ } else {
|
|
93
|
+ it.state = TimerDead
|
|
94
|
+ }
|
|
95
|
+ }()
|
126
|
96
|
|
127
|
|
- // ignore touches for unregistered clients
|
128
|
|
- if client != nil && !client.Registered() {
|
129
|
|
- return
|
|
97
|
+ if previousState == TimerActive {
|
|
98
|
+ it.client.Ping()
|
|
99
|
+ } else {
|
|
100
|
+ it.client.Quit(it.quitMessage(previousState))
|
|
101
|
+ it.client.destroy()
|
130
|
102
|
}
|
131
|
|
-
|
132
|
|
- it.Lock()
|
133
|
|
- it.lastSeen = time.Now()
|
134
|
|
- it.Unlock()
|
135
|
103
|
}
|
136
|
104
|
|
137
|
105
|
// Stop stops counting idle time.
|
138
|
106
|
func (it *IdleTimer) Stop() {
|
139
|
107
|
it.Lock()
|
140
|
108
|
defer it.Unlock()
|
141
|
|
- // no need to stop the goroutine, it'll clean itself up in a few minutes;
|
142
|
|
- // just ensure the Client object is collectable
|
143
|
|
- it.client = nil
|
|
109
|
+ it.state = TimerDead
|
|
110
|
+ it.resetTimeout()
|
|
111
|
+}
|
|
112
|
+
|
|
113
|
+func (it *IdleTimer) resetTimeout() {
|
|
114
|
+ if it.timer != nil {
|
|
115
|
+ it.timer.Stop()
|
|
116
|
+ }
|
|
117
|
+ var nextTimeout time.Duration
|
|
118
|
+ switch it.state {
|
|
119
|
+ case TimerUnregistered:
|
|
120
|
+ nextTimeout = it.registerTimeout
|
|
121
|
+ case TimerActive:
|
|
122
|
+ nextTimeout = it.idleTimeout
|
|
123
|
+ case TimerIdle:
|
|
124
|
+ nextTimeout = it.quitTimeout
|
|
125
|
+ case TimerDead:
|
|
126
|
+ return
|
|
127
|
+ }
|
|
128
|
+ it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
|
144
|
129
|
}
|
145
|
130
|
|
146
|
131
|
func (it *IdleTimer) quitMessage(state TimerState) string {
|