chickadee » simple-timer

simple-timer

Simple, cancel-able, efficient timers.

Currently only a low level interface is provided.

TBD: Support srfi-120 too.

Rationale

The srfi-120 API incurs just too much overhead for many use cases to settle upon. (The issue: srfi120's task identifiers as returned by timer-schedule! are defined to be readable object, which adds undue overhead for cancellation.)

Every other egg implements it's own idea of timers, which makes for a hell to mix them. This should be(come) a low level enough interface to support most needs while having all timers in one place.

Another issue frequently coming up with CHICKEN is the false deadlock detection when signal handlers are used to unlock the situation. The common work around to load yet another thread looping for some time puts load at the core's timeout queue. Simply using this egg should install one such timer once and for all. (TBD: make sure this works over forks too. Should this cancel timeouts?)

The timers here are assumed mostly timeouts or regular background jobs and hence rarely run. They should not be sensitive to precise timings. (It is the job of this eggs timers to reduce the load on the timeout queue in chickens core and optimize for minimal overhead for timers canceled within less than a timer-period.)

Timeouts fire only if they are not canceled before at least a full timer-period passed. A timer-period defaults to one second. Timers are run in batches rounded to timer-epsilon of it's scheduled time every timer-period.

Requirements

Requires llrb-tree, pigeon-hole.

Notes:

API

timer-period #!rest newprocedure

Without argument queries the current period. With argument chances the period at which timers are fired.

timer-epsilon #!rest newprocedure

Without argument queries the current epsilon. With argument chances the epsilon for grouping timers by due time.

timer-condition? objprocedure

Predicate to test for timer conditions.

make-timer-conditionprocedure

Creates a timer condition.

register-timer-task! time jobprocedure

Registers JOB to be run after TIME has passed. Returns a reference to the task. The reference is opaque by definition - unlike srfi-120 timer-schedule!'s task identifiers. In fact it is a pair.

The job is typically a thunk to be executed. This thunk MUST NOT raise exceptions, MAY NOT block and SHOULD return ASAP. So except for simple, fast operations it should schedule the actual operation, e.g. by starting a fresh thread, and return.

If a thread is given as JOB, it will receive a timer-condition? via thread-signal!.

Undocumented: if a pigeon-hole is given as JOB a timer-condition? is unconditionally queued.

cancel-timer-task! TASKprocedure

Cancels the TASK (must be a reference obtained from register-timer-task!). (In fact it atomically sets the cdr of the reference to #f and returns the old value.)

Returns #f if the TASK was already canceled or fired.

Examples

(handle-exceptions
 ex
 (cond
  ((timer-condition? ex) #t)
  (else ex))
 (register-timer-task! 1 (current-thread))
 (thread-sleep! 3)
 'timer-exceptions-should-have-occurred-before)
=> #t

Cancelation: set! b #t is never invoked.

(or (cancel-timer-task!
      (register-timer-task! 0.03 (lambda () (set! b #t))))
    (error "too late to cancel timer"))

Best practice using thread signals:

(let ((task #f))
  (handle-exceptions
   ex
   (cond
    ((timer-condition? ex)
     ;; clean up after timeout here
     #t)
    (else
     (or (cancel-timer-task! task)
	 (error "too late to cancel, that's bad"))
     ex))
   ...
   (set! task (register-timer-task! 1 (current-thread)))
   (let ((result (do-some-work-within-time-limit)))
     (cancel-timer-task! task)
     ...
     result)))

Best practice using messages (try to avoid needless dynamic-winds in this context):

 (let* ((chan (gochan 0))
        (task (register-timer-task! 1 (lambda () (go (gochan-send chan (make-timer-condition))))) )
        (result (do-some-work-with chan)))
   (cancel-timer-task! task)
   result)

About this egg

Source

Latest version: http://github.com/0-8-15/simple-timer

Version History

0.1: Initial version.

Authors

Jörg F. Wittenberger

License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ASIS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Contents »