2.6 KiB
Simplex.Messaging.Server.StoreLog
Append-only log for queue state changes: write, read/replay, compaction, crash recovery, backup retention.
Source: StoreLog.hs
writeStoreLogRecord — atomicity via manual write
See comment in writeStoreLogRecord. hPutStrLn breaks writes larger than 1024 bytes into multiple system calls on LineBuffered handles, which could interleave with concurrent writes. The solution is manual B.hPut (single call for the complete record + newline) plus hFlush. E.uninterruptibleMask_ prevents async exceptions between write and flush — ensures a complete record is always written.
readWriteStoreLog — crash recovery state machine
The .start temp backup file provides crash recovery during compaction. The sequence:
- Read existing log, replay into memory
- Rename log to
.start(atomic rename = backup point) - Write compacted state to new file
- Rename
.startto timestamped backup, remove old backups
If the router crashes during step 3, the next startup detects .start and restores from it instead of the incomplete new file. Any partially-written current file is preserved as .bak. The comment says "do not terminate" during compaction — there is no safe interrupt point between steps 2 and 4.
removeStoreLogBackups — layered retention policy
Backup retention is layered: (1) keep all backups newer than 24 hours, (2) of the rest, keep at least 3, (3) of those eligible for deletion, only delete backups older than 21 days. This means a router with infrequent restarts accumulates many backups (only cleaned on startup), while a frequently-restarting router keeps a rolling window. Backup timestamps come from ISO 8601 suffixes parsed from filenames.
QueueRec StrEncoding — backward-compatible parsing
The strP parser handles two field name generations: old format sndSecure= (boolean, mapping True → QMMessaging, False → QMContact) and new format queue_mode=. Missing queue mode defaults to Nothing with the comment "unknown queue mode, we cannot imply that it is contact address." EntityActive status is implicit — not written to the log, and parsed as default when status= is absent.
openReadStoreLog — creates file if missing
openReadStoreLog creates an empty file if it doesn't exist. Callers never need to handle "file not found."
foldLogLines — EOF flag for batching
The action callback receives a Bool indicating whether the current line is the last one. This allows consumers (like readQueueStore) to batch operations and flush only on the final line.