2024-11-29 14:07:24 +04:00
|
|
|
import std/[times, locks]
|
|
|
|
|
import chronos, results
|
|
|
|
|
import ./common
|
|
|
|
|
import ./utils
|
|
|
|
|
import ./protobuf
|
2024-10-21 16:55:07 +05:30
|
|
|
|
|
|
|
|
proc defaultConfig*(): ReliabilityConfig =
|
2024-11-28 07:01:23 +04:00
|
|
|
## Creates a default configuration for the ReliabilityManager.
|
|
|
|
|
##
|
|
|
|
|
## Returns:
|
|
|
|
|
## A ReliabilityConfig object with default values.
|
2024-10-21 16:55:07 +05:30
|
|
|
ReliabilityConfig(
|
|
|
|
|
bloomFilterCapacity: DefaultBloomFilterCapacity,
|
|
|
|
|
bloomFilterErrorRate: DefaultBloomFilterErrorRate,
|
|
|
|
|
bloomFilterWindow: DefaultBloomFilterWindow,
|
|
|
|
|
maxMessageHistory: DefaultMaxMessageHistory,
|
|
|
|
|
maxCausalHistory: DefaultMaxCausalHistory,
|
|
|
|
|
resendInterval: DefaultResendInterval,
|
|
|
|
|
maxResendAttempts: DefaultMaxResendAttempts
|
2024-10-14 15:05:19 +04:00
|
|
|
)
|
|
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc newReliabilityManager*(channelId: string, config: ReliabilityConfig = defaultConfig()): Result[ReliabilityManager, ReliabilityError] =
|
2024-11-28 07:01:23 +04:00
|
|
|
## Creates a new ReliabilityManager with the specified channel ID and configuration.
|
|
|
|
|
##
|
|
|
|
|
## Parameters:
|
|
|
|
|
## - channelId: A unique identifier for the communication channel.
|
|
|
|
|
## - config: Configuration options for the ReliabilityManager. If not provided, default configuration is used.
|
|
|
|
|
##
|
|
|
|
|
## Returns:
|
|
|
|
|
## A Result containing either a new ReliabilityManager instance or an error.
|
2024-10-21 16:55:07 +05:30
|
|
|
if channelId.len == 0:
|
2024-11-29 14:07:24 +04:00
|
|
|
return err(reInvalidArgument)
|
2024-10-21 16:55:07 +05:30
|
|
|
|
|
|
|
|
try:
|
2024-11-29 14:07:24 +04:00
|
|
|
let bloomFilter = newRollingBloomFilter(
|
|
|
|
|
config.bloomFilterCapacity,
|
|
|
|
|
config.bloomFilterErrorRate,
|
|
|
|
|
config.bloomFilterWindow
|
|
|
|
|
)
|
|
|
|
|
|
2024-10-21 16:55:07 +05:30
|
|
|
let rm = ReliabilityManager(
|
|
|
|
|
lamportTimestamp: 0,
|
|
|
|
|
messageHistory: @[],
|
2024-11-29 14:07:24 +04:00
|
|
|
bloomFilter: bloomFilter,
|
2024-10-21 16:55:07 +05:30
|
|
|
outgoingBuffer: @[],
|
|
|
|
|
incomingBuffer: @[],
|
|
|
|
|
channelId: channelId,
|
|
|
|
|
config: config
|
|
|
|
|
)
|
|
|
|
|
initLock(rm.lock)
|
|
|
|
|
return ok(rm)
|
|
|
|
|
except:
|
2024-11-29 14:07:24 +04:00
|
|
|
return err(reOutOfMemory)
|
2024-10-21 16:55:07 +05:30
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc wrapOutgoingMessage*(rm: ReliabilityManager, message: seq[byte], messageId: MessageID): Result[seq[byte], ReliabilityError] =
|
2024-11-28 07:01:23 +04:00
|
|
|
## Wraps an outgoing message with reliability metadata.
|
|
|
|
|
##
|
|
|
|
|
## Parameters:
|
|
|
|
|
## - message: The content of the message to be sent.
|
|
|
|
|
##
|
|
|
|
|
## Returns:
|
|
|
|
|
## A Result containing either a Message object with reliability metadata or an error.
|
2024-10-21 16:55:07 +05:30
|
|
|
if message.len == 0:
|
2024-11-29 14:07:24 +04:00
|
|
|
return err(reInvalidArgument)
|
2024-10-21 16:55:07 +05:30
|
|
|
if message.len > MaxMessageSize:
|
2024-11-29 14:07:24 +04:00
|
|
|
return err(reMessageTooLarge)
|
2024-10-21 16:55:07 +05:30
|
|
|
|
|
|
|
|
withLock rm.lock:
|
|
|
|
|
try:
|
2024-12-09 17:32:50 +04:00
|
|
|
rm.updateLamportTimestamp(getTime().toUnix)
|
2024-10-21 16:55:07 +05:30
|
|
|
let msg = Message(
|
2024-11-29 14:07:24 +04:00
|
|
|
messageId: messageId,
|
2024-10-21 16:55:07 +05:30
|
|
|
lamportTimestamp: rm.lamportTimestamp,
|
|
|
|
|
causalHistory: rm.getRecentMessageIDs(rm.config.maxCausalHistory),
|
|
|
|
|
channelId: rm.channelId,
|
|
|
|
|
content: message
|
|
|
|
|
)
|
|
|
|
|
rm.outgoingBuffer.add(UnacknowledgedMessage(message: msg, sendTime: getTime(), resendAttempts: 0))
|
2024-12-09 17:32:50 +04:00
|
|
|
# rm.messageHistory.add(messageId)
|
|
|
|
|
# rm.bloomFilter.add(messageId)
|
2024-11-29 14:07:24 +04:00
|
|
|
return serializeMessage(msg)
|
2024-10-21 16:55:07 +05:30
|
|
|
except:
|
2024-11-29 14:07:24 +04:00
|
|
|
return err(reInternalError)
|
2024-10-21 16:55:07 +05:30
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc unwrapReceivedMessage*(rm: ReliabilityManager, message: seq[byte]): Result[tuple[message: seq[byte], missingDeps: seq[MessageID]], ReliabilityError] =
|
2024-11-28 07:01:23 +04:00
|
|
|
## Unwraps a received message and processes its reliability metadata.
|
|
|
|
|
##
|
|
|
|
|
## Parameters:
|
|
|
|
|
## - message: The received Message object.
|
|
|
|
|
##
|
|
|
|
|
## Returns:
|
|
|
|
|
## A Result containing either a tuple with the processed message and missing dependencies, or an error.
|
2024-10-21 16:55:07 +05:30
|
|
|
withLock rm.lock:
|
|
|
|
|
try:
|
2024-11-29 14:07:24 +04:00
|
|
|
let msgResult = deserializeMessage(message)
|
|
|
|
|
if not msgResult.isOk:
|
|
|
|
|
return err(msgResult.error)
|
|
|
|
|
|
|
|
|
|
let msg = msgResult.get
|
|
|
|
|
if rm.bloomFilter.contains(msg.messageId):
|
|
|
|
|
return ok((msg.content, @[]))
|
2024-10-21 16:55:07 +05:30
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
rm.bloomFilter.add(msg.messageId)
|
|
|
|
|
rm.updateLamportTimestamp(msg.lamportTimestamp)
|
2024-10-21 16:55:07 +05:30
|
|
|
|
|
|
|
|
var missingDeps: seq[MessageID] = @[]
|
2024-11-29 14:07:24 +04:00
|
|
|
for depId in msg.causalHistory:
|
2024-10-21 16:55:07 +05:30
|
|
|
if not rm.bloomFilter.contains(depId):
|
|
|
|
|
missingDeps.add(depId)
|
|
|
|
|
|
|
|
|
|
if missingDeps.len == 0:
|
2024-11-29 14:07:24 +04:00
|
|
|
rm.messageHistory.add(msg.messageId)
|
2024-10-21 16:55:07 +05:30
|
|
|
if rm.messageHistory.len > rm.config.maxMessageHistory:
|
|
|
|
|
rm.messageHistory.delete(0)
|
|
|
|
|
if rm.onMessageReady != nil:
|
2024-11-29 14:07:24 +04:00
|
|
|
rm.onMessageReady(msg.messageId)
|
2024-10-21 16:55:07 +05:30
|
|
|
else:
|
2024-11-29 14:07:24 +04:00
|
|
|
rm.incomingBuffer.add(msg)
|
2024-10-21 16:55:07 +05:30
|
|
|
if rm.onMissingDependencies != nil:
|
2024-11-29 14:07:24 +04:00
|
|
|
rm.onMissingDependencies(msg.messageId, missingDeps)
|
2024-10-21 16:55:07 +05:30
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
return ok((msg.content, missingDeps))
|
2024-10-21 16:55:07 +05:30
|
|
|
except:
|
2024-11-29 14:07:24 +04:00
|
|
|
return err(reInternalError)
|
2024-10-21 16:55:07 +05:30
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc markDependenciesMet*(rm: ReliabilityManager, messageIds: seq[MessageID]): Result[void, ReliabilityError] =
|
2024-11-28 07:01:23 +04:00
|
|
|
## Marks the specified message dependencies as met.
|
|
|
|
|
##
|
|
|
|
|
## Parameters:
|
|
|
|
|
## - messageIds: A sequence of message IDs to mark as met.
|
|
|
|
|
##
|
|
|
|
|
## Returns:
|
|
|
|
|
## A Result indicating success or an error.
|
2024-10-21 16:55:07 +05:30
|
|
|
withLock rm.lock:
|
|
|
|
|
try:
|
|
|
|
|
var processedMessages: seq[Message] = @[]
|
2024-11-29 14:07:24 +04:00
|
|
|
var newIncomingBuffer: seq[Message] = @[]
|
|
|
|
|
|
|
|
|
|
for msg in rm.incomingBuffer:
|
|
|
|
|
var allDependenciesMet = true
|
|
|
|
|
for depId in msg.causalHistory:
|
|
|
|
|
if depId notin messageIds and not rm.bloomFilter.contains(depId):
|
|
|
|
|
allDependenciesMet = false
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if allDependenciesMet:
|
|
|
|
|
processedMessages.add(msg)
|
|
|
|
|
else:
|
|
|
|
|
newIncomingBuffer.add(msg)
|
|
|
|
|
|
|
|
|
|
rm.incomingBuffer = newIncomingBuffer
|
2024-10-21 16:55:07 +05:30
|
|
|
|
|
|
|
|
for msg in processedMessages:
|
|
|
|
|
rm.messageHistory.add(msg.messageId)
|
|
|
|
|
if rm.messageHistory.len > rm.config.maxMessageHistory:
|
|
|
|
|
rm.messageHistory.delete(0)
|
|
|
|
|
if rm.onMessageReady != nil:
|
|
|
|
|
rm.onMessageReady(msg.messageId)
|
|
|
|
|
|
|
|
|
|
return ok()
|
|
|
|
|
except:
|
2024-11-29 14:07:24 +04:00
|
|
|
return err(reInternalError)
|
2024-10-14 15:05:19 +04:00
|
|
|
|
|
|
|
|
proc setCallbacks*(rm: ReliabilityManager,
|
2024-11-29 14:07:24 +04:00
|
|
|
onMessageReady: proc(messageId: MessageID) {.gcsafe.},
|
|
|
|
|
onMessageSent: proc(messageId: MessageID) {.gcsafe.},
|
|
|
|
|
onMissingDependencies: proc(messageId: MessageID, missingDeps: seq[MessageID]) {.gcsafe.},
|
|
|
|
|
onPeriodicSync: PeriodicSyncCallback = nil) =
|
2024-11-28 07:01:23 +04:00
|
|
|
## Sets the callback functions for various events in the ReliabilityManager.
|
|
|
|
|
##
|
|
|
|
|
## Parameters:
|
|
|
|
|
## - onMessageReady: Callback function called when a message is ready to be processed.
|
|
|
|
|
## - onMessageSent: Callback function called when a message is confirmed as sent.
|
|
|
|
|
## - onMissingDependencies: Callback function called when a message has missing dependencies.
|
2024-11-29 14:07:24 +04:00
|
|
|
## - onPeriodicSync: Callback function called to notify about periodic sync
|
2024-10-21 16:55:07 +05:30
|
|
|
withLock rm.lock:
|
|
|
|
|
rm.onMessageReady = onMessageReady
|
|
|
|
|
rm.onMessageSent = onMessageSent
|
|
|
|
|
rm.onMissingDependencies = onMissingDependencies
|
2024-11-29 14:07:24 +04:00
|
|
|
rm.onPeriodicSync = onPeriodicSync
|
2024-10-14 15:05:19 +04:00
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc checkUnacknowledgedMessages*(rm: ReliabilityManager) {.raises: [].} =
|
2024-11-28 07:01:23 +04:00
|
|
|
## Checks and processes unacknowledged messages in the outgoing buffer.
|
|
|
|
|
withLock rm.lock:
|
|
|
|
|
let now = getTime()
|
|
|
|
|
var newOutgoingBuffer: seq[UnacknowledgedMessage] = @[]
|
2024-11-29 14:07:24 +04:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
for msg in rm.outgoingBuffer:
|
|
|
|
|
if (now - msg.sendTime) < rm.config.resendInterval:
|
|
|
|
|
newOutgoingBuffer.add(msg)
|
|
|
|
|
elif msg.resendAttempts < rm.config.maxResendAttempts:
|
|
|
|
|
var updatedMsg = msg
|
|
|
|
|
updatedMsg.resendAttempts += 1
|
|
|
|
|
updatedMsg.sendTime = now
|
|
|
|
|
newOutgoingBuffer.add(updatedMsg)
|
|
|
|
|
elif rm.onMessageSent != nil:
|
|
|
|
|
rm.onMessageSent(msg.message.messageId)
|
|
|
|
|
|
|
|
|
|
rm.outgoingBuffer = newOutgoingBuffer
|
|
|
|
|
except:
|
|
|
|
|
discard
|
2024-11-28 07:01:23 +04:00
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc periodicBufferSweep(rm: ReliabilityManager) {.async: (raises: [CancelledError]).} =
|
|
|
|
|
## Periodically sweeps the buffer to clean up and check unacknowledged messages.
|
2024-11-28 07:01:23 +04:00
|
|
|
##
|
|
|
|
|
## This is an internal function and should not be called directly.
|
|
|
|
|
while true:
|
2024-11-29 14:07:24 +04:00
|
|
|
{.gcsafe.}:
|
|
|
|
|
try:
|
|
|
|
|
rm.checkUnacknowledgedMessages()
|
|
|
|
|
rm.cleanBloomFilter()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logError("Error in periodic buffer sweep: " & e.msg)
|
|
|
|
|
await sleepAsync(chronos.seconds(5))
|
2024-11-28 07:01:23 +04:00
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc periodicSyncMessage(rm: ReliabilityManager) {.async: (raises: [CancelledError]).} =
|
|
|
|
|
## Periodically notifies to send a sync message to maintain connectivity.
|
2024-11-28 07:01:23 +04:00
|
|
|
while true:
|
2024-11-29 14:07:24 +04:00
|
|
|
{.gcsafe.}:
|
|
|
|
|
try:
|
|
|
|
|
if rm.onPeriodicSync != nil:
|
|
|
|
|
rm.onPeriodicSync()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logError("Error in periodic sync: " & e.msg)
|
|
|
|
|
await sleepAsync(chronos.seconds(30))
|
2024-11-28 07:01:23 +04:00
|
|
|
|
|
|
|
|
proc startPeriodicTasks*(rm: ReliabilityManager) =
|
|
|
|
|
## Starts the periodic tasks for buffer sweeping and sync message sending.
|
|
|
|
|
##
|
|
|
|
|
## This procedure should be called after creating a ReliabilityManager to enable automatic maintenance.
|
2024-11-29 14:07:24 +04:00
|
|
|
asyncSpawn rm.periodicBufferSweep()
|
|
|
|
|
asyncSpawn rm.periodicSyncMessage()
|
2024-11-28 07:01:23 +04:00
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc resetReliabilityManager*(rm: ReliabilityManager): Result[void, ReliabilityError] =
|
2024-11-28 07:01:23 +04:00
|
|
|
## Resets the ReliabilityManager to its initial state.
|
|
|
|
|
##
|
|
|
|
|
## This procedure clears all buffers and resets the Lamport timestamp.
|
|
|
|
|
##
|
|
|
|
|
## Returns:
|
|
|
|
|
## A Result indicating success or an error if the Bloom filter initialization fails.
|
|
|
|
|
withLock rm.lock:
|
2024-11-29 14:07:24 +04:00
|
|
|
try:
|
|
|
|
|
rm.lamportTimestamp = 0
|
|
|
|
|
rm.messageHistory.setLen(0)
|
|
|
|
|
rm.outgoingBuffer.setLen(0)
|
|
|
|
|
rm.incomingBuffer.setLen(0)
|
|
|
|
|
rm.bloomFilter = newRollingBloomFilter(
|
|
|
|
|
rm.config.bloomFilterCapacity,
|
|
|
|
|
rm.config.bloomFilterErrorRate,
|
|
|
|
|
rm.config.bloomFilterWindow
|
|
|
|
|
)
|
|
|
|
|
return ok()
|
|
|
|
|
except:
|
|
|
|
|
return err(reInternalError)
|
2024-10-14 15:05:19 +04:00
|
|
|
|
2024-11-29 14:07:24 +04:00
|
|
|
proc cleanup*(rm: ReliabilityManager) {.raises: [].} =
|
|
|
|
|
if not rm.isNil:
|
|
|
|
|
{.gcsafe.}:
|
|
|
|
|
try:
|
|
|
|
|
rm.outgoingBuffer.setLen(0)
|
|
|
|
|
rm.incomingBuffer.setLen(0)
|
|
|
|
|
rm.messageHistory.setLen(0)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logError("Error during cleanup: " & e.msg)
|