leoliu / ggtags

Emacs frontend to GNU Global source code tagging system.
http://elpa.gnu.org
578 stars 56 forks source link

With tramp: Error running timer ‘compilation-auto-jump’: (wrong-type-argument stringp nil) #217

Open Andreas-Krebbel opened 2 years ago

Andreas-Krebbel commented 2 years ago

Trying to jump to a definition with one match in a file accessed via ssh using tramp gives me (with emacs 27.2): Error running timer ‘compilation-auto-jump’: (wrong-type-argument stringp nil)

This looks quite similar to #89

Debugger entered--Lisp error: (wrong-type-argument stringp nil)
  get-buffer(nil)
  display-buffer(nil (nil (allow-no-window . t)))
  compilation-goto-locus(#<marker in no buffer> #<marker at 19252 in foo.c> nil)
  compilation-next-error-function(0 nil)
  next-error-internal()
  compile-goto-error()
  (if compilation-auto-jump-to-first-error (compile-goto-error))
  (save-current-buffer (set-buffer buffer) (goto-char pos) (let ((win (get-buffer-window buffer 0))) (if win (set-window-point win pos))) (if compilation-auto-jump-to-first-error (compile-goto-error)))
  (progn (message buffer-file-name buffer) (save-current-buffer (set-buffer buffer) (goto-char pos) (let ((win (get-buffer-window buffer 0))) (if win (set-window-point win pos))) (if compilation-auto-jump-to-first-error (compile-goto-error))))
  (if (buffer-live-p buffer) (progn (message buffer-file-name buffer) (save-current-buffer (set-buffer buffer) (goto-char pos) (let ((win (get-buffer-window buffer 0))) (if win (set-window-point win pos))) (if compilation-auto-jump-to-first-error (compile-goto-error)))) (message "buffer deleted"))
  compilation-auto-jump(#<killed buffer> 301)
  apply(compilation-auto-jump (#<killed buffer> 301))
  timer-event-handler([t 25067 8932 580775 nil compilation-auto-jump (#<killed buffer> 301) nil 906000])

Enabling tracing for a few of the involved functions indicates that ggtags-navigation-mode-cleanup gets invoked somewhere in the middle of compilation-find-file. The ggtags buffer is gone afterwards what triggers the problem:

1 -> (compilation-next-error-function 0 nil)
| 2 -> (compilation-find-file #<marker at 313 in *ggtags-global*> "foo.c" nil)
| | 3 -> (ggtags-navigation-mode-cleanup #<buffer *ggtags-global*> t)
| | 3 <- ggtags-navigation-mode-cleanup: t
| 2 <- compilation-find-file: #<buffer foo.c>
| 2 -> (compilation-move-to-column 6 nil)
| 2 <- compilation-move-to-column: 883
| 2 -> (compilation-goto-locus #<marker in no buffer> #<marker at 883 in foo.c> nil)
| 2 <- compilation-goto-locus: !non-local\ exit!
1 <- compilation-next-error-function: !non-local\ exit!

Apparently when tramp waits for the output of the ssh process it tries to do other stuff. It can be prevented to invoke timers by turning the last parameter of accept-process-output to -1 but this does not prevent sentinels from getting called. The "compilation-sentinel" then invokes the cleanup routine which throws away the buffer.

ggtags-navigation-mode-cleanup(#<buffer *ggtags-global*> t)
  ggtags-global-handle-exit(#<buffer *ggtags-global*> "finished\n")
  run-hook-with-args(ggtags-global-handle-exit #<buffer *ggtags-global*> "finished\n")
  compilation-handle-exit(exit 0 "finished\n")
  compilation-sentinel(#<process global> "finished\n")
  accept-process-output(#<process *tramp/ssh x*> nil nil t)
  tramp-accept-process-output(#<process *tramp/ssh x*>)
  tramp-wait-for-regexp(#<process *tramp/ssh x*> nil "\\(^\\|\0\\)[^#$\n]*///5a88bf322cb36bc974ff5717ef7cfb5f...")
  tramp-wait-for-output(#<process *tramp/ssh x*>)
  tramp-send-command((tramp-file-name "ssh" "user" nil "x" nil "foo..." "ssh y|") "test -e foo...")
  tramp-send-command-and-check((tramp-file-name "ssh" "user" nil "x" nil "foo..." "ssh y|") "test -e foo...")
  tramp-sh-handle-file-exists-p("/ssh y|ssh:an...")
  apply(tramp-sh-handle-file-exists-p "/ssh y|ssh:an...")
  tramp-sh-file-name-handler(file-exists-p "/ssh y|ssh:an...")
  apply(tramp-sh-file-name-handler file-exists-p "/ssh y|ssh:an...")
  tramp-file-name-handler(file-exists-p "/ssh y|ssh:an...")
  file-exists-p("/ssh y|ssh:an...")
  (and (file-exists-p name) (find-file-noselect name))
  (setq name (expand-file-name (format (car fmts) filename) thisdir) buffer (and (file-exists-p name) (find-file-noselect name)) fmts (cdr fmts))
  (while (and fmts (null buffer)) (setq name (expand-file-name (format (car fmts) filename) thisdir) buffer (and (file-exists-p name) (find-file-noselect name)) fmts (cdr fmts)))
  (while (and dirs (null buffer)) (setq thisdir (or (car dirs) spec-dir) fmts formats) (while (and fmts (null buffer)) (setq name (expand-file-name (format (car fmts) filename) thisdir) buffer (and (file-exists-p name) (find-file-noselect name)) fmts (cdr fmts))) (setq dirs (cdr dirs)))
  (let ((dirs compilation-search-path) (spec-dir (if directory (expand-file-name directory) default-directory)) buffer thisdir fmts name) (if (and filename (file-name-absolute-p filename)) (setq filename (abbreviate-file-name (expand-file-name filename)) dirs (cons (file-name-directory filename) dirs) filename (file-name-nondirectory filename))) (while (and dirs (null buffer)) (setq thisdir (or (car dirs) spec-dir) fmts formats) (while (and fmts (null buffer)) (setq name (expand-file-name (format (car fmts) filename) thisdir) buffer (and (file-exists-p name) (find-file-noselect name)) fmts (cdr fmts))) (setq dirs (cdr dirs))) (while (null buffer) (save-excursion (let ((w (let (...) (display-buffer ... ...)))) (with-current-buffer (marker-buffer marker) (goto-char marker) (and w (progn (compilation-set-window w marker) (compilation-set-overlay-arrow w)))) (let* ((name (read-file-name ... spec-dir filename t nil)) (origname name)) (cond ((not ...) (message "Cannot find file `%s'" name) (ding) (sit-for 2)) ((and ... ...) (message "No `%s' in directory %s" filename origname) (ding) (sit-for 2)) (t (setq buffer ...))))))) (dolist (ov (overlays-in (point-min) (point-max))) (when (overlay-get ov 'intangible) (overlay-put ov 'intangible nil))) buffer)
  (closure (t) (marker filename directory &rest formats) "Find a buffer for file FILENAME.\nIf FILENAME is no..." (or formats (setq formats '("%s"))) (let ((dirs compilation-search-path) (spec-dir (if directory (expand-file-name directory) default-directory)) buffer thisdir fmts name) (if (and filename (file-name-absolute-p filename)) (setq filename (abbreviate-file-name (expand-file-name filename)) dirs (cons (file-name-directory filename) dirs) filename (file-name-nondirectory filename))) (while (and dirs (null buffer)) (setq thisdir (or (car dirs) spec-dir) fmts formats) (while (and fmts (null buffer)) (setq name (expand-file-name (format ... filename) thisdir) buffer (and (file-exists-p name) (find-file-noselect name)) fmts (cdr fmts))) (setq dirs (cdr dirs))) (while (null buffer) (save-excursion (let ((w ...)) (with-current-buffer (marker-buffer marker) (goto-char marker) (and w ...)) (let* (... ...) (cond ... ... ...))))) (dolist (ov (overlays-in (point-min) (point-max))) (when (overlay-get ov 'intangible) (overlay-put ov 'intangible nil))) buffer))(#<marker at 313 in *ggtags-global*> "foo.c" nil)
  apply((closure (t) (marker filename directory &rest formats) "Find a buffer for file FILENAME.\nIf FILENAME is no..." (or formats (setq formats '("%s"))) (let ((dirs compilation-search-path) (spec-dir (if directory (expand-file-name directory) default-directory)) buffer thisdir fmts name) (if (and filename (file-name-absolute-p filename)) (setq filename (abbreviate-file-name (expand-file-name filename)) dirs (cons (file-name-directory filename) dirs) filename (file-name-nondirectory filename))) (while (and dirs (null buffer)) (setq thisdir (or (car dirs) spec-dir) fmts formats) (while (and fmts (null buffer)) (setq name (expand-file-name (format ... filename) thisdir) buffer (and (file-exists-p name) (find-file-noselect name)) fmts (cdr fmts))) (setq dirs (cdr dirs))) (while (null buffer) (save-excursion (let ((w ...)) (with-current-buffer (marker-buffer marker) (goto-char marker) (and w ...)) (let* (... ...) (cond ... ... ...))))) (dolist (ov (overlays-in (point-min) (point-max))) (when (overlay-get ov 'intangible) (overlay-put ov 'intangible nil))) buffer)) (#<marker at 313 in *ggtags-global*> "foo.c" nil))
  #f(compiled-function (body &rest args) #<bytecode 0x1394e2d>)((closure (t) (marker filename directory &rest formats) "Find a buffer for file FILENAME.\nIf FILENAME is no..." (or formats (setq formats '("%s"))) (let ((dirs compilation-search-path) (spec-dir (if directory (expand-file-name directory) default-directory)) buffer thisdir fmts name) (if (and filename (file-name-absolute-p filename)) (setq filename (abbreviate-file-name (expand-file-name filename)) dirs (cons (file-name-directory filename) dirs) filename (file-name-nondirectory filename))) (while (and dirs (null buffer)) (setq thisdir (or (car dirs) spec-dir) fmts formats) (while (and fmts (null buffer)) (setq name (expand-file-name (format ... filename) thisdir) buffer (and (file-exists-p name) (find-file-noselect name)) fmts (cdr fmts))) (setq dirs (cdr dirs))) (while (null buffer) (save-excursion (let ((w ...)) (with-current-buffer (marker-buffer marker) (goto-char marker) (and w ...)) (let* (... ...) (cond ... ... ...))))) (dolist (ov (overlays-in (point-min) (point-max))) (when (overlay-get ov 'intangible) (overlay-put ov 'intangible nil))) buffer)) #<marker at 313 in *ggtags-global*> "foo.c" nil)
  apply(#f(compiled-function (body &rest args) #<bytecode 0x1394e2d>) (closure (t) (marker filename directory &rest formats) "Find a buffer for file FILENAME.\nIf FILENAME is no..." (or formats (setq formats '("%s"))) (let ((dirs compilation-search-path) (spec-dir (if directory (expand-file-name directory) default-directory)) buffer thisdir fmts name) (if (and filename (file-name-absolute-p filename)) (setq filename (abbreviate-file-name (expand-file-name filename)) dirs (cons (file-name-directory filename) dirs) filename (file-name-nondirectory filename))) (while (and dirs (null buffer)) (setq thisdir (or (car dirs) spec-dir) fmts formats) (while (and fmts (null buffer)) (setq name (expand-file-name (format ... filename) thisdir) buffer (and (file-exists-p name) (find-file-noselect name)) fmts (cdr fmts))) (setq dirs (cdr dirs))) (while (null buffer) (save-excursion (let ((w ...)) (with-current-buffer (marker-buffer marker) (goto-char marker) (and w ...)) (let* (... ...) (cond ... ... ...))))) (dolist (ov (overlays-in (point-min) (point-max))) (when (overlay-get ov 'intangible) (overlay-put ov 'intangible nil))) buffer)) (#<marker at 313 in *ggtags-global*> "foo.c" nil))
  compilation-find-file(#<marker at 313 in *ggtags-global*> "foo.c" nil)
  apply(compilation-find-file #<marker at 313 in *ggtags-global*> "foo.c" nil nil)
  #f(compiled-function (n &optional reset) "Advance to the next error message and visit the file where the error was.\nThis is the value of `next-error-function' in Compilation buffers." (interactive "p") #<bytecode 0x110df35>)(0 nil)
  apply(#f(compiled-function (n &optional reset) "Advance to the next error message and visit the file where the error was.\nThis is the value of `next-error-function' in Compilation buffers." (interactive "p") #<bytecode 0x110df35>) (0 nil))
  #f(compiled-function (body &rest args) #<bytecode 0x11bb751>)(#f(compiled-function (n &optional reset) "Advance to the next error message and visit the file where the error was.\nThis is the value of `next-error-function' in Compilation buffers." (interactive "p") #<bytecode 0x110df35>) 0 nil)
  apply(#f(compiled-function (body &rest args) #<bytecode 0x11bb751>) #f(compiled-function (n &optional reset) "Advance to the next error message and visit the file where the error was.\nThis is the value of `next-error-function' in Compilation buffers." (interactive "p") #<bytecode 0x110df35>) (0 nil))
  compilation-next-error-function(0 nil)
  next-error-internal()
  compile-goto-error()
  compilation-auto-jump(#<buffer *ggtags-global*> 313)
  apply(compilation-auto-jump (#<buffer *ggtags-global*> 313))
  timer-event-handler([t 25070 54931 998249 nil compilation-auto-jump (#<buffer *ggtags-global*> 313) nil 227000])

To me it looks like ggtags-navigation-mode-cleanup needs a way to prevent itself from running in parallel to the execution of the compilation-auto-jump code path. ggtags already makes sure to call the compilation-auto-jump synchronously before calling ggtags-navigation-mode-cleanup. So perhaps we can also remove it from the timer list to make sure it doesn't get called another time in parallel?

TrunovS commented 2 years ago

Encountered the same error today. Maybe it can be fixed with some quick workaround?

Ergus commented 2 years ago

Same error here... Any solution yet?

aarnez commented 2 years ago

Same here (again). The issue is obviously racy; it worked for some time, but now it's persistent again. I'm currently circumventing this very crudely, by preserving the compilation buffer even if there's only one match:

diff -u /home/arnez/.emacs.d/elpa/ggtags-20220511.610/ggtags.el\~ /home/arnez/.emacs.d/elpa/ggtags-20220511.610/ggtags.el
--- /home/arnez/.emacs.d/elpa/ggtags-20220511.610/ggtags.el~    2022-06-01 14:07:40.781288558 +0200
+++ /home/arnez/.emacs.d/elpa/ggtags-20220511.610/ggtags.el 2022-07-20 20:18:20.920608602 +0200
@@ -1668,11 +1668,12 @@
       ;; - http://debbugs.gnu.org/23987
       ;; - https://github.com/leoliu/ggtags/issues/89
       ;;
-      (pcase (cl-find 'compilation-auto-jump timer-list :key #'timer--function)
-        (`nil )
-        (timer (timer-event-handler timer)))
-      (ggtags-navigation-mode -1)
-      (ggtags-navigation-mode-cleanup buf t)))))
+      ;; (pcase (cl-find 'compilation-auto-jump timer-list :key #'timer--function)
+      ;;   (`nil )
+      ;;   (timer (timer-event-handler timer)))
+      ;; (ggtags-navigation-mode -1)
+      ;; (ggtags-navigation-mode-cleanup buf t)
+      ))))

 (defvar ggtags-global-mode-font-lock-keywords
   '(("^Global \\(exited abnormally\\|interrupt\\|killed\\|terminated\\)\\(?:.*with code \\([0-9]+\\)\\)?.*"
gmlarumbe commented 1 year ago

I faced this same issue in the past and finally decided to use xref with ggtags as a backend to avoid these races.

The xref backend code section also seems to have a race, but I recently sent PR #224 which seems to fix it.

Ergus commented 1 year ago

I faced this same issue in the past and finally decided to use xref with ggtags as a backend to avoid these races.

The xref backend code section also seems to have a race, but I recently sent PR #224 which seems to fix it.

At the end I also decided to use xref and all the other emacs provided infrastructure for everything (imenu, completions, project.el). And as I also faced issues there I decided to go with my own simpler and clean implementation optimized for tramp support: https://github.com/Ergus/gtags-mode

It is already in elpa btw.