Losing items in recentf, org-id-locations? Run kill-emacs-hook periodically.

Losing items in recentf, org-id-locations? Run kill-emacs-hook periodically.

This has been annoying me for years. Every time I start #Emacs, there will be some org notes I cannot find by org-id, and recentf suffers partial amnesia so that I cannot jump to a file I was just working on minutes ago (before Emacs crashed), and so on.

The issue stems from a programming anti-pattern: assuming that takedown logic will execute. In other words, relying on kill-emacs-hook.

At the end of this page, you can find an initfile snippet to fix this.


Rant:

It's insane to put any sort of data-sync on kill-emacs-hook. Most of the time my Emacs goes down, it happens in a non-clean way – why would I intentionally shut off Emacs if everything is fine? Ergo, that hook never runs. Package developers, look over your code and ask what happens if kill-emacs-hook never runs.

If you want to persist state across restarts, you must write it to disk at some other time than takedown. Assume your user's customary method of restarting is yanking the power cord – code accordingly.

IMO they should warn developers about this in the docstring.


(defvar my-state-sync-hooks nil
  "Dynamic variable.
For some reason, `run-hooks' can't use a let-bound list when
lexical binding is t, so instead `my-state-sync' will work with
this global variable.")

(defun my-state-sync ()
  "Write histories and caches to disk.
This runs many members of `kill-emacs-hook' so we don't have to
rely on that hook.  You may put this on a repeating idle timer."
  (setq my-state-sync-hooks
        (seq-intersection
         ;; NOTE: Check your `kill-emacs-hook' in case there's
         ;; more functions you want to add here.
         #'(bookmark-exit-hook-internal
            savehist-autosave
            transient-maybe-save-history
            org-id-locations-save
            save-place-kill-emacs-hook
            recentf-save-list
            recentf-cleanup)
         kill-emacs-hook))
  (run-hooks 'my-state-sync-hooks))

;; Run after 3 minutes of idle.
;; (Will not repeat until user becomes idle again.)
(setq my-state-sync-timer
      (run-with-idle-timer (* 3 60) t #'my-state-sync))

Bonus tips

If your exact problem concerned abbrev or recentf, one of these lines could be sufficient for you.

(add-hook 'kill-emacs-hook #'write-abbrev-file)
(setq recentf-max-saved-items 1000)
Created (2 years ago)