simple timerpool implementation for uncritical event-purposes. The "tick" is an abstract value and depends on the selected timebase and the environment
Its useful if you need wakeup-timers for protocol implementations or you like to calculate/interpolate something for a given timeslot
For each TimerPool object only one tickthread is spawned which handles the message-queue and the lifecycle of the TimerHandle. The maximum amount of timers is only constrained by memory and the given timebase.
The allocation of a new TimerHandle always block but is threadsafe. The maximum blocking-time relates directly to your given timebase of the pool
There is a blocking and a nonblocking API on the TimerHandles which can be used simulataneously from different threads at once. All actions on the TimerHandles are completely threadsafe and the ptrs itself can be shared between threads.
the following example demonstrates the basic use. For detailed api use and for multithreading examples use the tests as a starter.
import timerpool let tpRef = timerpool.newTimerPool(10.int) # timerpool with 10ms timebase timerhdl = allocTimer(tpRef) timerhdl.setAlarmCounter(5) # set expiration to 50ms (timebase * 5) while timerhdl.getAlarmCounter() > 0: # you can poll it discard timerhdl.waitForAlarm() # or sleep till timer expired timerhdl.deallocTimer() # pushes the timer back to pool tpRef.shutdownTimerPool() # shutdown the pool and blocks till all # timers are expired
Types
TimerHandlePtr = ptr TimerHandle
- pointer type to the timerpoolhandle.
TPError = object of Exception
- generic exception
TimerPoolPtr = ptr TimerPool
- used to share among threads
TimerPoolRef = ref TimerPool
Tickval = range[1 .. int.high]
MinTimerval = range[1 .. int.high]
- integer type used to initialise the timerpool and to set the timeout of the timer
PoolStats = tuple[runningCount: int, freedCount: int, inactiveCount: int]
- container type returned by waitForGetStats. the sum of runningCount,freedCount and inactiveCount is the total amount of timerhandles within the pool
Procs
proc initThreadContext(tpptr: TimerPoolPtr): void {...}{.raises: [TPError], tags: [].}
-
to be called explicit if the pool-accessing thread is not the owner of the timerpool (initialises threadvar globs)
raises a TPError if called within the spawning thread
proc newTimerPool(tbase_ms: Tickval = 100; minFreedTimers: MinTimerval = 5): ref TimerPool {...}{. raises: [], tags: [].}
- creator proc. The tickval is of milliseconds and the default timebase is 100 milliseconds the default of the mintimers parameter is 5 (shrink_pool leave this minimum amount of freed timers within the pool)
proc deinitThreadContext(tpptr: TimerPoolPtr): void {...}{.raises: [TPError], tags: [].}
-
call this proc if the pool-accessing thread should be detached from the timerpool (cleanup threadvar globs)
call this proc only if the current thread is not owner of the timerpool. if not a TPError is raised
proc allocTimer(tpptr: TimerPoolPtr): TimerHandlePtr {...}{.raises: [TPError], tags: [].}
-
returns a timerhandle. the timer is always of type:oneshot but could also act as a continous one. in this case the caller needs to reset the alarm to the needed value. This threadsafe call blocks till the request was handled by the pool-tick-thread
before calling (if the pool was not spawned by the calling thread) initThreadContext() should be called
raises TPError if the pointer parameter is nil and/or the threadContext was not initialised with initThreadContext
proc allocTimer(tpptr: TimerPoolRef): TimerHandlePtr {...}{.inline, raises: [TPError], tags: [].}
proc deallocTimer(timerhdl: TimerHandlePtr): void {...}{.raises: [TPError], tags: [].}
-
the timer handle is pushed back to the pool. once freed it is not handled by the timerscan any more and its recycled for later use
this proc could be called from multiple threads simultaneously. if one ore more threads are waiting on the timers signal all threads gets informed. This call is part of the nonblocking api
raises TPError if the pointer parameter is nil
proc setAlarmCounter(timerhdl: TimerHandlePtr; value: Tickval): void {...}{. raises: [TPError], tags: [].}
-
sets the timers countdown alarm-value to the given one. reset the counter after it´s fired to obtain a continous timer
this call is threadsafe and part of the nonblocking-api
raises TPError if the pointer parameter is nil or the timer is freed
proc getAlarmCounter(timerhdl: TimerHandlePtr): int {...}{.raises: [TPError], tags: [].}
-
returns the current value of the alarmcounter could be used for a polling-style-waiting_for_timer_fired
this call is threadsafe and part of the nonblocking-api
raises TPError if the pointer parameter is nil or the timer already freed
proc waitForAlarm(timerhdl: TimerHandlePtr): void {...}{.raises: [TPError], tags: [].}
-
blocking wait till the alarmcounter is decremented to 0
threadsafe impl and could be called by multiple threads simultaniously
raises TPError if the pointer parameter is nil or the timer already freed
proc waitForGetStats(tpptr: TimerPoolPtr): PoolStats {...}{.raises: [TPError], tags: [].}
-
fetches some pool statistics for debugging purposes
raises TPError if the pointer parameter is nil or the threadContext was not initialized with initThreadContext
proc shrinkTimerPool(tpptr: TimerPoolPtr) {...}{.raises: [TPError], tags: [].}
-
shrinks the pool of freed Timers. the given minFreedTimers value at pool construction specifies the lower watermark
this is a nonblocking call. raises TPError if the pointer parameter is nil and/or the threadContext was not initialised with initThreadContext (only needed if the pool was not spawned by the caller)
Templates
template checkForNil(timerhdl: TimerHandlePtr; callingProc: string = ""): void
- checks if the timerhdl is nil. if so a TPError is raised
template poolRef2Ptr(stpp: TimerPoolRef): TimerPoolPtr
- convenience template to get the TimerPoolPtr from the ref