mirror of
https://github.com/logos-messaging/nim-sds.git
synced 2026-07-02 22:10:13 +00:00
The 14 Persistence proc fields now return Future[...] with
{.async: (raises: []), gcsafe.}, allowing real I/O backends (SQLite,
encrypted file, network) to suspend rather than block the Chronos event
loop the manager runs on.
Propagates through:
- ReliabilityManager.lock: system.Lock -> chronos.AsyncLock. Acquired
across awaits cleanly; matches the single-threaded Chronos worker the
FFI uses. Multi-OS-thread use is now explicitly the caller's
responsibility.
- sds_utils + sds.nim public API procs (wrapOutgoingMessage,
unwrapReceivedMessage, markDependenciesMet, setCallbacks,
resetReliabilityManager, cleanup, ensureChannel, removeChannel, the
getter snapshots, etc.) are now async.
- FFI request handlers in library/sds_thread/... await the new API.
- Tests converted via an asyncTest template that wraps each test body
in an async proc; setup/teardown use waitFor for their single async
call (ensureChannel / cleanup).
Lock scope is preserved exactly: the same call sites that held the
kernel Lock today hold AsyncLock now -- no new locking added.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
153 lines
6.0 KiB
Nim
153 lines
6.0 KiB
Nim
import std/tables
|
|
import chronos
|
|
import sds
|
|
|
|
## Test-only Persistence backend backed by Nim tables. Lets tests verify the
|
|
## full write → restart → read-back loop without depending on SQLite (or any
|
|
## real storage technology). Exposes the underlying store so tests can assert
|
|
## on what got saved.
|
|
|
|
type InMemoryStore* = ref object
|
|
lamports*: Table[SdsChannelID, int64]
|
|
log*: Table[SdsChannelID, OrderedTable[SdsMessageID, SdsMessage]]
|
|
hints*: Table[SdsMessageID, seq[byte]]
|
|
outgoing*: Table[SdsChannelID, OrderedTable[SdsMessageID, UnacknowledgedMessage]]
|
|
incoming*: Table[SdsChannelID, OrderedTable[SdsMessageID, IncomingMessage]]
|
|
outgoingRepair*: Table[SdsChannelID, OrderedTable[SdsMessageID, OutgoingRepairEntry]]
|
|
incomingRepair*: Table[SdsChannelID, OrderedTable[SdsMessageID, IncomingRepairEntry]]
|
|
dropChannelCalls*: Table[SdsChannelID, int]
|
|
## Per-channel counter; lets tests assert dropChannel is invoked exactly
|
|
## once per logical drop (not N times — see PR #66 review).
|
|
|
|
proc newInMemoryStore*(): InMemoryStore =
|
|
InMemoryStore()
|
|
|
|
proc newInMemoryPersistence*(store: InMemoryStore): Persistence =
|
|
Persistence(
|
|
saveLamport: proc(
|
|
channelId: SdsChannelID, lamport: int64
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
store.lamports[channelId] = lamport,
|
|
|
|
appendLogEntry: proc(
|
|
channelId: SdsChannelID, msg: SdsMessage
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId notin store.log:
|
|
store.log[channelId] = initOrderedTable[SdsMessageID, SdsMessage]()
|
|
store.log[channelId][msg.messageId] = msg,
|
|
|
|
removeLogEntry: proc(
|
|
channelId: SdsChannelID, msgId: SdsMessageID
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId in store.log:
|
|
store.log[channelId].del(msgId),
|
|
|
|
setRetrievalHint: proc(
|
|
msgId: SdsMessageID, hint: seq[byte]
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
store.hints[msgId] = hint,
|
|
|
|
saveOutgoing: proc(
|
|
channelId: SdsChannelID, msg: UnacknowledgedMessage
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId notin store.outgoing:
|
|
store.outgoing[channelId] =
|
|
initOrderedTable[SdsMessageID, UnacknowledgedMessage]()
|
|
store.outgoing[channelId][msg.message.messageId] = msg,
|
|
|
|
removeOutgoing: proc(
|
|
channelId: SdsChannelID, msgId: SdsMessageID
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId in store.outgoing:
|
|
store.outgoing[channelId].del(msgId),
|
|
|
|
saveIncoming: proc(
|
|
channelId: SdsChannelID, msg: IncomingMessage
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId notin store.incoming:
|
|
store.incoming[channelId] =
|
|
initOrderedTable[SdsMessageID, IncomingMessage]()
|
|
store.incoming[channelId][msg.message.messageId] = msg,
|
|
|
|
removeIncoming: proc(
|
|
channelId: SdsChannelID, msgId: SdsMessageID
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId in store.incoming:
|
|
store.incoming[channelId].del(msgId),
|
|
|
|
saveOutgoingRepair: proc(
|
|
channelId: SdsChannelID, msgId: SdsMessageID, entry: OutgoingRepairEntry
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId notin store.outgoingRepair:
|
|
store.outgoingRepair[channelId] =
|
|
initOrderedTable[SdsMessageID, OutgoingRepairEntry]()
|
|
store.outgoingRepair[channelId][msgId] = entry,
|
|
|
|
removeOutgoingRepair: proc(
|
|
channelId: SdsChannelID, msgId: SdsMessageID
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId in store.outgoingRepair:
|
|
store.outgoingRepair[channelId].del(msgId),
|
|
|
|
saveIncomingRepair: proc(
|
|
channelId: SdsChannelID, msgId: SdsMessageID, entry: IncomingRepairEntry
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId notin store.incomingRepair:
|
|
store.incomingRepair[channelId] =
|
|
initOrderedTable[SdsMessageID, IncomingRepairEntry]()
|
|
store.incomingRepair[channelId][msgId] = entry,
|
|
|
|
removeIncomingRepair: proc(
|
|
channelId: SdsChannelID, msgId: SdsMessageID
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
if channelId in store.incomingRepair:
|
|
store.incomingRepair[channelId].del(msgId),
|
|
|
|
dropChannel: proc(
|
|
channelId: SdsChannelID
|
|
): Future[void] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
store.lamports.del(channelId)
|
|
store.log.del(channelId)
|
|
store.outgoing.del(channelId)
|
|
store.incoming.del(channelId)
|
|
store.outgoingRepair.del(channelId)
|
|
store.incomingRepair.del(channelId)
|
|
store.dropChannelCalls[channelId] =
|
|
store.dropChannelCalls.getOrDefault(channelId) + 1,
|
|
|
|
loadAllForChannel: proc(
|
|
channelId: SdsChannelID
|
|
): Future[ChannelSnapshot] {.async: (raises: []), gcsafe.} =
|
|
{.cast(raises: []).}:
|
|
var snap = ChannelSnapshot()
|
|
if channelId in store.lamports:
|
|
snap.lamportTimestamp = store.lamports[channelId]
|
|
if channelId in store.log:
|
|
for msg in store.log[channelId].values:
|
|
snap.messageHistory.add(msg)
|
|
if channelId in store.outgoing:
|
|
for unack in store.outgoing[channelId].values:
|
|
snap.outgoingBuffer.add(unack)
|
|
if channelId in store.incoming:
|
|
for incoming in store.incoming[channelId].values:
|
|
snap.incomingBuffer.add(incoming)
|
|
if channelId in store.outgoingRepair:
|
|
for msgId, entry in store.outgoingRepair[channelId]:
|
|
snap.outgoingRepairBuffer.add((msgId, entry))
|
|
if channelId in store.incomingRepair:
|
|
for msgId, entry in store.incomingRepair[channelId]:
|
|
snap.incomingRepairBuffer.add((msgId, entry))
|
|
return snap,
|
|
)
|