EmacsTalk

如何排查 Tramp 卡住问题

Jiacai Liu   tags: tips tramp

今天在进行一个操作时,突然出现了访问 tramp 的操作,最近有一段时间没有使用过,所以看到这个消息时比较好奇,是什么操作导致触发了 tramp。

之前有些排查 tramp 卡住的经验,知道一些看似无关的函数在调用时,会去访问已经打开的 tramp buffer,比如 file-truename ,这次又是什么函数触发了呢?

首先调高 tramp 日志级别 (setq tramp-verbose 10) ,之后重复会触发 tramp 的操作,这时 minibuffer 中应该会有类似下面的输出:

Tramp: Opening connection nil for dev using ssh...

由于 dev 已经关机,所以这里是连接不上的,此时可以 C-g 将当前操作强制取消,然后去找 *debug tramp/ssh dev* 的 buffer,这个名字中的 dev 是我 ssh config 中一台机器的别名,翻到这个 buffer 的最后面,会有触发 tramp 的调用链,如下:

20:23:33.548536 tramp-recentf-cleanup (10) #
  backtrace()
  tramp-error((tramp-file-name "ssh" nil nil "dev" nil "~/" nil) wrong-type-argument "listp abbreviate-file-name")
  tramp-signal-hook-function(wrong-type-argument (listp abbreviate-file-name))
  recentf-apply-filename-handlers("~/.config/emacs/deps.el")
  recentf-expand-file-name("~/.config/emacs/deps.el")
  recentf-cleanup()
  tramp-recentf-cleanup((tramp-file-name "ssh" nil nil "dev" nil "~/" nil))
  run-hook-with-args(tramp-recentf-cleanup (tramp-file-name "ssh" nil nil "dev" nil "~/" nil))
  tramp-cleanup-connection((tramp-file-name "ssh" nil nil "dev" nil "~/" nil) t)
  tramp-maybe-open-connection((tramp-file-name "ssh" nil nil "dev" nil "~/" nil))
  tramp-send-command((tramp-file-name "ssh" nil nil "dev" nil "~/" nil) "echo ~ 2>/dev/null; echo tramp_exit_status $?")
  tramp-send-command-and-check((tramp-file-name "ssh" nil nil "dev" nil "~/" nil) "echo ~")
  tramp-sh-handle-get-home-directory((tramp-file-name "ssh" nil nil "dev" nil "~/" nil) "")
  apply(tramp-sh-handle-get-home-directory ((tramp-file-name "ssh" nil nil "dev" nil "~/" nil) ""))
  tramp-sh-file-name-handler(tramp-get-home-directory (tramp-file-name "ssh" nil nil "dev" nil "~/" nil) "")
  apply(tramp-sh-file-name-handler tramp-get-home-directory ((tramp-file-name "ssh" nil nil "dev" nil "~/" nil) ""))
  tramp-file-name-handler(tramp-get-home-directory (tramp-file-name "ssh" nil nil "dev" nil "~/" nil) "")
  tramp-get-home-directory((tramp-file-name "ssh" nil nil "dev" nil "~/" nil) "")
  tramp-sh-handle-expand-file-name("/ssh:dev:~/" nil)
  apply(tramp-sh-handle-expand-file-name ("/ssh:dev:~/" nil))
  tramp-sh-file-name-handler(expand-file-name "/ssh:dev:~/" nil)
  apply(tramp-sh-file-name-handler expand-file-name ("/ssh:dev:~/" nil))
  tramp-file-name-handler(expand-file-name "/ssh:dev:~/" nil)
  expand-file-name("/ssh:dev:~/")
  read-file-name-default("Use file /dev/null: " "/dev/" "/dev/null" t "null" nil)
  read-file-name("Use file /dev/null: " "/dev/" "/dev/null" t "null")
  diff-find-file-name()
  diff-add-log-current-defuns()
  log-edit-generate-changelog-from-diff()
  funcall-interactively(log-edit-generate-changelog-from-diff)
  call-interactively(log-edit-generate-changelog-from-diff nil nil)
  command-execute(log-edit-generate-changelog-from-diff)

从中可以看到, log-edit-generate-changelog-from-diff 是我主动调用的函数,它间接触发了 tramp 操作,其中关键的两行日志是:

  expand-file-name("/ssh:dev:~/")
  read-file-name-default("Use file /dev/null: " "/dev/" "/dev/null" t "null" nil)

expand-file-name 的输入是一个 tramp 文件地址,这个是怎么来的呢?从 read-file-name-default 的代码来看,是这么调用来的:

 (abbreviate-file-name "/dev/")

为什么 /dev/ 在进行缩写时,会得到 "/ssh:dev:~/" 这个文件地址呢?

问题排查到这里,其实可以继续看 abbreviate-file-name 的源码,但是这时我脑子中隐约对这个 ssh 地址有些印象,因为 dev 是我日常开发中需要经常用到的一台 Linux 机器,为了方便,我之前定义了这么一个缩写:

(setq directory-abbrev-alist '(("^/dev" . "/ssh:dev:~")))

这样的话我可以通过在 find-file 中,直接输入 /dev 来直接打开 tramp 链接。

所以到这里问题基本上就清楚了,是 abbreviate-file-name 在内部使用了 directory-abbrev-alist ,所以导致了本文的问题,解决方法也很简单,直接从 directory-abbrev-alist 去掉这个选项好了,用的也不是很多,而且通过 /ssh:dev 来打开也不是很麻烦。

希望通过本文的这个案例分享,让读者了解如何排查 tramp 相关的问题。

PS:至于为何 log-edit-generate-changelog-from-diff 为什么会去访问 /dev/ ,这就是另一个问题了,感兴趣的同学可以从对照上面的堆栈,继续查看源码。

收听方式

反馈