Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/bot.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,7 @@ pub struct CacheEntryConfig {
| `recent_messages` | 5 minutes | 0 (disabled) | Optional L1 in-memory cache for sent messages (retry support) |
| `message_retry_counts` | 5 minutes | 1,000 | Retry count tracking |
| `pdo_pending_requests` | 30 seconds | 500 | PDO pending requests |
| `pdo_requested` | 24 hours | 512 | PDO placeholder-resend memo — at-most-once per message |
| `sender_key_devices_cache` | 1 hour (TTI) | 500 | Per-group SKDM distribution state |

<Note>
Expand Down
1 change: 1 addition & 0 deletions api/client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1821,6 +1821,7 @@ Returns a snapshot of all internal collection sizes for memory leak detection. M
| `recent_messages` | `u64` | Recent message dedup cache |
| `message_retry_counts` | `u64` | Message retry counter cache |
| `pdo_pending_requests` | `u64` | PDO pending request cache |
| `pdo_requested` | `u64` | PDO once-per-message memo cache |
| `sender_key_device_cache` | `u64` | Per-group sender key device tracking cache |
| `session_locks` | `u64` | Per-device session locks |
| `chat_lanes` | `u64` | Per-chat lanes (combined enqueue lock + message queue) |
Expand Down
53 changes: 53 additions & 0 deletions changelog/2026-06-11-pdo-once-per-message.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: "June 11, 2026 — PDO placeholder-resend: at-most-once per message"
description: "Adds a pdo_requested memo cache that caps PDO placeholder-resend requests to one per message, skips a redundant migration retry decrypt when nothing moved, and adjusts noisy log levels."
---

## Bug Fix

**PDO placeholder-resend: at-most-once per message ([#841](https://github.com/oxidezap/whatsapp-rust/pull/841))**

A peer device with cloned Signal state could redeliver the same undecryptable message every ~11 seconds. Each copy triggered a PDO placeholder-resend request to our own phone — the `pdo_pending_requests` dedup only covered in-flight requests, so it emptied the moment the phone answered (~800 ms), and the next copy immediately opened a new one. In a three-hour storm this produced ~700 redundant requests with no benefit: the phone had already answered without content, so re-asking could not produce anything new.

**One request per message.** A new `pdo_requested` memo cache (24h TTL, 512 entries) gates `send_pdo_placeholder_resend_request` — mirroring `WAWebNonMessageDataRequestPlaceholderMessageResendUtils`, which uses a session-lifetime set for the same purpose. The memo slot is released if the send itself fails, so a transient error does not permanently block recovery. A content-less phone response leaves the memo in place (the phone has nothing to share for this message; re-asking on the next redelivery cannot help).

**Skip migration retry decrypt when nothing moved.** `migrate_signal_sessions_on_lid_discovery` now returns `bool` indicating whether any sessions moved into a LID slot. When it returns `false`, the subsequent retry decrypt in `try_pn_to_lid_migration_decrypt` is skipped: the Signal state is unchanged, so the retry would fail identically and only add a second decrypt error to the log for every redelivered copy.

**Log-level adjustments.** Three lines that fired once per redelivered copy are brought in line with WA Web's telemetry handling:
- "Skipping skmsg decryption" → `debug` (WA Web's `canDecryptNext` skips silently after a retryable pkmsg failure)
- "missing message content" on a PDO response → `info` (WA Web counts this outcome in telemetry only, no warning)
- "Max retries reached" at the PDO fallback → `debug` (the high-retry `warn!` already fired on the way to the cap)

With these changes, the same storm would produce 1 PDO request, 1 decrypt error per copy, and the existing retry receipt cap — instead of thousands of WARN/ERROR lines and hundreds of peer messages.

## Breaking changes

**`CacheConfig` gains a new field (`whatsapp-rust`)**

`CacheConfig` now has a `pdo_requested` field (default: 24h TTL, 512 entries). Struct literals that spell out every field rather than using `..Default::default()` will fail to compile.

```rust
// Update struct literals that do not use ..Default::default():
let config = CacheConfig {
// your overrides ...
..Default::default() // pdo_requested gets its default; no action required
};
```

To tune the memo TTL or capacity:

```rust
use std::time::Duration;
use whatsapp_rust::{CacheConfig, CacheEntryConfig};

let config = CacheConfig {
// Extend the TTL beyond the 24h default if you want the memo to survive
// across longer offline gaps without re-asking the phone.
pdo_requested: CacheEntryConfig::new(Some(Duration::from_secs(48 * 3600)), 512),
..Default::default()
};
```

**`MemoryDiagnostics` gains a new field (`whatsapp-rust`)**

`MemoryDiagnostics` now has a `pdo_requested: u64` field. Code that exhaustively pattern-matches or constructs `MemoryDiagnostics` directly will need updating.
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"group": "Changelog",
"pages": [
"changelog/overview",
"changelog/2026-06-11-pdo-once-per-message",
"changelog/2026-06-11-history-sync-secret-prescan",
"changelog/2026-06-10-jid-into-api-convention",
"changelog/2026-06-10-prekey-unupload-watermark",
Expand Down