Files
Evgeny @ SimpleX Chat 260ffb1a9d SMP router specs
2026-03-12 11:29:18 +00:00

2.4 KiB

Simplex.Messaging.Server.MsgStore.STM

In-memory STM message store: TQueue-based message queues with quota enforcement.

Source: STM.hs

withQueueLock is identity

withQueueLock _ _ = id — STM queues need no locking since STM provides atomicity. Journal.hs overrides this with a TMVar-based in-memory lock (via withLockWaitShared). Any code calling withQueueLock transparently gets the right concurrency control for the backend.

writeMsg — quota with empty-queue override

When canWrite is False (over quota) but the queue is empty, writing is still allowed. This handles the case where all messages were deleted or expired but the canWrite flag was not reset. When the quota is exceeded, the actual message content is replaced with a MessageQuota (preserving only msgId and msgTs) — the client receives a quota notification instead of the message.

getMsgQueue — lazy initialization

The message queue TVar (msgQueue') starts as Nothing. The queue is created on first getMsgQueue call (lazy initialization). This means queues that are created but never receive messages don't allocate a TQueue. getPeekMsgQueue returns Nothing if no message queue exists — callers handle this as "queue is empty."

deleteQueue_ — atomic swap prevents post-delete operations

swapTVar (msgQueue' q) Nothing atomically retrieves the old message queue and sets to Nothing. Any subsequent getMsgQueue call would create a fresh empty queue, but the deleted queue's queueRec TVar is also set to Nothing by deleteStoreQueue, so all operations would fail with AUTH first.

tryDeleteMsg_ — blind dequeue, no msgId check

tryDeleteMsg_ does tryReadTQueue — removes whatever is at the head without verifying the message ID. The msgId check lives in the default tryDelMsg / tryDelPeekMsg implementations in Types.hs, which always call tryPeekMsg_ first to verify. Calling tryDeleteMsg_ directly would silently delete the wrong message if the head changed between peek and delete. Safe only because isolateQueue serializes all operations on the same queue.

getQueueMessages_ snapshot — invisible gap

getQueueMessages_ False implements non-destructive read by flushing TQueue then writing back. This runs inside atomically (via isolateQueue), so the temporarily-empty state is never visible to other transactions.