Subject: 23.1; Emacs hangs when two run-at-time calls in effect

From: Sullivan Beck <[email-masked]>
Date: Wed, 18 Nov 2009 10:41:48 -0500

I wrote two simple emacs extensions, both of which use the run-at-time
function periodically write some information to a file. When one or the
other is loaded, emacs works fine. When both are loaded, emacs will work
fine for a while, and then suddently start behaving very sluggishly.
Keyboard input will not be printed on the screen for 2-4 seconds. It
never seems to recover (though the periodic work should only take a
fraction of a second) and eventually, I have to kill emacs and restart.

I'll include both extensions below, though I don't believe that either
of them are directly related to the cause of the problem... it just
happened that they both use run-at-time.

The first is to make the scratch buffer persistent. I (perhaps
unwisely... but it's a habit I've gotten into) use the scratch buffer
almost like a post-it note where I just jot things down and I don't want
to lose it if my emacs gets shut down (either due to a system crash,
power outage, or whatever). For safety, I save the buffer to a file
every 5 minutes.

##### scratch.el ####################################
;; This makes the scratch buffer persistent. It will preserve the
;; contents of the buffer in a file stored in the variable scratch-file.
;; Whenever the scratch buffer is killed, it will automatically be
;; recreated with the old contents. If emacs is killed, the contents
;; will be placed in the scratch buffer the next time it is started.

;; The name of the file to preserve the scratch buffer in.
(if (not (boundp 'scratch-file))
    (setq scratch-file "~/.emacs.scratch"))

(if (not (boundp 'scratch-autosave-interval))
    (setq scratch-autosave-interval 300))

;; If the *scratch* buffer is killed, recreate it automatically and
;; preserve the contents.

(add-hook 'after-init-hook 'init-scratch-buffer)
(add-hook 'after-init-hook 'init-scratch-autosave)
(add-hook 'kill-emacs-hook 'kill-scratch-buffer)

(defun init-scratch-buffer ()
  (set-buffer (get-buffer-create "*scratch*"))
  (if (file-exists-p scratch-file)
      (insert-file-contents scratch-file))
  (lisp-interaction-mode)
  (make-local-variable 'kill-buffer-query-functions)
  (add-hook 'kill-buffer-query-functions 'restart-scratch-buffer))

;; This is called when we kill emacs.
;; Save *scratch*, kill *scratch*, don't restart it
;;
(defun kill-scratch-buffer ()
  (save-scratch-buffer)

  (save-current-buffer
    (set-buffer "*scratch*")
    (remove-hook 'kill-buffer-query-functions 'restart-scratch-buffer))

  (kill-buffer "*scratch*"))

;; This saves the scratch buffer.
;;
(defun save-scratch-buffer ()
  (save-current-buffer
    (set-buffer "*scratch*")
    (write-region nil nil scratch-file)))

;; This restarts the scratch buffer. If we're currently in the scratch
;; buffer, come back to it. Otherwise, preserve the current buffer.
;;
(defun restart-scratch-buffer ()
  (setq currbuf (buffer-name))

  (save-current-buffer

    ;; Kill the current (*scratch*) buffer
    (kill-scratch-buffer)

    ;; Make a brand new *scratch* buffer
    (init-scratch-buffer))

  (if (string= currbuf "*scratch*")
      (switch-to-buffer "*scratch*"))

  ;; Since we killed it, don't let caller do that.
  nil)

(defun init-scratch-autosave ()
  (run-at-time t scratch-autosave-interval 'save-scratch-buffer))
##### scratch.el ####################################

The second, which is much less important (and it may be that there is
an extension for doing this already) is that when I start up emacs, I
like to automatically load the buffers that were loaded when emacs
stopped. For safety, I also periodically saved the buffer list.

Because I wanted the functionality of both, but couldn't deal with the
hangs, I added an option to disable the periodic save of the buffer
list (so it only gets saved when emacs is shut down nicely) and after
that change was made, emacs worked fine.

##### bufferlist.el ####################################
;; This will store the list of open files when emacs is closed. When it
;; is restarted, it will attempt to reopen them.

;; The name of the file to preserve the buffer list in.
(if (not (boundp 'bufferlist-file))
    (setq bufferlist-file "~/.emacs.bufferlist"))

(if (not (boundp 'bufferlist-autosave-interval))
    (setq bufferlist-autosave-interval 300))

(add-hook 'after-init-hook 'init-bufferlist)

(if (> bufferlist-autosave-interval 0)
  (add-hook 'after-init-hook 'init-bufferlist-autosave))

(add-hook 'kill-emacs-hook 'save-bufferlist)

;; This reads in the given buffer from the point to the end
;; of the line AND moves the point to the start of the next line.
(defun read-text-line (buffer)
  ;; Switch to the buffer, and start a line at the current point.
  (setq line "")
  (save-current-buffer
    (set-buffer buffer)

    ;; Read characters until we get to EOL or EOB
    (while (and (not (eobp))
                (not (eolp)))
      (setq c (following-char))
      (setq line (concat line (char-to-string c)))
      (forward-char))

    ;; If we're at EOL, move to the start of the next line
    (if (not (eobp))
        (forward-char)))

  ;; Return line
  line)

(defun eobp-buffer (buffer)
  (save-current-buffer
    (set-buffer buffer)
    (eobp)))

(defun eolp-buffer (buffer)
  (save-current-buffer
    (set-buffer buffer)
    (eolp)))

(defun save-bufferlist ()
  ;; Create the bufferlist buffer
  (setq buflist "")
  (setq buflistbuf (generate-new-buffer "*bufferlist*"))

  ;; Insert the buffername and filename for all open files into this buffer
  (dolist (buffer (buffer-list))
    (setq bufname (buffer-name buffer))
    (save-current-buffer
      (set-buffer buffer)
      (setq filname buffer-file-name)
      (if buffer-file-name
          (progn
            (set-buffer buflistbuf)
            (insert bufname)
            (insert "\C-j")
            (insert filname)
            (insert "\C-j")))))

  ;; Save the buffer to the file
  (save-current-buffer
    (set-buffer buflistbuf)
    (write-file bufferlist-file))
  (kill-buffer buflistbuf))

(defun init-bufferlist ()
  ;; Create the bufferlist buffer and insert the file into it.
  (save-current-buffer
    (setq buflistbuf (generate-new-buffer "*bufferlist*"))
    (set-buffer buflistbuf)
    (insert-file-contents-literally bufferlist-file))

  ;; Load each buffer/file name
  (while (not (eobp-buffer buflistbuf))
    (setq bufname (read-text-line buflistbuf))
    (setq filname (read-text-line buflistbuf))

    ;; Create the buffer. If it's different then it used to be,
    ;; rename it.
    (if (file-readable-p filname)
        (save-current-buffer
          (switch-to-buffer (find-file-noselect filname t nil nil))
          (setq name (buffer-name))
          (if (not (string= bufname name))
              (rename-buffer bufname)))))

  ;; Clean up
  (kill-buffer buflistbuf))

(defun init-bufferlist-autosave ()
  (run-at-time t bufferlist-autosave-interval 'save-bufferlist))
##### bufferlist.el ####################################

I'm sure there are other options available for both options... but
these work for me, and I believe both should work without causing
emacs to hang.

They are loaded from my init.el file as:

##### init.el ####################################
(setq scratch-file "~/..emacs/.scratch.")
(load "scratch.elc" t t t)

(setq bufferlist-autosave-interval 0)
(setq bufferlist-file "~/..emacs/.bufferlist.")
(load "bufferlist.elc" t t t)
##### init.el ####################################

If I don't set bufferlist-autosave-interval to 0 (and use the
default 5 minutes), emacs will hang. If I set it to 0 (which
disables the autosave functionaity), everything works fine.

Emacs doesn't crash, and I'm able to continue using it (though
painfully).

Thanks

In GNU Emacs 23.1.1 (x86_64-suse-linux-gnu, GTK+ Version 2.18.1)
 of 2009-10-24 on build24
Windowing system distributor `The X.Org Foundation', version 11.0.10605000
configured using `configure  '--with-pop' '--without-hesiod'
'--with-kerberos' '--with-kerberos5' '--with-xim' '--prefix=/usr'
'--mandir=/usr/share/man' '--infodir=/usr/share/info'
'--datadir=/usr/share' '--localstatedir=/var'
'--sharedstatedir=/var/lib' '--libexecdir=/usr/lib' '--with-x'
'--with-sound' '--with-sync-input' '--with-xpm' '--with-jpeg'
'--with-tiff' '--with-gif' '--with-png' '--with-rsvg' '--with-dbus'
'--without-gpm' '--with-x-toolkit=gtk' '--x-includes=/usr/include'
'--x-libraries=/usr/lib64:/usr/share/X11' '--with-xft' '--with-libotf'
'--with-m17n-flt' '--build=x86_64-suse-linux'
'build_alias=x86_64-suse-linux' 'CC=gcc' 'CFLAGS=-fmessage-length=0 -O2
-Wall -D_FORTIFY_SOURCE=2 -fstack-protector -funwind-tables
-fasynchronous-unwind-tables -g -D_GNU_SOURCE -std=gnu89 -pipe
-Wno-pointer-sign -Wno-unused-variable -Wno-unused-label
-Wno-unprototyped-calls -DSYSTEM_PURESIZE_EXTRA=55000 	
-DSITELOAD_PURESIZE_EXTRA=10000 ' 'LDFLAGS=-Wl,-O2 -Wl,--hash-size=65521''

Important settings:
  value of $LC_ALL: C
  value of $LC_COLLATE: nil
  value of $LC_CTYPE: nil
  value of $LC_MESSAGES: nil
  value of $LC_MONETARY: nil
  value of $LC_NUMERIC: nil
  value of $LC_TIME: nil
  value of $LANG: en_US.UTF-8
  value of $XMODIFIERS: @im=local
  locale-coding-system: nil
  default-enable-multibyte-characters: t

Major mode: CSS

Minor modes in effect:
  show-paren-mode: t
  tooltip-mode: t
  mouse-wheel-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  global-auto-composition-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  line-number-mode: t
  transient-mark-mode: t

Recent input:
<down> <down> <down> <down> <down> <down> <down> <down>
<down> <down> <down> <down> <down> <down> <down> C-y
<up> <right> <right> <right> <right> <right> C-x r
k <up> <up> <up> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<down-mouse-1> <mouse-1> C-SPC <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <escape> w <down-mouse-1> <mouse-1>
C-y <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> <right> <right> C-SPC
<right> <right> <right> <right> <right> <right> <right>
<right> <right> <right> <right> C-w C-x C-s <down-mouse-1>
<mouse-1> <down-mouse-1> <mouse-1> C-a C-SPC <down>
<down> <down> <down> <down> <down> <down> <down> <up>
C-w C-x C-s <down-mouse-1> <mouse-1> C-a C-SPC <down>
<down> <down> <down> C-w C-x C-s <switch-frame> C-x
C-f ~ / p e r s / p a s s w d <return> C-s m o z i
l l a C-s C-s C-s <switch-frame> <switch-frame> <down-mouse-1>
<mouse-movement> <mouse-movement> <drag-mouse-1> <switch-frame>
<down-mouse-1> <drag-mouse-1> <switch-frame> <down-mouse-1>
<drag-mouse-1> <switch-frame> <down-mouse-1> <drag-mouse-1>
<switch-frame> <down-mouse-1> <drag-mouse-1> <switch-frame>
<down-mouse-1> <drag-mouse-1> <switch-frame> <up> <switch-frame>
<up> <up> C-e <return> g e t s a t i s f a c t i o
n . c o m C-x C-s <switch-frame> <down-mouse-4> <mouse-4>
<double-down-mouse-4> <double-mouse-4> <triple-down-mouse-4>
<triple-mouse-4> <triple-down-mouse-4> <triple-mouse-4>
<down-mouse-1> <mouse-movement> <mouse-movement> <drag-mouse-1>
<down-mouse-1> <mouse-movement> <mouse-movement> <drag-mouse-1>
<down-mouse-1> <mouse-movement> <mouse-movement> <drag-mouse-1>
<down-mouse-1> <mouse-1> <switch-frame> C-x k <return>
<switch-frame> <switch-frame> <switch-frame> <escape>
x r e p o r t SPC e m <tab> <return>

Recent messages:
Saving file
/home/sulbeck/.thunderbird/3crazqn6.default/chrome/userChrome.css...
Wrote /home/sulbeck/.thunderbird/3crazqn6.default/chrome/userChrome.css
Mark set
Saving file
/home/sulbeck/.thunderbird/3crazqn6.default/chrome/userChrome.css...
Wrote /home/sulbeck/.thunderbird/3crazqn6.default/chrome/userChrome.css
Wrote /home/sulbeck/..emacs/.scratch. [12 times]
Mark saved where search started
Saving file /home/sulbeck/pers/passwd...
Wrote /home/sulbeck/pers/passwd
Wrote /home/sulbeck/..emacs/.scratch. [18 times]

-- 
--------------------------|  Sullivan Beck  |---------------------------
Email      : [email-masked]     |                 University of Florida
Work Phone : (352) 273-1367      |     Computing and Networking Services
                                 |                              301 SSRB
                                 |                Gainesville, FL  32611
------------------------------------------------------------------------
 For non-work related matters, please contact by email instead of phone
------------------------------------------------------------------------