瀏覽代碼

Merge pull request #173 from slingamn/timeouts.3

refactor idle timeouts again
tags/v0.10.3
Daniel Oaks 6 年之前
父節點
當前提交
36b26f99be
No account linked to committer's email address
共有 1 個文件被更改,包括 58 次插入73 次删除
  1. 58
    73
      irc/idletimer.go

+ 58
- 73
irc/idletimer.go 查看文件

@@ -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 {

Loading…
取消
儲存