Преглед на файлове

fix a race condition in idle timeouts

squigz on freenode reported an issue where bots were responding to PING
on time, but were occasionally being timed out regardless. This was a race
condition: timeout was detected as idleTime >= it.quitTimeout, but if
the client responded promptly to its PING message and sent no further messages,
but the main loop subsequently slept for longer than expected (i.e., significantly
longer than quitTimeout), this condition would be met through no fault of the
client's.

The fix here is to explicitly track the last time the ping was sent, then test
!lastSeen.After(lastPinged) instead (making use of time.Time's monotonicity).
It is sufficient that the measurement of lastPinged happens-before the PING is sent.
tags/v0.10.3
Shivaram Lingamneni преди 6 години
родител
ревизия
ad1e00629b
променени са 1 файла, в които са добавени 8 реда и са изтрити 11 реда
  1. 8
    11
      irc/idletimer.go

+ 8
- 11
irc/idletimer.go Целия файл

@@ -38,7 +38,6 @@ type IdleTimer struct {
38 38
 
39 39
 	// mutable
40 40
 	client   *Client
41
-	state    TimerState
42 41
 	lastSeen time.Time
43 42
 }
44 43
 
@@ -49,7 +48,6 @@ func NewIdleTimer(client *Client) *IdleTimer {
49 48
 		idleTimeout:     IdleTimeout,
50 49
 		quitTimeout:     QuitTimeout,
51 50
 		client:          client,
52
-		state:           TimerUnregistered,
53 51
 	}
54 52
 	return &it
55 53
 }
@@ -58,17 +56,18 @@ func NewIdleTimer(client *Client) *IdleTimer {
58 56
 // it will eventually be stopped.
59 57
 func (it *IdleTimer) Start() {
60 58
 	it.Lock()
61
-	it.state = TimerUnregistered
62 59
 	it.lastSeen = time.Now()
63 60
 	it.Unlock()
64 61
 	go it.mainLoop()
65 62
 }
66 63
 
67 64
 func (it *IdleTimer) mainLoop() {
65
+	state := TimerUnregistered
66
+	var lastPinged time.Time
67
+
68 68
 	for {
69 69
 		it.Lock()
70 70
 		client := it.client
71
-		state := it.state
72 71
 		lastSeen := it.lastSeen
73 72
 		it.Unlock()
74 73
 
@@ -76,7 +75,8 @@ func (it *IdleTimer) mainLoop() {
76 75
 			return
77 76
 		}
78 77
 
79
-		idleTime := time.Now().Sub(lastSeen)
78
+		now := time.Now()
79
+		idleTime := now.Sub(lastSeen)
80 80
 		var nextSleep time.Duration
81 81
 
82 82
 		if state == TimerUnregistered {
@@ -87,8 +87,8 @@ func (it *IdleTimer) mainLoop() {
87 87
 				nextSleep = it.registerTimeout - idleTime
88 88
 			}
89 89
 		} else if state == TimerIdle {
90
-			if idleTime < it.quitTimeout {
91
-				// new ping came in after we transitioned to TimerIdle,
90
+			if lastSeen.After(lastPinged) {
91
+				// new pong came in after we transitioned to TimerIdle,
92 92
 				// transition back to active and process deadlines below
93 93
 				state = TimerActive
94 94
 			} else {
@@ -100,6 +100,7 @@ func (it *IdleTimer) mainLoop() {
100 100
 			nextSleep = it.idleTimeout - idleTime
101 101
 			if nextSleep <= 0 {
102 102
 				state = TimerIdle
103
+				lastPinged = now
103 104
 				client.Ping()
104 105
 				// grant the client at least quitTimeout to respond
105 106
 				nextSleep = it.quitTimeout
@@ -113,10 +114,6 @@ func (it *IdleTimer) mainLoop() {
113 114
 			return
114 115
 		}
115 116
 
116
-		it.Lock()
117
-		it.state = state
118
-		it.Unlock()
119
-
120 117
 		time.Sleep(nextSleep)
121 118
 	}
122 119
 }

Loading…
Отказ
Запис