mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-03-30 18:35:59 +00:00
agent util specs
This commit is contained in:
9
spec/modules/Simplex/Messaging/Agent/Env/SQLite.md
Normal file
9
spec/modules/Simplex/Messaging/Agent/Env/SQLite.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Simplex.Messaging.Agent.Env.SQLite
|
||||
|
||||
> Agent environment configuration, default values, and worker/supervisor record types.
|
||||
|
||||
**Source**: [`Agent/Env/SQLite.hs`](../../../../../../src/Simplex/Messaging/Agent/Env/SQLite.hs)
|
||||
|
||||
## mkUserServers — silent fallback on all-disabled
|
||||
|
||||
See comment on `mkUserServers`. If filtering servers by `enabled && role` yields an empty list, `fromMaybe srvs` falls back to *all* servers regardless of enabled/role status. This prevents a configuration where all servers are disabled from leaving the user with no servers — but means disabled servers can still be used if every server in a role is disabled.
|
||||
7
spec/modules/Simplex/Messaging/Agent/Lock.md
Normal file
7
spec/modules/Simplex/Messaging/Agent/Lock.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Simplex.Messaging.Agent.Lock
|
||||
|
||||
> TMVar-based named mutex with concurrent multi-lock acquisition.
|
||||
|
||||
**Source**: [`Agent/Lock.hs`](../../../../../src/Simplex/Messaging/Agent/Lock.hs)
|
||||
|
||||
No non-obvious behavior. See source. See comment on `getPutLock` for the atomicity argument.
|
||||
7
spec/modules/Simplex/Messaging/Agent/QueryString.md
Normal file
7
spec/modules/Simplex/Messaging/Agent/QueryString.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Simplex.Messaging.Agent.QueryString
|
||||
|
||||
> HTTP query string parsing utilities for connection link URIs.
|
||||
|
||||
**Source**: [`Agent/QueryString.hs`](../../../../../src/Simplex/Messaging/Agent/QueryString.hs)
|
||||
|
||||
No non-obvious behavior. See source.
|
||||
35
spec/modules/Simplex/Messaging/Agent/RetryInterval.md
Normal file
35
spec/modules/Simplex/Messaging/Agent/RetryInterval.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Simplex.Messaging.Agent.RetryInterval
|
||||
|
||||
> Retry-with-backoff combinators for agent reconnection and worker loops.
|
||||
|
||||
**Source**: [`Agent/RetryInterval.hs`](../../../../../src/Simplex/Messaging/Agent/RetryInterval.hs)
|
||||
|
||||
## Overview
|
||||
|
||||
Four retry combinators with increasing sophistication: basic (`withRetryInterval`), counted (`withRetryIntervalCount`), foreground-aware (`withRetryForeground`), and dual-interval with external wake-up (`withRetryLock2`). All share the same backoff curve via `nextRetryDelay`.
|
||||
|
||||
## Backoff curve — nextRetryDelay
|
||||
|
||||
Delay stays constant at `initialInterval` until `elapsed >= increaseAfter`, then grows by 1.5x per step (`delay * 3 / 2`) up to `maxInterval`. The `delay == maxInterval` guard short-circuits the comparison once the cap is reached.
|
||||
|
||||
## updateRetryInterval2 — resume from saved state
|
||||
|
||||
Sets `increaseAfter = 0` on both intervals. This skips the initial constant-delay phase — the next retry will immediately begin increasing from the saved interval. Used to restore retry state across reconnections without restarting from the initial interval.
|
||||
|
||||
## withRetryForeground — reset on foreground/online transition
|
||||
|
||||
The retry loop resets to `initialInterval` when either:
|
||||
- The app transitions from background to foreground (`not wasForeground && foreground`)
|
||||
- The network transitions from offline to online (`not wasOnline && online`)
|
||||
|
||||
The STM transaction blocks on three things simultaneously: the `registerDelay` timer, the `isForeground` TVar, and the `isOnline` TVar. Whichever fires first unblocks the retry. On reset, elapsed time is zeroed.
|
||||
|
||||
The `registerDelay` is capped at `maxBound :: Int` (~36 minutes on 32-bit) to prevent overflow.
|
||||
|
||||
## withRetryLock2 — interruptible dual-interval retry
|
||||
|
||||
Maintains two independent backoff states (slow and fast) that the action toggles between by calling the loop continuation with `RISlow` or `RIFast`. Only the chosen interval advances; the other preserves its state.
|
||||
|
||||
The `wait` function is the non-obvious part: it spawns a timer thread that puts `()` into the `lock` TMVar after the delay, while the main thread blocks on `takeTMVar lock`. This means the retry can be woken early by *external code* putting into the same TMVar — the timer is just a fallback. The `waiting` TVar prevents a stale timer from firing after the main thread has already been woken by an external signal.
|
||||
|
||||
**Consumed by**: [Agent/Client.hs](./Client.md) — `reconnectSMPClient` uses the lock TMVar to allow immediate reconnection when new subscriptions arrive, rather than waiting for the full backoff delay.
|
||||
7
spec/modules/Simplex/Messaging/Agent/Stats.md
Normal file
7
spec/modules/Simplex/Messaging/Agent/Stats.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Simplex.Messaging.Agent.Stats
|
||||
|
||||
> Per-server statistics counters (SMP, XFTP, NTF) with TVar-based live state and serializable snapshots.
|
||||
|
||||
**Source**: [`Agent/Stats.hs`](../../../../../src/Simplex/Messaging/Agent/Stats.hs)
|
||||
|
||||
No non-obvious behavior. See source.
|
||||
60
spec/modules/Simplex/Messaging/Agent/TSessionSubs.md
Normal file
60
spec/modules/Simplex/Messaging/Agent/TSessionSubs.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Simplex.Messaging.Agent.TSessionSubs
|
||||
|
||||
> Per-session subscription state machine tracking active and pending queue subscriptions.
|
||||
|
||||
**Source**: [`Agent/TSessionSubs.hs`](../../../../../src/Simplex/Messaging/Agent/TSessionSubs.hs)
|
||||
|
||||
## Overview
|
||||
|
||||
TSessionSubs manages the two-tier (active/pending) subscription state for SMP queues, keyed by transport session. Every subscription confirmation from a router is validated against the current session ID before being promoted to active — if the session has changed (reconnect happened), the subscription is demoted to pending for resubscription.
|
||||
|
||||
Service subscriptions (aggregate, router-managed) and queue subscriptions (individual, per-recipient-ID) are tracked separately but follow the same active/pending pattern.
|
||||
|
||||
**Consumed by**: [Agent/Client.hs](./Client.md) — `subscribeSMPQueues`, `subscribeSessQueues_`, `resubscribeSMPSession`, `smpClientDisconnected`.
|
||||
|
||||
## Session ID gating
|
||||
|
||||
The central invariant: a subscription is only active if it was confirmed on the *current* TLS session. Every function that promotes subscriptions to active (`addActiveSub'`, `batchAddActiveSubs`, `setActiveServiceSub`) checks `Just sessId == sessId'` (stored session ID). On mismatch, the subscription goes to pending instead — silently, with no error.
|
||||
|
||||
This means subscription RPCs that succeed but return after a reconnect are safely caught: the response carries the old session ID, which won't match the new one stored by `setSessionId`.
|
||||
|
||||
## setSessionId — silent demotion on reconnect
|
||||
|
||||
`setSessionId` has two behaviors:
|
||||
- **First call** (stored is `Nothing`): stores the session ID. No side effects.
|
||||
- **Subsequent call with different ID**: calls `setSubsPending_`, which moves *all* active subscriptions to pending and demotes the active service subscription. The new session ID is stored.
|
||||
- **Same ID**: no-op (the `unless` guard).
|
||||
|
||||
This is the mechanism by which reconnection invalidates all prior subscriptions. Callers don't need to explicitly move subscriptions — setting the new session ID does it atomically.
|
||||
|
||||
## addActiveSub' — service-associated queue elision
|
||||
|
||||
When `serviceId_` is `Just` and `serviceAssoc` is `True`, the queue is **not** added to `activeSubs`. Instead, `updateActiveService` increments the service subscription's count and XORs the queue's `IdsHash`. The queue is also removed from `pendingSubs`.
|
||||
|
||||
This means service-associated queues have no individual representation in `activeSubs` — they exist only as aggregated count + hash in `activeServiceSub`. The router tracks them via the service subscription; the agent doesn't need per-queue state.
|
||||
|
||||
When `serviceAssoc` is `False` (or no service ID), the queue goes to `activeSubs` normally.
|
||||
|
||||
## updateActiveService — accumulative XOR merge
|
||||
|
||||
`updateActiveService` adds to an existing `ServiceSub` rather than replacing it. It increments the queue count (`n + addN`) and appends the IdsHash (`idsHash <> addIdsHash`). The `<>` on `IdsHash` is XOR — this means the hash is order-independent and can be built incrementally as individual subscription confirmations arrive.
|
||||
|
||||
The guard `serviceId == serviceId'` silently drops updates if the service ID has changed (e.g., credential rotation happened between individual queue confirmations).
|
||||
|
||||
## setSubsPending — mode-dependent redistribution
|
||||
|
||||
`setSubsPending` handles two cases based on whether the transport session mode (entity vs shared) matches the session key shape:
|
||||
|
||||
1. **Mode matches key shape** (`entitySession == isJust connId_`): in-place demotion via `setSubsPending_` — active subs move to pending within the same `SessSubs` entry. Session ID is cleared (`Nothing`).
|
||||
|
||||
2. **Mode mismatch** (e.g., switching from shared session to entity mode): the entire `SessSubs` entry is **deleted** from the map (`TM.lookupDelete`), and all subscriptions are redistributed to new per-entity session keys via `addPendingSub (uId, srv, sessEntId (connId rq))`. This changes the map granularity — one shared entry becomes many entity entries.
|
||||
|
||||
Both paths check `Just sessId == sessId'` first — if the stored session ID doesn't match the one being invalidated, no work is done (returns empty).
|
||||
|
||||
## getSessSubs — lazy initialization
|
||||
|
||||
`getSessSubs` creates a new `SessSubs` entry if none exists for the transport session. This means any write operation (`addPendingSub`, `setSessionId`, etc.) will create map entries as a side effect. Read operations (`hasActiveSub`, `getActiveSubs`) use `lookupSubs` instead, which returns `Nothing`/empty without creating entries.
|
||||
|
||||
## updateClientNotices
|
||||
|
||||
Adjusts the `clientNoticeId` field on pending subscriptions in bulk. Uses `M.adjust`, so missing recipient IDs are silently skipped. Only modifies pending subs — active subs are not touched because they've already been confirmed.
|
||||
Reference in New Issue
Block a user