Bladeren bron

change "last signoff" tracking to "last seen"

Explicit quit and ping timeout behave the same way,
but reattach after abandoning/losing the previous session
(without the break being detected server-side) is more aggressive
about replaying missed messages, at the cost of potential duplication.
tags/v2.0.0-rc1
Shivaram Lingamneni 4 jaren geleden
bovenliggende
commit
db39608bcb
7 gewijzigde bestanden met toevoegingen van 78 en 91 verwijderingen
  1. 15
    11
      irc/accounts.go
  2. 2
    2
      irc/channel.go
  3. 51
    47
      irc/client.go
  4. 2
    2
      irc/client_lookup_set.go
  5. 3
    2
      irc/commands.go
  6. 5
    15
      irc/getters.go
  7. 0
    12
      irc/idletimer.go

+ 15
- 11
irc/accounts.go Bestand weergeven

@@ -35,7 +35,7 @@ const (
35 35
 	keyCertToAccount           = "account.creds.certfp %s"
36 36
 	keyAccountChannels         = "account.channels %s" // channels registered to the account
37 37
 	keyAccountJoinedChannels   = "account.joinedto %s" // channels a persistent client has joined
38
-	keyAccountLastSignoff      = "account.lastsignoff %s"
38
+	keyAccountLastSeen         = "account.lastseen %s"
39 39
 
40 40
 	keyVHostQueueAcctToId = "vhostQueue %s"
41 41
 	vhostRequestIdx       = "vhostQueue"
@@ -104,7 +104,7 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) {
104 104
 		account, err := am.LoadAccount(accountName)
105 105
 		if err == nil && account.Verified &&
106 106
 			persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, account.Settings.AlwaysOn) {
107
-			am.server.AddAlwaysOnClient(account, am.loadChannels(accountName), am.loadLastSignoff(accountName))
107
+			am.server.AddAlwaysOnClient(account, am.loadChannels(accountName), am.loadLastSeen(accountName))
108 108
 		}
109 109
 	}
110 110
 }
@@ -535,11 +535,11 @@ func (am *AccountManager) loadChannels(account string) (channels []string) {
535 535
 	return
536 536
 }
537 537
 
538
-func (am *AccountManager) saveLastSignoff(account string, lastSignoff time.Time) {
539
-	key := fmt.Sprintf(keyAccountLastSignoff, account)
538
+func (am *AccountManager) saveLastSeen(account string, lastSeen time.Time) {
539
+	key := fmt.Sprintf(keyAccountLastSeen, account)
540 540
 	var val string
541
-	if !lastSignoff.IsZero() {
542
-		val = strconv.FormatInt(lastSignoff.UnixNano(), 10)
541
+	if !lastSeen.IsZero() {
542
+		val = strconv.FormatInt(lastSeen.UnixNano(), 10)
543 543
 	}
544 544
 	am.server.store.Update(func(tx *buntdb.Tx) error {
545 545
 		if val != "" {
@@ -551,11 +551,15 @@ func (am *AccountManager) saveLastSignoff(account string, lastSignoff time.Time)
551 551
 	})
552 552
 }
553 553
 
554
-func (am *AccountManager) loadLastSignoff(account string) (lastSignoff time.Time) {
555
-	key := fmt.Sprintf(keyAccountLastSignoff, account)
554
+func (am *AccountManager) loadLastSeen(account string) (lastSeen time.Time) {
555
+	key := fmt.Sprintf(keyAccountLastSeen, account)
556 556
 	var lsText string
557
-	am.server.store.View(func(tx *buntdb.Tx) error {
557
+	am.server.store.Update(func(tx *buntdb.Tx) error {
558 558
 		lsText, _ = tx.Get(key)
559
+		// XXX clear this on startup, because it's not clear when it's
560
+		// going to be overwritten, and restarting the server twice in a row
561
+		// could result in a large amount of duplicated history replay
562
+		tx.Delete(key)
559 563
 		return nil
560 564
 	})
561 565
 	lsNum, err := strconv.ParseInt(lsText, 10, 64)
@@ -1071,7 +1075,7 @@ func (am *AccountManager) Unregister(account string) error {
1071 1075
 	vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount)
1072 1076
 	channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
1073 1077
 	joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount)
1074
-	lastSignoffKey := fmt.Sprintf(keyAccountLastSignoff, casefoldedAccount)
1078
+	lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
1075 1079
 
1076 1080
 	var clients []*Client
1077 1081
 
@@ -1108,7 +1112,7 @@ func (am *AccountManager) Unregister(account string) error {
1108 1112
 		channelsStr, _ = tx.Get(channelsKey)
1109 1113
 		tx.Delete(channelsKey)
1110 1114
 		tx.Delete(joinedChannelsKey)
1111
-		tx.Delete(lastSignoffKey)
1115
+		tx.Delete(lastSeenKey)
1112 1116
 
1113 1117
 		_, err := tx.Delete(vhostQueueKey)
1114 1118
 		am.decrementVHostQueueCount(casefoldedAccount, err)

+ 2
- 2
irc/channel.go Bestand weergeven

@@ -775,9 +775,9 @@ func (channel *Channel) autoReplayHistory(client *Client, rb *ResponseBuffer, sk
775 775
 	var after, before time.Time
776 776
 	if rb.session.zncPlaybackTimes != nil && (rb.session.zncPlaybackTimes.targets == nil || rb.session.zncPlaybackTimes.targets.Has(channel.NameCasefolded())) {
777 777
 		after, before = rb.session.zncPlaybackTimes.after, rb.session.zncPlaybackTimes.before
778
-	} else if !rb.session.lastSignoff.IsZero() {
778
+	} else if !rb.session.autoreplayMissedSince.IsZero() {
779 779
 		// we already checked for history caps in `playReattachMessages`
780
-		after = rb.session.lastSignoff
780
+		after = rb.session.autoreplayMissedSince
781 781
 	}
782 782
 
783 783
 	if !after.IsZero() || !before.IsZero() {

+ 51
- 47
irc/client.go Bestand weergeven

@@ -47,7 +47,6 @@ type Client struct {
47 47
 	accountName        string // display name of the account: uncasefolded, '*' if not logged in
48 48
 	accountRegDate     time.Time
49 49
 	accountSettings    AccountSettings
50
-	atime              time.Time
51 50
 	away               bool
52 51
 	awayMessage        string
53 52
 	brbTimer           BrbTimer
@@ -60,7 +59,8 @@ type Client struct {
60 59
 	invitedTo          map[string]bool
61 60
 	isSTSOnly          bool
62 61
 	languages          []string
63
-	lastSignoff        time.Time // for always-on clients, the time their last session quit
62
+	lastActive         time.Time // last time they sent a command that wasn't PONG or similar
63
+	lastSeen           time.Time // last time they sent any kind of command
64 64
 	loginThrottle      connection_limits.GenericThrottle
65 65
 	nick               string
66 66
 	nickCasefolded     string
@@ -103,8 +103,8 @@ func (s *saslStatus) Clear() {
103 103
 type Session struct {
104 104
 	client *Client
105 105
 
106
-	ctime time.Time
107
-	atime time.Time
106
+	ctime      time.Time
107
+	lastActive time.Time
108 108
 
109 109
 	socket      *Socket
110 110
 	realIP      net.IP
@@ -130,10 +130,10 @@ type Session struct {
130 130
 
131 131
 	registrationMessages int
132 132
 
133
-	resumeID         string
134
-	resumeDetails    *ResumeDetails
135
-	zncPlaybackTimes *zncPlaybackTimes
136
-	lastSignoff      time.Time
133
+	resumeID              string
134
+	resumeDetails         *ResumeDetails
135
+	zncPlaybackTimes      *zncPlaybackTimes
136
+	autoreplayMissedSince time.Time
137 137
 
138 138
 	batch MultilineBatch
139 139
 }
@@ -247,11 +247,12 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
247 247
 	// give them 1k of grace over the limit:
248 248
 	socket := NewSocket(conn.Conn, ircmsg.MaxlenTagsFromClient+512+1024, config.Server.MaxSendQBytes)
249 249
 	client := &Client{
250
-		atime:     now,
251
-		channels:  make(ChannelSet),
252
-		ctime:     now,
253
-		isSTSOnly: conn.Config.STSOnly,
254
-		languages: server.Languages().Default(),
250
+		lastSeen:   now,
251
+		lastActive: now,
252
+		channels:   make(ChannelSet),
253
+		ctime:      now,
254
+		isSTSOnly:  conn.Config.STSOnly,
255
+		languages:  server.Languages().Default(),
255 256
 		loginThrottle: connection_limits.GenericThrottle{
256 257
 			Duration: config.Accounts.LoginThrottling.Duration,
257 258
 			Limit:    config.Accounts.LoginThrottling.MaxAttempts,
@@ -270,7 +271,7 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
270 271
 		capVersion: caps.Cap301,
271 272
 		capState:   caps.NoneState,
272 273
 		ctime:      now,
273
-		atime:      now,
274
+		lastActive: now,
274 275
 		realIP:     realIP,
275 276
 		isTor:      conn.Config.Tor,
276 277
 	}
@@ -306,24 +307,27 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
306 307
 	client.run(session, proxyLine)
307 308
 }
308 309
 
309
-func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSignoff time.Time) {
310
+func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastActive time.Time) {
310 311
 	now := time.Now().UTC()
311 312
 	config := server.Config()
313
+	if lastActive.IsZero() {
314
+		lastActive = now
315
+	}
312 316
 
313 317
 	client := &Client{
314
-		atime:     now,
315
-		channels:  make(ChannelSet),
316
-		ctime:     now,
317
-		languages: server.Languages().Default(),
318
-		server:    server,
318
+		lastSeen:   now,
319
+		lastActive: lastActive,
320
+		channels:   make(ChannelSet),
321
+		ctime:      now,
322
+		languages:  server.Languages().Default(),
323
+		server:     server,
319 324
 
320 325
 		// TODO figure out how to set these on reattach?
321 326
 		username:    "~user",
322 327
 		rawHostname: server.name,
323 328
 		realIP:      utils.IPv4LoopbackAddress,
324 329
 
325
-		alwaysOn:    true,
326
-		lastSignoff: lastSignoff,
330
+		alwaysOn: true,
327 331
 	}
328 332
 
329 333
 	client.SetMode(modes.TLS, true)
@@ -662,25 +666,30 @@ func (client *Client) playReattachMessages(session *Session) {
662 666
 		channel.autoReplayHistory(client, rb, "")
663 667
 		rb.Send(true)
664 668
 	}
665
-	if !session.lastSignoff.IsZero() && !hasHistoryCaps {
669
+	if !session.autoreplayMissedSince.IsZero() && !hasHistoryCaps {
666 670
 		rb := NewResponseBuffer(session)
667
-		zncPlayPrivmsgs(client, rb, session.lastSignoff, time.Time{})
671
+		zncPlayPrivmsgs(client, rb, session.autoreplayMissedSince, time.Time{})
668 672
 		rb.Send(true)
669 673
 	}
670
-	session.lastSignoff = time.Time{}
674
+	session.autoreplayMissedSince = time.Time{}
671 675
 }
672 676
 
673 677
 //
674 678
 // idle, quit, timers and timeouts
675 679
 //
676 680
 
677
-// Active updates when the client was last 'active' (i.e. the user should be sitting in front of their client).
678
-func (client *Client) Active(session *Session) {
681
+// Touch indicates that we received a line from the client (so the connection is healthy
682
+// at this time, modulo network latency and fakelag). `active` means not a PING or suchlike
683
+// (i.e. the user should be sitting in front of their client).
684
+func (client *Client) Touch(active bool, session *Session) {
679 685
 	now := time.Now().UTC()
680 686
 	client.stateMutex.Lock()
681 687
 	defer client.stateMutex.Unlock()
682
-	session.atime = now
683
-	client.atime = now
688
+	client.lastSeen = now
689
+	if active {
690
+		client.lastActive = now
691
+		session.lastActive = now
692
+	}
684 693
 }
685 694
 
686 695
 // Ping sends the client a PING message.
@@ -896,7 +905,7 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
896 905
 func (client *Client) IdleTime() time.Duration {
897 906
 	client.stateMutex.RLock()
898 907
 	defer client.stateMutex.RUnlock()
899
-	return time.Since(client.atime)
908
+	return time.Since(client.lastActive)
900 909
 }
901 910
 
902 911
 // SignonTime returns this client's signon time as a unix timestamp.
@@ -1151,12 +1160,6 @@ func (client *Client) Quit(message string, session *Session) {
1151 1160
 // has no more sessions.
1152 1161
 func (client *Client) destroy(session *Session) {
1153 1162
 	var sessionsToDestroy []*Session
1154
-	var lastSignoff time.Time
1155
-	if session != nil {
1156
-		lastSignoff = session.idletimer.LastTouch()
1157
-	} else {
1158
-		lastSignoff = time.Now().UTC()
1159
-	}
1160 1163
 
1161 1164
 	client.stateMutex.Lock()
1162 1165
 	details := client.detailsNoMutex()
@@ -1166,6 +1169,7 @@ func (client *Client) destroy(session *Session) {
1166 1169
 	sessionRemoved := false
1167 1170
 	registered := client.registered
1168 1171
 	alwaysOn := client.alwaysOn
1172
+	saveLastSeen := alwaysOn && client.accountSettings.AutoreplayMissed
1169 1173
 	var remainingSessions int
1170 1174
 	if session == nil {
1171 1175
 		sessionsToDestroy = client.sessions
@@ -1187,16 +1191,17 @@ func (client *Client) destroy(session *Session) {
1187 1191
 		// if it's our job to destroy it, don't let anyone else try
1188 1192
 		client.destroyed = true
1189 1193
 	}
1190
-	if alwaysOn && remainingSessions == 0 {
1191
-		client.lastSignoff = lastSignoff
1192
-		client.dirtyBits |= IncludeLastSignoff
1193
-	} else {
1194
-		lastSignoff = time.Time{}
1194
+	if saveLastSeen {
1195
+		client.dirtyBits |= IncludeLastSeen
1195 1196
 	}
1196 1197
 	exitedSnomaskSent := client.exitedSnomaskSent
1197 1198
 	client.stateMutex.Unlock()
1198 1199
 
1199
-	if !lastSignoff.IsZero() {
1200
+	// XXX there is no particular reason to persist this state here rather than
1201
+	// any other place: it would be correct to persist it after every `Touch`. However,
1202
+	// I'm not comfortable introducing that many database writes, and I don't want to
1203
+	// design a throttle.
1204
+	if saveLastSeen {
1200 1205
 		client.wakeWriter()
1201 1206
 	}
1202 1207
 
@@ -1571,10 +1576,9 @@ func (client *Client) historyStatus(config *Config) (status HistoryStatus, targe
1571 1576
 
1572 1577
 // these are bit flags indicating what part of the client status is "dirty"
1573 1578
 // and needs to be read from memory and written to the db
1574
-// TODO add a dirty flag for lastSignoff
1575 1579
 const (
1576 1580
 	IncludeChannels uint = 1 << iota
1577
-	IncludeLastSignoff
1581
+	IncludeLastSeen
1578 1582
 )
1579 1583
 
1580 1584
 func (client *Client) markDirty(dirtyBits uint) {
@@ -1629,10 +1633,10 @@ func (client *Client) performWrite() {
1629 1633
 		}
1630 1634
 		client.server.accounts.saveChannels(account, channelNames)
1631 1635
 	}
1632
-	if (dirtyBits & IncludeLastSignoff) != 0 {
1636
+	if (dirtyBits & IncludeLastSeen) != 0 {
1633 1637
 		client.stateMutex.RLock()
1634
-		lastSignoff := client.lastSignoff
1638
+		lastSeen := client.lastSeen
1635 1639
 		client.stateMutex.RUnlock()
1636
-		client.server.accounts.saveLastSignoff(account, lastSignoff)
1640
+		client.server.accounts.saveLastSeen(account, lastSeen)
1637 1641
 	}
1638 1642
 }

+ 2
- 2
irc/client_lookup_set.go Bestand weergeven

@@ -174,7 +174,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
174 174
 		if registered || !bouncerAllowed || account == "" || account != currentClient.Account() || client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
175 175
 			return "", errNicknameInUse
176 176
 		}
177
-		reattachSuccessful, numSessions, lastSignoff := currentClient.AddSession(session)
177
+		reattachSuccessful, numSessions, lastSeen := currentClient.AddSession(session)
178 178
 		if !reattachSuccessful {
179 179
 			return "", errNicknameInUse
180 180
 		}
@@ -183,7 +183,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
183 183
 			operator := client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator)
184 184
 			client.server.stats.AddRegistered(invisible, operator)
185 185
 		}
186
-		session.lastSignoff = lastSignoff
186
+		session.autoreplayMissedSince = lastSeen
187 187
 		// XXX SetNames only changes names if they are unset, so the realname change only
188 188
 		// takes effect on first attach to an always-on client (good), but the user/ident
189 189
 		// change is always a no-op (bad). we could make user/ident act the same way as

+ 3
- 2
irc/commands.go Bestand weergeven

@@ -65,8 +65,9 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
65 65
 		session.idletimer.Touch()
66 66
 	}
67 67
 
68
-	if !exiting && client.registered && !cmd.leaveClientIdle {
69
-		client.Active(session)
68
+	// TODO: eliminate idletimer entirely in favor of this measurement
69
+	if client.registered {
70
+		client.Touch(!cmd.leaveClientIdle, session)
70 71
 	}
71 72
 
72 73
 	return exiting

+ 5
- 15
irc/getters.go Bestand weergeven

@@ -79,7 +79,7 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat
79 79
 			currentIndex = i
80 80
 		}
81 81
 		data[i] = SessionData{
82
-			atime:    session.atime,
82
+			atime:    session.lastActive,
83 83
 			ctime:    session.ctime,
84 84
 			hostname: session.rawHostname,
85 85
 			certfp:   session.certfp,
@@ -93,13 +93,7 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat
93 93
 	return
94 94
 }
95 95
 
96
-func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSignoff time.Time) {
97
-	defer func() {
98
-		if !lastSignoff.IsZero() {
99
-			client.wakeWriter()
100
-		}
101
-	}()
102
-
96
+func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time) {
103 97
 	client.stateMutex.Lock()
104 98
 	defer client.stateMutex.Unlock()
105 99
 
@@ -112,15 +106,11 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
112 106
 	newSessions := make([]*Session, len(client.sessions)+1)
113 107
 	copy(newSessions, client.sessions)
114 108
 	newSessions[len(newSessions)-1] = session
115
-	if len(client.sessions) == 0 && client.accountSettings.AutoreplayMissed {
116
-		// n.b. this is only possible if client is persistent and remained
117
-		// on the server with no sessions:
118
-		lastSignoff = client.lastSignoff
119
-		client.lastSignoff = time.Time{}
120
-		client.dirtyBits |= IncludeLastSignoff
109
+	if client.accountSettings.AutoreplayMissed {
110
+		lastSeen = client.lastSeen
121 111
 	}
122 112
 	client.sessions = newSessions
123
-	return true, len(client.sessions), lastSignoff
113
+	return true, len(client.sessions), lastSeen
124 114
 }
125 115
 
126 116
 func (client *Client) removeSession(session *Session) (success bool, length int) {

+ 0
- 12
irc/idletimer.go Bestand weergeven

@@ -52,7 +52,6 @@ type IdleTimer struct {
52 52
 	quitTimeout time.Duration
53 53
 	state       TimerState
54 54
 	timer       *time.Timer
55
-	lastTouch   time.Time
56 55
 }
57 56
 
58 57
 // Initialize sets up an IdleTimer and starts counting idle time;
@@ -62,11 +61,9 @@ func (it *IdleTimer) Initialize(session *Session) {
62 61
 	it.registerTimeout = RegisterTimeout
63 62
 	it.idleTimeout, it.quitTimeout = it.recomputeDurations()
64 63
 	registered := session.client.Registered()
65
-	now := time.Now().UTC()
66 64
 
67 65
 	it.Lock()
68 66
 	defer it.Unlock()
69
-	it.lastTouch = now
70 67
 	if registered {
71 68
 		it.state = TimerActive
72 69
 	} else {
@@ -95,12 +92,10 @@ func (it *IdleTimer) recomputeDurations() (idleTimeout, quitTimeout time.Duratio
95 92
 
96 93
 func (it *IdleTimer) Touch() {
97 94
 	idleTimeout, quitTimeout := it.recomputeDurations()
98
-	now := time.Now().UTC()
99 95
 
100 96
 	it.Lock()
101 97
 	defer it.Unlock()
102 98
 	it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
103
-	it.lastTouch = now
104 99
 	// a touch transitions TimerUnregistered or TimerIdle into TimerActive
105 100
 	if it.state != TimerDead {
106 101
 		it.state = TimerActive
@@ -108,13 +103,6 @@ func (it *IdleTimer) Touch() {
108 103
 	}
109 104
 }
110 105
 
111
-func (it *IdleTimer) LastTouch() (result time.Time) {
112
-	it.Lock()
113
-	result = it.lastTouch
114
-	it.Unlock()
115
-	return
116
-}
117
-
118 106
 func (it *IdleTimer) processTimeout() {
119 107
 	idleTimeout, quitTimeout := it.recomputeDurations()
120 108
 

Laden…
Annuleren
Opslaan