-
Notifications
You must be signed in to change notification settings - Fork 0
/
idletimer.go
142 lines (124 loc) · 3.29 KB
/
idletimer.go
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
// Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"fmt"
"sync"
"time"
)
const (
// RegisterTimeout is how long clients have to register before we disconnect them
RegisterTimeout = time.Minute
// IdleTimeout is how long without traffic before a registered client is considered idle.
IdleTimeout = time.Minute + time.Second*30
// QuitTimeout is how long without traffic before an idle client is disconnected
QuitTimeout = time.Minute
)
// client idleness state machine
type TimerState uint
const (
TimerUnregistered TimerState = iota // client is unregistered
TimerActive // client is actively sending commands
TimerIdle // client is idle, we sent PING and are waiting for PONG
TimerDead // client was terminated
)
type IdleTimer struct {
sync.Mutex // tier 1
// immutable after construction
registerTimeout time.Duration
idleTimeout time.Duration
quitTimeout time.Duration
client *Client
// mutable
state TimerState
timer *time.Timer
}
// NewIdleTimer sets up a new IdleTimer using constant timeouts.
func NewIdleTimer(client *Client) *IdleTimer {
it := IdleTimer{
registerTimeout: RegisterTimeout,
idleTimeout: IdleTimeout,
quitTimeout: QuitTimeout,
client: client,
}
return &it
}
// Start starts counting idle time; if there is no activity from the client,
// it will eventually be stopped.
func (it *IdleTimer) Start() {
it.Lock()
defer it.Unlock()
it.state = TimerUnregistered
it.resetTimeout()
}
func (it *IdleTimer) Touch() {
// ignore touches from unregistered clients
if !it.client.Registered() {
return
}
it.Lock()
defer it.Unlock()
// a touch transitions TimerUnregistered or TimerIdle into TimerActive
if it.state != TimerDead {
it.state = TimerActive
it.resetTimeout()
}
}
func (it *IdleTimer) processTimeout() {
var previousState TimerState
func() {
it.Lock()
defer it.Unlock()
previousState = it.state
// TimerActive transitions to TimerIdle, all others to TimerDead
if it.state == TimerActive {
// send them a ping, give them time to respond
it.state = TimerIdle
it.resetTimeout()
} else {
it.state = TimerDead
}
}()
if previousState == TimerActive {
it.client.Ping()
} else {
it.client.Quit(it.quitMessage(previousState))
it.client.destroy()
}
}
// Stop stops counting idle time.
func (it *IdleTimer) Stop() {
it.Lock()
defer it.Unlock()
it.state = TimerDead
it.resetTimeout()
}
func (it *IdleTimer) resetTimeout() {
if it.timer != nil {
it.timer.Stop()
}
var nextTimeout time.Duration
switch it.state {
case TimerUnregistered:
nextTimeout = it.registerTimeout
case TimerActive:
nextTimeout = it.idleTimeout
case TimerIdle:
nextTimeout = it.quitTimeout
case TimerDead:
return
}
it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
}
func (it *IdleTimer) quitMessage(state TimerState) string {
switch state {
case TimerUnregistered:
return fmt.Sprintf("Registration timeout: %v", it.registerTimeout)
case TimerIdle:
// how many seconds before registered clients are timed out (IdleTimeout plus QuitTimeout).
return fmt.Sprintf("Ping timeout: %v", (it.idleTimeout + it.quitTimeout))
default:
// shouldn't happen
return ""
}
}