@@ -40,9 +40,9 @@ const (
4040 IRCv3TimestampFormat = utils .IRCv3TimestampFormat
4141 // limit the number of device IDs a client can use, as a DoS mitigation
4242 maxDeviceIDsPerClient = 64
43- // controls how often often we write an autoreplay-missed client's
44- // deviceid->lastseentime mapping to the database
45- lastSeenWriteInterval = time . Hour
43+ // maximum total read markers that can be stored
44+ // (writeback of read markers is controlled by lastSeen logic)
45+ maxReadMarkers = 256
4646)
4747
4848const (
@@ -83,7 +83,7 @@ type Client struct {
8383 languages []string
8484 lastActive time.Time // last time they sent a command that wasn't PONG or similar
8585 lastSeen map [string ]time.Time // maps device ID (including "") to time of last received command
86- lastSeenLastWrite time. Time // last time `lastSeen` was written to the datastore
86+ readMarkers map [ string ]time. Time // maps casefolded target to time of last read marker
8787 loginThrottle connection_limits.GenericThrottle
8888 nextSessionID int64 // Incremented when a new session is established
8989 nick string
@@ -101,6 +101,7 @@ type Client struct {
101101 requireSASL bool
102102 registered bool
103103 registerCmdSent bool // already sent the draft/register command, can't send it again
104+ dirtyTimestamps bool // lastSeen or readMarkers is dirty
104105 registrationTimer * time.Timer
105106 server * Server
106107 skeleton string
@@ -745,41 +746,23 @@ func (client *Client) playReattachMessages(session *Session) {
745746// Touch indicates that we received a line from the client (so the connection is healthy
746747// at this time, modulo network latency and fakelag).
747748func (client * Client ) Touch (session * Session ) {
748- var markDirty bool
749749 now := time .Now ().UTC ()
750750 client .stateMutex .Lock ()
751751 if client .registered {
752752 client .updateIdleTimer (session , now )
753753 if client .alwaysOn {
754754 client .setLastSeen (now , session .deviceID )
755- if now .Sub (client .lastSeenLastWrite ) > lastSeenWriteInterval {
756- markDirty = true
757- client .lastSeenLastWrite = now
758- }
755+ client .dirtyTimestamps = true
759756 }
760757 }
761758 client .stateMutex .Unlock ()
762- if markDirty {
763- client .markDirty (IncludeLastSeen )
764- }
765759}
766760
767761func (client * Client ) setLastSeen (now time.Time , deviceID string ) {
768762 if client .lastSeen == nil {
769763 client .lastSeen = make (map [string ]time.Time )
770764 }
771- client .lastSeen [deviceID ] = now
772- // evict the least-recently-used entry if necessary
773- if maxDeviceIDsPerClient < len (client .lastSeen ) {
774- var minLastSeen time.Time
775- var minClientId string
776- for deviceID , lastSeen := range client .lastSeen {
777- if minLastSeen .IsZero () || lastSeen .Before (minLastSeen ) {
778- minClientId , minLastSeen = deviceID , lastSeen
779- }
780- }
781- delete (client .lastSeen , minClientId )
782- }
765+ updateLRUMap (client .lastSeen , deviceID , now , maxDeviceIDsPerClient )
783766}
784767
785768func (client * Client ) updateIdleTimer (session * Session , now time.Time ) {
@@ -1191,7 +1174,6 @@ func (client *Client) Quit(message string, session *Session) {
11911174func (client * Client ) destroy (session * Session ) {
11921175 config := client .server .Config ()
11931176 var sessionsToDestroy []* Session
1194- var saveLastSeen bool
11951177 var quitMessage string
11961178
11971179 client .stateMutex .Lock ()
@@ -1223,20 +1205,6 @@ func (client *Client) destroy(session *Session) {
12231205 }
12241206 }
12251207
1226- // save last seen if applicable:
1227- if alwaysOn {
1228- if client .accountSettings .AutoreplayMissed {
1229- saveLastSeen = true
1230- } else {
1231- for _ , session := range sessionsToDestroy {
1232- if session .deviceID != "" {
1233- saveLastSeen = true
1234- break
1235- }
1236- }
1237- }
1238- }
1239-
12401208 // should we destroy the whole client this time?
12411209 shouldDestroy := ! client .destroyed && remainingSessions == 0 && ! alwaysOn
12421210 // decrement stats on a true destroy, or for the removal of the last connected session
@@ -1246,9 +1214,6 @@ func (client *Client) destroy(session *Session) {
12461214 // if it's our job to destroy it, don't let anyone else try
12471215 client .destroyed = true
12481216 }
1249- if saveLastSeen {
1250- client .dirtyBits |= IncludeLastSeen
1251- }
12521217
12531218 becameAutoAway := false
12541219 var awayMessage string
@@ -1266,14 +1231,6 @@ func (client *Client) destroy(session *Session) {
12661231
12671232 client .stateMutex .Unlock ()
12681233
1269- // XXX there is no particular reason to persist this state here rather than
1270- // any other place: it would be correct to persist it after every `Touch`. However,
1271- // I'm not comfortable introducing that many database writes, and I don't want to
1272- // design a throttle.
1273- if saveLastSeen {
1274- client .wakeWriter ()
1275- }
1276-
12771234 // destroy all applicable sessions:
12781235 for _ , session := range sessionsToDestroy {
12791236 if session .client != client {
@@ -1784,18 +1741,13 @@ func (client *Client) handleRegisterTimeout() {
17841741func (client * Client ) copyLastSeen () (result map [string ]time.Time ) {
17851742 client .stateMutex .RLock ()
17861743 defer client .stateMutex .RUnlock ()
1787- result = make (map [string ]time.Time , len (client .lastSeen ))
1788- for id , lastSeen := range client .lastSeen {
1789- result [id ] = lastSeen
1790- }
1791- return
1744+ return utils .CopyMap (client .lastSeen )
17921745}
17931746
17941747// these are bit flags indicating what part of the client status is "dirty"
17951748// and needs to be read from memory and written to the db
17961749const (
17971750 IncludeChannels uint = 1 << iota
1798- IncludeLastSeen
17991751 IncludeUserModes
18001752 IncludeRealname
18011753)
@@ -1853,9 +1805,6 @@ func (client *Client) performWrite(additionalDirtyBits uint) {
18531805 }
18541806 client .server .accounts .saveChannels (account , channelToModes )
18551807 }
1856- if (dirtyBits & IncludeLastSeen ) != 0 {
1857- client .server .accounts .saveLastSeen (account , client .copyLastSeen ())
1858- }
18591808 if (dirtyBits & IncludeUserModes ) != 0 {
18601809 uModes := make (modes.Modes , 0 , len (modes .SupportedUserModes ))
18611810 for _ , m := range modes .SupportedUserModes {
0 commit comments