Timers + tests

This commit is contained in:
Raycho Mukelov 2023-08-25 12:00:40 +03:00
parent 833cc236a3
commit 93fa2d632c
5 changed files with 70 additions and 60 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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: