Status broadcasts block the handler queue for ~75s each; opt-in skip?
Problem
handleEncryptedMessage tries to decrypt every incoming status broadcast. On a companion device those messages are often undecryptable, so the attempt fails and a retry receipt follows. That path is slow, and handlerQueueLoop processes nodes in order, so every real message waits behind it.
A client that never reads other people's statuses still pays the full cost for each one.
Evidence
One bridge account, 18 days of logs, 18,123 lines. The account posts no statuses and receives about 80 a day.
| measurement | value |
|---|---|
| lines that are "node handling is taking long" / "took" warnings | 10,960 (60% of the log) |
"taking long" warnings whose node was status@broadcast |
7,271 of 7,304 (99.5%) |
| "taking long" warnings from any other JID | 33 |
nodes that took exactly 1m15s |
3,582 |
The 1m15s figure is the striking part. It is not a spread — 3,582 nodes land on the same value, which points at a fixed timeout in the decrypt-and-retry path rather than at load.
The practical effect: an incoming message could sit for over a minute before the client stored it. On this account that also delayed an outgoing-message feature that reacts to the user's own text.
What helped, and what did not
Upgrading whatsmeow to the current version cut the warnings a long way, so recent work already improved this. It did not remove them.
Skipping status broadcasts before the decrypt attempt removed them completely. Measured over a 12-minute window after the change: 0 stalls over 5s, 0 over 30s.
Proposal
An opt-in client field, so nothing changes for callers who do want statuses:
// SkipStatusBroadcasts makes the client ack and discard incoming status
// broadcasts (JID status@broadcast) without attempting to decrypt them.
SkipStatusBroadcasts boolChecked early in handleEncryptedMessage:
if cli.SkipStatusBroadcasts && info.Chat == types.StatusBroadcastJID {
cli.Log.Debugf("Skipping status broadcast %s from %s", info.ID, info.Sender)
cli.sendAck(ctx, node, 0)
return
}The node is still acked, so the server does not resend it. Outgoing statuses from the account are unaffected, and so are status nodes from newsletters.
Default is false, which keeps present behaviour.
I am happy to open a PR — the change is 21 lines across client.go and message.go, and has run in production for a day. Let me know if you would rather solve it another way, for example by deciding this inside the existing filtering rather than adding a field.
Environment
- whatsmeow: current
mainas of 2026-09-06 - Go 1.24, linux/arm64
- Companion device (multi-device pairing), single account
Source: tulir/whatsmeow