Emacs timer gotchas
Gotcha: If you use a repeating timer and suspend the computer, all the queued repeats will execute simultaneously when you come back. If you only want it to be called once at a given time regardless of computer sleep, make a function that passes itself to
(run-with-timer)
as suggested in the manual. So instead of using a repeating timer, you make a chain of once-off timers.(Given that you want this behavior in almost all use cases of repeating,
(run-with-timer)
is a too low-level function to recommend to programmers – it's only solid if you avoid the REPEAT argument. Repeating safely requires boilerplate which you can skip with named-timer, a 70-line library.)a. With the usage pattern suggested in the manual, you'll run into a different gotcha: a bug anywhere in your function body will stop it from restarting the timer. So you thought you'd set up a repeating timer but the chain of restarts died at some point and never gets back up and running. Either restart the timer first of all, or wrap the work in
(unwind-protect)
. The boilerplate goes something like:(defvar my-timer nil) (defun my-next-tick () (unwind-protect (progn (do work) (do more work) (work. work never changes)) (when (timerp my-timer) ;; prevent duplicates (cancel-timer my-timer)) (setq my-timer (run-with-timer 60 nil #'my-next-tick)))) (my-next-tick)
Note how we never specify the REPEAT argument!
We cancel the timer just in case something has run
(my-next-tick)
more than once (one culprit is reloading your library), which would spawn more than one series of timers visible inM-x list-timers
even though the variablemy-timer
only holds a reference to one of them. The library named-timer takes care of this boilerplate as well as the need for the defvar.- Gotcha: If you use a repeating idle timer, it will not repeat after the first invocation, for as long as you stay idle. If you want it to continue repeating while idle, don't use an idle timer. If you want behavior to be different while idle (especially if you want the repeat interval to differ), use two different functions plus a third that checks whether you're idle and selects the appropriate function. For an example how you may do this, go to eva.el and search for
eva--start-next-timer
. Again, this never involves an actual idle timer, just standard once-off timers. - Gotcha (and duh): don't use the private getter
(timer--triggered my-timer)
. How to check that your chain of timers got killed and hasn't restarted? Eval(member my-timer timer-list)
or(member my-timer timer-idle-list)
. It'd help discoverability if there was a descriptively named builtin function that just did this. However, this method isn't foolproof, see #4. - Gotcha: I had a "keepalive" timer that would respawn the first timer if there was a problem. Being a bona fide repeating timer, it was invoked many times upon return from a laptop-suspend state, which should have been fine because it checked
(member my-timer timer-list)
– but it went ahead and spawned many copies of this timer; in other words, the conditional failed. Clearly the variabletimer-list
is not always updated in time. In summary, even if your code looks foolproof, it isn't. Make it verifiably foolproof with a safety wrapper like named-timer. And when you have a repeating timer, check and set some boolean to ensure you only start one on laptop wakeup.
What links here
- 2021-01-10