minimart-2014/minimart/drivers/timer.rkt

88 lines
2.6 KiB
Racket

#lang racket/base
;; Timer driver.
;; Uses mutable state internally, but because the scope of the
;; mutation is limited to each timer process alone, it's easy to show
;; correct linear use of the various pointers.
(require racket/set)
(require racket/match)
(require data/heap)
(require "../main.rkt")
(struct pending-timer (deadline label) #:transparent)
(provide (struct-out set-timer)
(struct-out timer-expired)
spawn-timer-driver)
(struct set-timer (label msecs kind) #:prefab)
(struct timer-expired (label msecs) #:prefab)
(define (spawn-timer-driver)
(actor #:name timer-driver
#:state [count 0]
(define control-ch (make-channel))
(thread (lambda () (timer-driver-thread-main control-ch)))
(subscribe ($ expiry (timer-expired ? ?))
#:meta-level 1
#:when (positive? count)
(send expiry)
#:update [count (- count 1)]
#:update-routes) ;; TODO: only update-routes when count is zero
(subscribe ($ instruction (set-timer ? ? ?))
(channel-put control-ch instruction)
#:update [count (+ count 1)]
#:update-routes))) ;; TODO: only update-routes when count was zero
(define (timer-driver-thread-main control-ch)
(define heap (make-timer-heap))
(let loop ()
(sync (match (next-timer heap)
[#f never-evt]
[t (handle-evt (timer-evt (pending-timer-deadline t))
(lambda (now)
(for-each send-ground-message (fire-timers! heap now))
(loop)))])
(handle-evt control-ch
(match-lambda
[(set-timer label msecs 'relative)
(install-timer! heap label (+ (current-inexact-milliseconds) msecs))
(loop)]
[(set-timer label msecs 'absolute)
(install-timer! heap label msecs)
(loop)]
['quit (void)])))))
(define (make-timer-heap)
(make-heap (lambda (t1 t2) (<= (pending-timer-deadline t1) (pending-timer-deadline t2)))))
(define (next-timer heap)
(and (positive? (heap-count heap))
(heap-min heap)))
(define (fire-timers! heap now)
(if (zero? (heap-count heap))
'()
(let ((m (heap-min heap)))
(if (<= (pending-timer-deadline m) now)
(begin (heap-remove-min! heap)
(cons (timer-expired (pending-timer-label m) now)
(fire-timers! heap now)))
'()))))
(define (install-timer! heap label deadline)
(define now (current-inexact-milliseconds))
(heap-add! heap (pending-timer deadline label)))
;; Racket's alarm-evt is almost the right design for timeouts: its
;; synchronisation value should be the (or some) value of the clock
;; after the asked-for time. That way it serves as timeout and
;; clock-reader in one.
(define (timer-evt msecs)
(handle-evt (alarm-evt msecs)
(lambda (_) (current-inexact-milliseconds))))