Don't send WhoList within 15 seconds of server start

To help alleviate rocky chat server reboots, WhoList messages will be
withheld in the first 15 seconds from server start.

When a lot of chatters were online during a reboot (e.g. 50 or more),
they would all try and reconnect after 5 seconds and each login was
broadcasting Who List updates to everybody else logged in. With the
surge of logins in a short time, these WhoList messages would fill up
the buffers of each recipient, kicking them offline for being too slow
to keep up with messages; only for that recipient to reconnect again.
The average user may have experienced 2 or 3 disconnects when the chat
server reboots.

To help alleviate the spam of messages being sent out:

* Presence messages are withheld for the first 30 seconds ("has joined
  the room" notifications)
* Now, WhoList messages are withheld for the first 15 seconds.

After 16 seconds of uptime, the server will send a WhoList to everybody
currently online to catch them up.

To accommodate this, the front-end will show a spinner and say "Waiting
for Who List..." when the page is connected but no WhoList has been
received yet. Under normal operation, this spinner won't be visible
beyond a brief moment as a WhoList is normally sent upon joining the
chat.
This commit is contained in:
Noah 2025-03-21 20:49:26 -07:00
parent 3180d2ddf9
commit 09da9fa23d
3 changed files with 29 additions and 0 deletions

View File

@ -77,5 +77,16 @@ func (s *Server) ListenAndServe(address string) error {
// Run the polling user idle kicker.
s.upSince = time.Now()
go s.KickIdlePollUsers()
go s.sendWhoListAfterReady()
return http.ListenAndServe(address, s.mux)
}
// Send first WhoList update 15 seconds after the server reboots. This is in case a lot of chatters
// are online during a reboot: we avoid broadcasting Presence for 30 seconds and WhoList for 15 to
// reduce chatter and avoid kicking clients offline repeatedly for filling up their message buffer
// as everyone rejoins the chat all at once.
func (s *Server) sendWhoListAfterReady() {
time.Sleep(16 * time.Second)
log.Info("Up 15 seconds, sending WhoList to any online chatters")
s.SendWhoList()
}

View File

@ -429,6 +429,18 @@ func (s *Server) SendTo(username string, msg messages.Message) error {
// SendWhoList broadcasts the connected members to everybody in the room.
func (s *Server) SendWhoList() {
// Don't send WhoList messages in the first 15 seconds of the server launch. This is to minimize
// messages sent during a server reboot if a lot of chatters were online: Presence messages are
// suppressed for 30 seconds, WhoList updates for 15, so that each user who reconnects doesn't
// spam updates to every other user, which would fill their message buffer and kick them off and
// makes for a rocky reboot. Instead: the server will send a WhoList to everyone at the 15 second
// mark, and then send them normally from then on.
if time.Since(s.upSince) < 15*time.Second {
log.Debug("skip sending WhoList messages within 15 seconds of server reboot")
return
}
var (
subscribers = s.IterSubscribers()
usernames = []string{} // distinct and sorted usernames

View File

@ -5416,6 +5416,12 @@ export default {
<!-- Who Is Online -->
<div v-if="whoTab === 'online'">
<!-- Show a loading spinner if we are connected but the Who List hasn't arrived -->
<div v-if="connected && sortedWhoList.length === 0" class="is-size-7">
<i class="fa fa-spinner fa-spin mr-1"></i> Waiting for Who List...
</div>
<div v-for="(u, i) in sortedWhoList" v-bind:key="i">
<WhoListRow
:user="u"