diff --git a/raft.nimble b/raft.nimble index c81c8ce..922f379 100644 --- a/raft.nimble +++ b/raft.nimble @@ -19,6 +19,6 @@ skipDirs = @["tests"] requires "nim >= 1.6.0" requires "stew >= 0.1.0" requires "unittest2 >= 0.0.4" -requires "uuid4 >= 0.9.3" +requires "uuids >= 0.1.11" # Helper functions \ No newline at end of file diff --git a/raft/raft_api.nim b/raft/raft_api.nim index a69355f..d2cba46 100644 --- a/raft/raft_api.nim +++ b/raft/raft_api.nim @@ -73,9 +73,9 @@ proc RaftNodeSmApply[SmCommandType, SmStateType](stateMachine: RaftNodeStateMach RaftSmApply(stateMachine, command) # Private Abstract Timer manipulation Ops -template RaftTimerCreate(timerInterval: int, repeat: bool, timerCallback: RaftTimerCallback): RaftTimer = +template RaftTimerCreate(timerInterval: int, oneshot: bool, timerCallback: RaftTimerCallback): RaftTimer = mixin RaftTimerCreateCustomImpl - RaftTimerCreateCustomImpl(timerInterval, repeat, timerCallback) + RaftTimerCreateCustomImpl(timerInterval, oneshot, timerCallback) template RaftTimerCancel(timer: RaftTimer) = mixin RaftTimerCancelCustomImpl diff --git a/raft/types.nim b/raft/types.nim index 8a360b7..1c7e179 100644 --- a/raft/types.nim +++ b/raft/types.nim @@ -12,6 +12,7 @@ import std/locks import options import stew/results +# import uuids/ export results, options @@ -23,9 +24,10 @@ type rnsCandidate = 2 rnsLeader = 3 - RaftNodeId* = uint64 # uuid4 uniquely identifying every Raft Node - RaftNodeTerm* = uint64 # Raft Node Term Type - RaftLogIndex* = uint64 # Raft Node Log Index Type + UUID = object + RaftNodeId* = UUID # uuid4 uniquely identifying every Raft Node + RaftNodeTerm* = uint64 # Raft Node Term Type + RaftLogIndex* = uint64 # Raft Node Log Index Type RaftNodePeer* = object # Raft Node Peer object id*: RaftNodeId @@ -63,7 +65,7 @@ type raftNodeAccessCallback: RaftNodeAccessCallback[SmCommandType, SmStateType] # Callback for sending messages out of this Raft Node - RaftMessageId* = uint64 # UUID assigned to every Raft Node Message, + RaftMessageId* = UUID # UUID assigned to every Raft Node Message, # so it can be matched with it's corresponding response etc. RaftMessageBase* = ref object of RootObj # Base Type for Raft Protocol Messages @@ -97,14 +99,14 @@ type logData*: seq[RaftNodeLogEntry[SmCommandType]] # Raft Node Log Data # Timer types - RaftTimer* = object + RaftTimer* = ref object mtx*: Lock canceled*: bool expired*: bool timeout*: int - repeat*: bool + oneshot*: bool - RaftTimerCallback* = proc (timer: var RaftTimer) {.nimcall, gcsafe.} # Pass any function wrapped in a closure + RaftTimerCallback* = proc (timer: RaftTimer) {.nimcall, gcsafe.} # Pass any function wrapped in a closure # Raft Node Object type RaftNode*[SmCommandType, SmStateType] = object diff --git a/tests/basic_timers.nim b/tests/basic_timers.nim index df90026..1bd4cbe 100644 --- a/tests/basic_timers.nim +++ b/tests/basic_timers.nim @@ -18,52 +18,55 @@ var runningMtx: Lock running: bool -proc RaftTimerCreateCustomImpl*(timerInterval: int, repeat: bool, timerCallback: RaftTimerCallback): RaftTimer = +proc RaftTimerCreateCustomImpl*(timerInterval: int, oneshot: bool, timerCallback: RaftTimerCallback): RaftTimer {.nimcall, gcsafe.} = var - timer = RaftTimer(canceled: false, expired: false, timeout: timerInterval, repeat: repeat) + timer = RaftTimer(mtx: Lock(), canceled: false, expired: false, timeout: timerInterval, oneshot: oneshot) + initLock(timer.mtx) - proc CallbackClosureProc(): Callback = - result = proc (fd: AsyncFD): bool {.closure, gcsafe.} = - withLock(timer.mtx): - if not timer.canceled: - timerCallback(timer) - if not timer.repeat: - timer.expired = true + addTimer(timer.timeout, timer.oneshot, proc (fd: AsyncFD): bool {.closure, gcsafe.} = + withLock(timer.mtx): + if not timer.canceled: + timerCallback(timer) + if timer.oneshot: + timer.expired = true return true else: return false - - debugEcho repr(CallbackClosureProc()) - addTimer(timer.timeout, timer.repeat, CallbackClosureProc()) + else: + return true + ) timer -proc RaftTimerCancelCustomImpl*(timer: var RaftTimer): bool {.discardable.} = - withLock(timer.mtx): - if not timer.expired and not timer.canceled: - timer.canceled = true - return true - else: - return false +proc RaftTimerCancelCustomImpl*(timer: RaftTimer): bool {.nimcall, gcsafe, discardable.} = + withLock(timer.mtx): + if not timer.expired and not timer.canceled: + timer.canceled = true + else: + return false proc RaftTimerPollThread() {.thread, nimcall, gcsafe.} = while running: try: poll() except ValueError as e: - debugEcho e.msg + # debugEcho e.msg + # Add a 'dummy' timer if no other handles are present to prevent more + # ValueError exceptions this is a workaround for a asyncdyspatch bug + # see - https://github.com/nim-lang/Nim/issues/14564 + addTimer(1, false, proc (fd: AsyncFD): bool {.closure, gcsafe.} = false) -proc RaftTimerJoinPollThread*() = +proc RaftTimerJoinPollThread*() {.nimcall, gcsafe.} = joinThread(pollThr) -proc RaftTimerStartCustomImpl*(joinThread: bool = true) = +proc RaftTimerStartCustomImpl*(joinThread: bool = true) {.nimcall, gcsafe.} = withLock(runningMtx): running = true createThread(pollThr, RaftTimerPollThread) if joinThread: RaftTimerJoinPollThread() -proc RaftTimerStopCustomImpl*(joinThread: bool = true) = +proc RaftTimerStopCustomImpl*(joinThread: bool = true) {.nimcall, gcsafe.} = withLock(runningMtx): running = false if joinThread: diff --git a/tests/basic_timers_test.nim b/tests/basic_timers_test.nim index d66d4ca..1c29bac 100644 --- a/tests/basic_timers_test.nim +++ b/tests/basic_timers_test.nim @@ -11,46 +11,51 @@ import unittest2 import ../raft/types import std/locks import basic_timers +import random -var - cancelCond: Cond - cancelLock: Lock - -initLock(cancelLock) -initCond(cancelCond) +const + MAX_TIMERS = 50 + SLOW_TIMERS_MIN = 300 + SLOW_TIMERS_MAX = 350 + FAST_TIMERS_MIN = 20 + FAST_TIMERS_MAX = 150 + WAIT_FOR_SLOW_TIMERS = 225 + FINAL_WAIT = 125 proc timersRunner() = - const - MAX_TIMERS = 50 var slowTimers: array[0..MAX_TIMERS, RaftTimer] fastTimers: array[0..MAX_TIMERS, RaftTimer] - cancelTimer: RaftTimer - proc CancelTimerCallbackClosure( - slowTimers: var array[0..MAX_TIMERS, RaftTimer], - fastTimers: var array[0..MAX_TIMERS, RaftTimer] - ): RaftTimerCallback = - result = proc (timer: var RaftTimer) {.nimcall, gcsafe.} = - debugEcho "Aahjsbdghajsdhjgshgjd" - signal(cancelCond) + var + RaftDummyTimerCallback = proc (timer: RaftTimer) {.nimcall, gcsafe.} = + discard suite "Create and test basic timers": - test "Create 50 slow timers (100-150 ms)": - check true - test "Create 50 fast timers (20-50 ms)": - check true - test "Create cancel timer": - check true test "Start timers": - cancelTimer = RaftTimerCreateCustomImpl(250, false, CancelTimerCallbackClosure(slowTimers, fastTimers)) - RaftTimerStartCustomImpl(joinThread=false) - debugEcho repr(cancelTimer) + RaftTimerStartCustomImpl(false) check true - test "Wait cancel timer 250 ms and stop timers": - wait(cancelCond, cancelLock) + test "Create 'slow' timers": + for i in 0..MAX_TIMERS: + slowTimers[i] = RaftTimerCreateCustomImpl(max(SLOW_TIMERS_MIN, rand(SLOW_TIMERS_MAX)), true, RaftDummyTimerCallback) + check true + test "Create 'fast' timers": + for i in 0..MAX_TIMERS: + fastTimers[i] = RaftTimerCreateCustomImpl(max(FAST_TIMERS_MIN, rand(FAST_TIMERS_MAX)), true, RaftDummyTimerCallback) + check true + test "Wait for and cancel 'slow' timers": + waitFor sleepAsync(WAIT_FOR_SLOW_TIMERS) + for i in 0..MAX_TIMERS: + RaftTimerCancelCustomImpl(slowTimers[i]) + check true + test "Wait and stop timers": + waitFor sleepAsync(FINAL_WAIT) + RaftTimerStopCustomImpl(true) check true test "Check timers consistency": + for i in 0..MAX_TIMERS: + if not fastTimers[i].expired or not slowTimers[i].canceled: + check false check true if isMainModule: