Browse Source

fix #1229

tags/v2.3.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
a3e5c9e98c
3 changed files with 104 additions and 182 deletions
  1. 104
    5
      irc/client.go
  2. 0
    7
      irc/commands.go
  3. 0
    170
      irc/idletimer.go

+ 104
- 5
irc/client.go View File

@@ -40,6 +40,22 @@ const (
40 40
 	lastSeenWriteInterval = time.Hour
41 41
 )
42 42
 
43
+const (
44
+	// RegisterTimeout is how long clients have to register before we disconnect them
45
+	RegisterTimeout = time.Minute
46
+	// DefaultIdleTimeout is how long without traffic before we send the client a PING
47
+	DefaultIdleTimeout = time.Minute + 30*time.Second
48
+	// For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
49
+	// (single-onion circuits will close unless the client sends data once every 60 seconds):
50
+	// https://bugs.torproject.org/29665
51
+	TorIdleTimeout = time.Second * 30
52
+	// This is how long a client gets without sending any message, including the PONG to our
53
+	// PING, before we disconnect them:
54
+	DefaultTotalTimeout = 2*time.Minute + 30*time.Second
55
+	// Resumeable clients (clients who have negotiated caps.Resume) get longer:
56
+	ResumeableTotalTimeout = 3*time.Minute + 30*time.Second
57
+)
58
+
43 59
 // ResumeDetails is a place to stash data at various stages of
44 60
 // the resume process: when handling the RESUME command itself,
45 61
 // when completing the registration, and when rejoining channels.
@@ -83,6 +99,7 @@ type Client struct {
83 99
 	realname           string
84 100
 	realIP             net.IP
85 101
 	registered         bool
102
+	registrationTimer  *time.Timer
86 103
 	resumeID           string
87 104
 	server             *Server
88 105
 	skeleton           string
@@ -123,7 +140,10 @@ type Session struct {
123 140
 	deviceID string
124 141
 
125 142
 	ctime      time.Time
126
-	lastActive time.Time
143
+	lastActive time.Time // last non-CTCP PRIVMSG sent; updates publicly visible idle time
144
+	lastTouch  time.Time // last line sent; updates timer for idle timeouts
145
+	idleTimer  *time.Timer
146
+	pingSent   bool // we sent PING to a putatively idle connection and we're waiting for PONG
127 147
 
128 148
 	socket      *Socket
129 149
 	realIP      net.IP
@@ -131,7 +151,6 @@ type Session struct {
131 151
 	rawHostname string
132 152
 	isTor       bool
133 153
 
134
-	idletimer            IdleTimer
135 154
 	fakelag              Fakelag
136 155
 	deferredFakelagCount int
137 156
 	destroyed            uint32
@@ -342,7 +361,6 @@ func (server *Server) RunClient(conn IRCConn) {
342 361
 	}
343 362
 	client.sessions = []*Session{session}
344 363
 
345
-	session.idletimer.Initialize(session)
346 364
 	session.resetFakelag()
347 365
 
348 366
 	if wConn.Secure {
@@ -363,6 +381,7 @@ func (server *Server) RunClient(conn IRCConn) {
363 381
 		}
364 382
 	}
365 383
 
384
+	client.registrationTimer = time.AfterFunc(RegisterTimeout, client.handleRegisterTimeout)
366 385
 	server.stats.Add()
367 386
 	client.run(session)
368 387
 }
@@ -605,7 +624,7 @@ func (client *Client) run(session *Session) {
605 624
 
606 625
 	isReattach := client.Registered()
607 626
 	if isReattach {
608
-		session.idletimer.Touch()
627
+		client.Touch(session)
609 628
 		if session.resumeDetails != nil {
610 629
 			session.playResume()
611 630
 			session.resumeDetails = nil
@@ -739,6 +758,7 @@ func (client *Client) Touch(session *Session) {
739 758
 			client.lastSeenLastWrite = now
740 759
 		}
741 760
 	}
761
+	client.updateIdleTimer(session, now)
742 762
 	client.stateMutex.Unlock()
743 763
 	if markDirty {
744 764
 		client.markDirty(IncludeLastSeen)
@@ -763,6 +783,71 @@ func (client *Client) setLastSeen(now time.Time, deviceID string) {
763 783
 	}
764 784
 }
765 785
 
786
+func (client *Client) updateIdleTimer(session *Session, now time.Time) {
787
+	session.lastTouch = now
788
+	session.pingSent = false
789
+
790
+	if session.idleTimer == nil {
791
+		pingTimeout := DefaultIdleTimeout
792
+		if session.isTor {
793
+			pingTimeout = TorIdleTimeout
794
+		}
795
+		session.idleTimer = time.AfterFunc(pingTimeout, session.handleIdleTimeout)
796
+	}
797
+}
798
+
799
+func (session *Session) handleIdleTimeout() {
800
+	totalTimeout := DefaultTotalTimeout
801
+	if session.capabilities.Has(caps.Resume) {
802
+		totalTimeout = ResumeableTotalTimeout
803
+	}
804
+	pingTimeout := DefaultIdleTimeout
805
+	if session.isTor {
806
+		pingTimeout = TorIdleTimeout
807
+	}
808
+
809
+	session.client.stateMutex.Lock()
810
+	now := time.Now()
811
+	timeUntilDestroy := session.lastTouch.Add(totalTimeout).Sub(now)
812
+	timeUntilPing := session.lastTouch.Add(pingTimeout).Sub(now)
813
+	shouldDestroy := session.pingSent && timeUntilDestroy <= 0
814
+	shouldSendPing := !session.pingSent && timeUntilPing <= 0
815
+	if !shouldDestroy {
816
+		if shouldSendPing {
817
+			session.pingSent = true
818
+		}
819
+		// check in again at the minimum of these 3 possible intervals:
820
+		// 1. the ping timeout (assuming we PING and they reply immediately with PONG)
821
+		// 2. the next time we would send PING (if they don't send any more lines)
822
+		// 3. the next time we would destroy (if they don't send any more lines)
823
+		nextTimeout := pingTimeout
824
+		if 0 < timeUntilPing && timeUntilPing < nextTimeout {
825
+			nextTimeout = timeUntilPing
826
+		}
827
+		if 0 < timeUntilDestroy && timeUntilDestroy < nextTimeout {
828
+			nextTimeout = timeUntilDestroy
829
+		}
830
+		session.idleTimer.Stop()
831
+		session.idleTimer.Reset(nextTimeout)
832
+	}
833
+	session.client.stateMutex.Unlock()
834
+
835
+	if shouldDestroy {
836
+		session.client.Quit(fmt.Sprintf("Ping timeout: %v", totalTimeout), session)
837
+		session.client.destroy(session)
838
+	} else if shouldSendPing {
839
+		session.Ping()
840
+	}
841
+}
842
+
843
+func (session *Session) stopIdleTimer() {
844
+	session.client.stateMutex.Lock()
845
+	defer session.client.stateMutex.Unlock()
846
+	if session.idleTimer != nil {
847
+		session.idleTimer.Stop()
848
+	}
849
+}
850
+
766 851
 // Ping sends the client a PING message.
767 852
 func (session *Session) Ping() {
768 853
 	session.Send(nil, "", "PING", session.client.Nick())
@@ -1119,6 +1204,10 @@ func (client *Client) SetNick(nick, nickCasefolded, skeleton string) (success bo
1119 1204
 	} else if !client.registered {
1120 1205
 		// XXX test this before setting it to avoid annoying the race detector
1121 1206
 		client.registered = true
1207
+		if client.registrationTimer != nil {
1208
+			client.registrationTimer.Stop()
1209
+			client.registrationTimer = nil
1210
+		}
1122 1211
 	}
1123 1212
 	client.nick = nick
1124 1213
 	client.nickCasefolded = nickCasefolded
@@ -1294,6 +1383,11 @@ func (client *Client) destroy(session *Session) {
1294 1383
 		client.awayMessage = awayMessage
1295 1384
 	}
1296 1385
 
1386
+	if client.registrationTimer != nil {
1387
+		// unconditionally stop; if the client is still unregistered it must be destroyed
1388
+		client.registrationTimer.Stop()
1389
+	}
1390
+
1297 1391
 	client.stateMutex.Unlock()
1298 1392
 
1299 1393
 	// XXX there is no particular reason to persist this state here rather than
@@ -1311,7 +1405,7 @@ func (client *Client) destroy(session *Session) {
1311 1405
 			// session has been attached to a new client; do not destroy it
1312 1406
 			continue
1313 1407
 		}
1314
-		session.idletimer.Stop()
1408
+		session.stopIdleTimer()
1315 1409
 		// send quit/error message to client if they haven't been sent already
1316 1410
 		client.Quit("", session)
1317 1411
 		quitMessage = session.quitMessage
@@ -1693,6 +1787,11 @@ func (client *Client) historyStatus(config *Config) (status HistoryStatus, targe
1693 1787
 	return
1694 1788
 }
1695 1789
 
1790
+func (client *Client) handleRegisterTimeout() {
1791
+	client.Quit(fmt.Sprintf("Registration timeout: %v", RegisterTimeout), nil)
1792
+	client.destroy(nil)
1793
+}
1794
+
1696 1795
 func (client *Client) copyLastSeen() (result map[string]time.Time) {
1697 1796
 	client.stateMutex.RLock()
1698 1797
 	defer client.stateMutex.RUnlock()

+ 0
- 7
irc/commands.go View File

@@ -58,13 +58,6 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
58 58
 		exiting = server.tryRegister(client, session)
59 59
 	}
60 60
 
61
-	// most servers do this only for PING/PONG, but we'll do it for any command:
62
-	if client.registered {
63
-		// touch even if `exiting`, so we record the time of a QUIT accurately
64
-		session.idletimer.Touch()
65
-	}
66
-
67
-	// TODO: eliminate idletimer entirely in favor of this measurement
68 61
 	if client.registered {
69 62
 		client.Touch(session)
70 63
 	}

+ 0
- 170
irc/idletimer.go View File

@@ -4,179 +4,9 @@
4 4
 package irc
5 5
 
6 6
 import (
7
-	"fmt"
8
-	"sync"
9 7
 	"time"
10
-
11
-	"github.com/oragono/oragono/irc/caps"
12
-)
13
-
14
-const (
15
-	// RegisterTimeout is how long clients have to register before we disconnect them
16
-	RegisterTimeout = time.Minute
17
-	// DefaultIdleTimeout is how long without traffic before we send the client a PING
18
-	DefaultIdleTimeout = time.Minute + 30*time.Second
19
-	// For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
20
-	// (single-onion circuits will close unless the client sends data once every 60 seconds):
21
-	// https://bugs.torproject.org/29665
22
-	TorIdleTimeout = time.Second * 30
23
-	// This is how long a client gets without sending any message, including the PONG to our
24
-	// PING, before we disconnect them:
25
-	DefaultTotalTimeout = 2*time.Minute + 30*time.Second
26
-	// Resumeable clients (clients who have negotiated caps.Resume) get longer:
27
-	ResumeableTotalTimeout = 3*time.Minute + 30*time.Second
28
-)
29
-
30
-// client idleness state machine
31
-
32
-type TimerState uint
33
-
34
-const (
35
-	TimerUnregistered TimerState = iota // client is unregistered
36
-	TimerActive                         // client is actively sending commands
37
-	TimerIdle                           // client is idle, we sent PING and are waiting for PONG
38
-	TimerDead                           // client was terminated
39 8
 )
40 9
 
41
-type IdleTimer struct {
42
-	sync.Mutex // tier 1
43
-
44
-	// immutable after construction
45
-	registerTimeout time.Duration
46
-	session         *Session
47
-
48
-	// mutable
49
-	idleTimeout time.Duration
50
-	quitTimeout time.Duration
51
-	state       TimerState
52
-	timer       *time.Timer
53
-}
54
-
55
-// Initialize sets up an IdleTimer and starts counting idle time;
56
-// if there is no activity from the client, it will eventually be stopped.
57
-func (it *IdleTimer) Initialize(session *Session) {
58
-	it.session = session
59
-	it.registerTimeout = RegisterTimeout
60
-	it.idleTimeout, it.quitTimeout = it.recomputeDurations()
61
-	registered := session.client.Registered()
62
-
63
-	it.Lock()
64
-	defer it.Unlock()
65
-	if registered {
66
-		it.state = TimerActive
67
-	} else {
68
-		it.state = TimerUnregistered
69
-	}
70
-	it.resetTimeout()
71
-}
72
-
73
-// recomputeDurations recomputes the idle and quit durations, given the client's caps.
74
-func (it *IdleTimer) recomputeDurations() (idleTimeout, quitTimeout time.Duration) {
75
-	totalTimeout := DefaultTotalTimeout
76
-	// if they have the resume cap, wait longer before pinging them out
77
-	// to give them a chance to resume their connection
78
-	if it.session.capabilities.Has(caps.Resume) {
79
-		totalTimeout = ResumeableTotalTimeout
80
-	}
81
-
82
-	idleTimeout = DefaultIdleTimeout
83
-	if it.session.isTor {
84
-		idleTimeout = TorIdleTimeout
85
-	}
86
-
87
-	quitTimeout = totalTimeout - idleTimeout
88
-	return
89
-}
90
-
91
-func (it *IdleTimer) Touch() {
92
-	idleTimeout, quitTimeout := it.recomputeDurations()
93
-
94
-	it.Lock()
95
-	defer it.Unlock()
96
-	it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
97
-	// a touch transitions TimerUnregistered or TimerIdle into TimerActive
98
-	if it.state != TimerDead {
99
-		it.state = TimerActive
100
-		it.resetTimeout()
101
-	}
102
-}
103
-
104
-func (it *IdleTimer) processTimeout() {
105
-	idleTimeout, quitTimeout := it.recomputeDurations()
106
-
107
-	var previousState TimerState
108
-	func() {
109
-		it.Lock()
110
-		defer it.Unlock()
111
-		it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
112
-		previousState = it.state
113
-		// TimerActive transitions to TimerIdle, all others to TimerDead
114
-		if it.state == TimerActive {
115
-			// send them a ping, give them time to respond
116
-			it.state = TimerIdle
117
-			it.resetTimeout()
118
-		} else {
119
-			it.state = TimerDead
120
-		}
121
-	}()
122
-
123
-	if previousState == TimerActive {
124
-		it.session.Ping()
125
-	} else {
126
-		it.session.client.Quit(it.quitMessage(previousState), it.session)
127
-		it.session.client.destroy(it.session)
128
-	}
129
-}
130
-
131
-// Stop stops counting idle time.
132
-func (it *IdleTimer) Stop() {
133
-	if it == nil {
134
-		return
135
-	}
136
-
137
-	it.Lock()
138
-	defer it.Unlock()
139
-	it.state = TimerDead
140
-	it.resetTimeout()
141
-}
142
-
143
-func (it *IdleTimer) resetTimeout() {
144
-	if it.timer != nil {
145
-		it.timer.Stop()
146
-	}
147
-	var nextTimeout time.Duration
148
-	switch it.state {
149
-	case TimerUnregistered:
150
-		nextTimeout = it.registerTimeout
151
-	case TimerActive:
152
-		nextTimeout = it.idleTimeout
153
-	case TimerIdle:
154
-		nextTimeout = it.quitTimeout
155
-	case TimerDead:
156
-		return
157
-	}
158
-	if it.timer != nil {
159
-		it.timer.Reset(nextTimeout)
160
-	} else {
161
-		it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
162
-	}
163
-}
164
-
165
-func (it *IdleTimer) quitMessage(state TimerState) string {
166
-	switch state {
167
-	case TimerUnregistered:
168
-		return fmt.Sprintf("Registration timeout: %v", it.registerTimeout)
169
-	case TimerIdle:
170
-		// how many seconds before registered clients are timed out (IdleTimeout plus QuitTimeout).
171
-		it.Lock()
172
-		defer it.Unlock()
173
-		return fmt.Sprintf("Ping timeout: %v", (it.idleTimeout + it.quitTimeout))
174
-	default:
175
-		// shouldn't happen
176
-		return ""
177
-	}
178
-}
179
-
180 10
 // BrbTimer is a timer on the client as a whole (not an individual session) for implementing
181 11
 // the BRB command and related functionality (where a client can remain online without
182 12
 // having any connected sessions).

Loading…
Cancel
Save