;;; mcl-timers contributed by Gary Byers (in-package "CCL") ;;; A simple timer mechanism for MCL/OpenMCL, which uses a ;;; PERIODIC-TASK to check for expired "timer requests". ;;; In MCL and OpenMCL, PERIODIC-TASKS run at specified ;;; intervals via the same preemption mechanism that the ;;; scheduler uses; they run in the execution context of ;;; whatever thread was preempted, and they're assumed to ;;; run pretty quickly. ;;; This code uses doubly-linked-list elements (DLL-NODEs) ;;; to represent a sorted list of "timer requests"; client ;;; processes use timer requests to schedule an interrupt ;;; action at a specified time. A periodic task walks this ;;; list once a second (by default), removing those requests ;;; whose time isn't in the future and interrupting the ;;; corresponding processes. ;;; The number of timer interrupts (ticks) per second. (defmacro ticks-per-second () #+OpenMCL '*ticks-per-second* #-OpenMCL 60) (defun expiration-tick-count (seconds) (+ (round (* seconds (ticks-per-second))) (get-tick-count))) (defstruct (timer-request (:include dll-node) (:constructor %make-timer-request)) expiration-tick ; when the timer expires process ; what process to interrupt function) ; how to interrupt it (defun make-timer-request (seconds-from-now function) (check-type seconds-from-now (and unsigned-byte fixnum)) (check-type function function) (%make-timer-request :expiration-tick (expiration-tick-count seconds-from-now) :process *current-process* :function function)) ;;; the CCL::DEFLOADVAR construct ensures that the variable ;;; will be reinitialized when a saved image is restarted (defloadvar *timer-request-queue* #-openmcl-native-threads (make-dll-header) #+openmcl-native-threads (make-locked-dll-header)) ;;; Insert the timer request before the first element with a later ;;; expiration time (or at the end of the queue if there's no such ;;; element.) (defun enqueue-timer-request (r) (#-openmcl-native-threads without-interrupts #+openmcl-native-threads with-locked-dll-header #+openmcl-native-threads (*timer-request-queue*) (if (dll-node-succ r) ; Already enqueued. r ; Or signal an error. (let* ((r-date (timer-request-expiration-tick r))) (do* ((node *timer-request-queue* next) (next (dll-node-succ node) (dll-node-succ next))) ((or (eq next *timer-request-queue*) (> (timer-request-expiration-tick next) r-date)) (insert-dll-node-after r node))))))) ;;; Remove a timer request. (It's a no-op if the request has already ;;; been removed.) (defun dequeue-timer-request (r) (#-openmcl-native-threads without-interrupts #+openmcl-native-threads with-locked-dll-header #+openmcl-native-threads (*timer-request-queue*) (when (dll-node-succ r) ;enqueued (remove-dll-node r)) r)) ;;; Since this runs in an arbitrary process, it tries to be a little ;;; careful with requests made by the current process (since running ;;; the interrupt function will probably transfer control out of the ;;; periodic task function.) The oldest (hopefully only) request for ;;; the current process is handled after all other pending requests. (defun process-timer-requests () (let* ((now (get-tick-count)) (current-process *current-process*) (current-process-action ())) (#-openmcl-native-threads progn #+openmcl-native-threads with-locked-dll-header #+openmcl-native-threads (*timer-request-queue*) (do-dll-nodes (r *timer-request-queue*) (when (> (timer-request-expiration-tick r) now) (return)) ; Anything remaining is ; in the future. (dequeue-timer-request r) (let* ((proc (timer-request-process r)) (func (timer-request-function r))) (if (eq proc current-process) (if (null current-process-action) (setq current-process-action func)) (process-interrupt (timer-request-process r) (timer-request-function r))))) (when current-process-action (funcall current-process-action))))) (%install-periodic-task 'process-timer-requests ; Name of periodic task 'process-timer-requests ; function to call (ticks-per-second) ; Run once per second )