Cancelable sit-for wrapper for a long-running Emacs Lisp polling loop
0 reputation · 28 Aug 2020, 19:41 UTC
0 reputation · 28 Aug 2020, 19:41 UTC
I am writing an Emacs Lisp polling loop that waits between iterations using sit-for, and I want the wait to be interruptible in two ways: by the user pressing C-g, and by an external condition (a process sentinel setting a flag) without relying on keyboard input.
My understanding is that sit-for returns t when input arrives and nil on timeout, and that C-g aborts it through keyboard-quit. What is unclear to me is how to structure the wait so that a sentinel-updated flag is noticed promptly, since sit-for with a long duration will not wake early for a variable change alone. I assume Emacs 29 and would like to avoid busy-waiting with very short intervals.
I have seen with-timeout mentioned as an option, but I am unsure how it composes with C-g handling and whether nested sit-for calls inside a sentinel create re-entrancy problems in the event loop.
Is there an idiomatic pattern for a cancelable, condition-aware wait built on sit-for? Should the sentinel signal the wait via input simulation, or is polling the flag with short sit-for intervals the accepted approach? Are there pitfalls when this runs during a recursive edit?
Wrap each sit-for call in a condition-case that catches the quit signal, then test the sentinel-updated flag immediately after sit-for returns. Break the loop when the flag is set, and let C-g abort via the caught quit signal. Polling the flag after each short wait (1–2 seconds) is the accepted approach; there is no need to simulate input from the sentinel.
sit-for returns t when input arrives and nil on timeout.C-g raises the quit signal, which condition-case can catch.sit-for will not wake early for a variable change alone, so the flag must be checked between waits.The event loop runs sentinel callbacks between your sit-for calls, so a flag set by a sentinel is visible on the next iteration. A moderate interval keeps CPU use negligible while staying responsive to both C-g and the external condition.
(defvar my-poll-done nil
"Flag set by the sentinel when the waited-for condition occurs.")
(defun my-poll-loop ()
(while (not my-poll-done)
(condition-case nil
(sit-for 2) ; wait up to 2 seconds
(quit ; C-g arrived
(message "C-g caught - aborting")
(signal 'quit nil)))
;; sit-for returned: t on input, nil on timeout; check the flag
(when my-poll-done
(message "Sentinel flag set - exiting loop"))))
Keep the sentinel minimal — it should only set the flag:
(setq my-poll-done nil)
(let ((proc (start-process "test" "*test*" "sleep" "5")))
(set-process-sentinel proc
(lambda (proc event)
(setq my-poll-done t))))
(my-poll-loop)
*scratch*, then run the example. The loop should end after the sleep finishes, or immediately when you press C-g.M-x profiler-start / M-x profiler-stop; a 1–2 second interval should show negligible overhead.inhibit-quit around sit-for; that suppresses C-g until it is nil again.sit-for or any input-processing command inside the sentinel; that risks event-loop re-entrancy.with-timeout, a firing timeout exits the body regardless of flag state — check the flag after the form or handle the timeout value explicitly.sit-for still honors quit and timeout, but make sure the flag is checked at the same nesting level; recursion-depth can help diagnose interference.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.