From 50765f3f511d467ff024dda7c84530c759253d18 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Tue, 31 Aug 2021 03:04:22 +0200 Subject: [PATCH] Make run-at-time try harder to run at integral multiples * lisp/emacs-lisp/timer.el (timer): Add new slot integral-multiple. (timerp): Adjust. (timer-event-handler): Recompute the delay if requested (bug#39099). (run-at-time): Mark the timer as recomputable if given a t parameter. * src/keyboard.c (decode_timer): Adjust. --- etc/NEWS | 11 ++++++ lisp/emacs-lisp/timer.el | 81 +++++++++++++++++++++++----------------- src/keyboard.c | 2 +- 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 657e12900fa..66006db8e0a 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -3145,6 +3145,17 @@ work for `context-menu-mode` in Xterm. * Incompatible Lisp Changes in Emacs 28.1 +--- +** 'run-at-time' now tries harder to implement the t TIME parameter. +If TIME is t, the timer runs at an integral multiple of REPEAT. +(I.e., if given a REPEAT of 60, it'll run at 08:11:00, 08:12:00, +08:13:00.) However, when a machine goes to sleep (or otherwise didn't +get a time slot to run when the timer was scheduled), the timer would +then fire every 60 seconds after the time the timer was fired. This +has now changed, and the timer code now recomputes the integral +multiple every time it runs, which means that if the laptop wakes at +08:16:43, it'll fire at that time, but then at 08:17:00, 08:18:00... + --- ** 'parse-partial-sexp' now signals an error if TO is smaller than FROM. Previously this would lead to the function interpreting FROM as TO and diff --git a/lisp/emacs-lisp/timer.el b/lisp/emacs-lisp/timer.el index 36de29a73a8..f7715109c82 100644 --- a/lisp/emacs-lisp/timer.el +++ b/lisp/emacs-lisp/timer.el @@ -29,6 +29,8 @@ (eval-when-compile (require 'cl-lib)) +;; If you change this structure, you also have to change `timerp' +;; (below) and decode_timer in keyboard.c. (cl-defstruct (timer (:constructor nil) (:copier nil) @@ -46,11 +48,16 @@ repeat-delay function args ;What to do when triggered. idle-delay ;If non-nil, this is an idle-timer. - psecs) + psecs + ;; A timer may be created with `t' as the TIME, which means that we + ;; want to run at specific integral multiples of `repeat-delay'. We + ;; then have to recompute this (because the machine may have gone to + ;; sleep, etc). + integral-multiple) (defun timerp (object) "Return t if OBJECT is a timer." - (and (vectorp object) (= (length object) 9))) + (and (vectorp object) (= (length object) 10))) (defsubst timer--check (timer) (or (timerp timer) (signal 'wrong-type-argument (list #'timerp timer)))) @@ -284,6 +291,13 @@ This function is called, by name, directly by the C code." (if (> repeats timer-max-repeats) (timer-inc-time timer (* (timer--repeat-delay timer) repeats))))) + ;; If we want integral multiples, we have to recompute + ;; the repetition. + (when (and (timer--integral-multiple timer) + (not (timer--idle-delay timer))) + (setf (timer--time timer) + (timer-next-integral-multiple-of-time + (current-time) (timer--repeat-delay timer)))) ;; Place it back on the timer-list before running ;; timer--function, so it can cancel-timer itself. (timer-activate timer t cell) @@ -340,45 +354,44 @@ This function returns a timer object which you can use in `cancel-timer'." (interactive "sRun at time: \nNRepeat interval: \naFunction: ") - (or (null repeat) - (and (numberp repeat) (< 0 repeat)) - (error "Invalid repetition interval")) + (when (and repeat + (numberp repeat) + (< repeat 0)) + (error "Invalid repetition interval")) - ;; Special case: nil means "now" and is useful when repeating. - (if (null time) + (let ((timer (timer-create))) + ;; Special case: nil means "now" and is useful when repeating. + (unless time (setq time (current-time))) - ;; Special case: t means the next integral multiple of REPEAT. - (if (and (eq time t) repeat) - (setq time (timer-next-integral-multiple-of-time (current-time) repeat))) + ;; Special case: t means the next integral multiple of REPEAT. + (when (and (eq time t) repeat) + (setq time (timer-next-integral-multiple-of-time (current-time) repeat)) + (setf (timer--integral-multiple timer) t)) - ;; Handle numbers as relative times in seconds. - (if (numberp time) + ;; Handle numbers as relative times in seconds. + (when (numberp time) (setq time (timer-relative-time nil time))) - ;; Handle relative times like "2 hours 35 minutes" - (if (stringp time) - (let ((secs (timer-duration time))) - (if secs - (setq time (timer-relative-time nil secs))))) - - ;; Handle "11:23pm" and the like. Interpret it as meaning today - ;; which admittedly is rather stupid if we have passed that time - ;; already. (Though only Emacs hackers hack Emacs at that time.) - (if (stringp time) - (progn - (require 'diary-lib) - (let ((hhmm (diary-entry-time time)) - (now (decode-time))) - (if (>= hhmm 0) - (setq time - (encode-time 0 (% hhmm 100) (/ hhmm 100) - (decoded-time-day now) - (decoded-time-month now) - (decoded-time-year now) - (decoded-time-zone now))))))) + ;; Handle relative times like "2 hours 35 minutes". + (when (stringp time) + (when-let ((secs (timer-duration time))) + (setq time (timer-relative-time nil secs)))) + + ;; Handle "11:23pm" and the like. Interpret it as meaning today + ;; which admittedly is rather stupid if we have passed that time + ;; already. (Though only Emacs hackers hack Emacs at that time.) + (when (stringp time) + (require 'diary-lib) + (let ((hhmm (diary-entry-time time)) + (now (decode-time))) + (when (>= hhmm 0) + (setq time (encode-time 0 (% hhmm 100) (/ hhmm 100) + (decoded-time-day now) + (decoded-time-month now) + (decoded-time-year now) + (decoded-time-zone now)))))) - (let ((timer (timer-create))) (timer-set-time timer time repeat) (timer-set-function timer function args) (timer-activate timer) diff --git a/src/keyboard.c b/src/keyboard.c index 2e4c4e6aabf..81ff9df153d 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4234,7 +4234,7 @@ decode_timer (Lisp_Object timer, struct timespec *result) { Lisp_Object *vec; - if (! (VECTORP (timer) && ASIZE (timer) == 9)) + if (! (VECTORP (timer) && ASIZE (timer) == 10)) return false; vec = XVECTOR (timer)->contents; if (! NILP (vec[0])) -- 2.39.2