lsp-mode 的 lsp-find-definition 会卡顿,有可能让它变成非阻塞么?

我在使用 lsp-find-definition 功能的时候,UI 总是会卡一下,快的话,感受不是很明显,慢的话会卡 1-2s。卡是指:我快捷键触发 lsp-find-definition 功能后,然后按上下左右,光标是不动的。

我看 lsp-mode 说自己是异步非阻塞调用?我理解无论 lsp 的后端多慢,UI 层都不会卡才对把。但我的实际体验是,我需要会等后端返回(或者等请求超时后),我才能进行其它 UI 操作。

这是我的配置问题么,还是说这种阻塞就是符合预期的?如果是我的配置问题,我应该如何定位呢?

应该是预期的吧,如果不卡住,你一直在那里点跳转,岂不是一直跳跳跳跳

我觉得 vscode 的处理方式比较理想:按下跳转后,后台在找,UI 层面不卡主。等后台找到函数定义了,UI 层就跳到那个文件。

可以先按照文档排查下卡在那里了:

而且 lsp 有很多华而不实的东西,我一般只用查找定义,其他的像 diagnose/lsp-ui 都关掉,再不行就试试 eglot,轻量很多。

2 个赞

谢谢 ~

想确认一下,理论上它确实是不应该卡的,对嘛?

按照这个文档 profile 了一下 ~ 我的操作方式是

  1. M-x profiler-start
  2. 使用快捷键触发 lsp-find-definition 操作
  3. 期间疯狂按 ctrl-n 按钮(以判断 UI 是否能相应)
  4. M-x profiler-stop
- command-execute                                                 405  90%
 - call-interactively                                             405  90%
  - funcall-interactively                                         405  90%
   - counsel-M-x                                                  341  76%
    - ivy-read                                                    325  72%
     - read-from-minibuffer                                        25   5%
      - ivy--queue-exhibit                                          7   1%
       - ivy--exhibit                                               7   1%
        - ivy--update-minibuffer                                    6   1%
         - ivy--format                                              4   0%
          - mapcar                                                  4   0%
             counsel-M-x-transformer                                4   0%
         - ivy--filter                                              2   0%
          - ivy--re-filter                                          2   0%
           - cl-remove                                              2   0%
            - apply                                                 2   0%
             - cl-delete                                            1   0%
                #<compiled 0x1ff116397f3d>                          1   0%
          ivy--insert-minibuffer                                    1   0%
      - timer-event-handler                                         2   0%
       - apply                                                      2   0%
        - auto-revert-buffers                                       2   0%
           mapcar                                                   1   0%
      - redisplay_internal (C function)                             1   0%
         eval                                                       1   0%
     - ivy--reset-state                                             4   0%
      - all-completions                                             1   0%
         counsel--M-x-externs-predicate                             1   0%
      - ivy--filter                                                 1   0%
         ivy--re-filter                                             1   0%
      - ivy-thing-at-point                                          1   0%
       - thing-at-point                                             1   0%
        - thing-at-point-url-at-point                               1   0%
         - thing-at-point-bounds-of-url-at-point                    1   0%
            thing-at-point--bounds-of-well-formed-url                  1   0%
     - ivy-call                                                     1   0%
      - counsel-M-x-action                                          1   0%
       - command-execute                                            1   0%
        - call-interactively                                        1   0%
         - funcall-interactively                                    1   0%
            profiler-stop                                           1   0%
    - counsel--M-x-externs                                         16   3%
     - smex-detect-new-commands                                     6   1%
      - mapatoms                                                    5   1%
         #<compiled 0x1ff11579932d>                                 5   1%
   - lsp-find-definition                                           44   9%
    - lsp-find-locations                                           44   9%
     - lsp-request                                                 41   9%
      - accept-process-output                                       2   0%
       - timer-event-handler                                        2   0%
        - apply                                                     2   0%
           auto-revert-buffers                                      1   0%
     - lsp--locations-to-xref-items                                 2   0%
      - seq-map                                                     2   0%
       - apply                                                      2   0%
        - #<compiled 0x1ff11521f305>                                2   0%
         - mapcar                                                   2   0%
          - #<compiled 0x1ff115a49af1>                              2   0%
           - find-buffer-visiting                                   2   0%
            - file-truename                                         2   0%
             - file-truename                                        1   0%
              - file-truename                                       1   0%
               - file-truename                                      1   0%
                - file-truename                                     1   0%
                 - file-truename                                    1   0%
                    file-truename                                   1   0%
     - lsp-show-xrefs                                               1   0%
      - xref--show-defs-buffer                                      1   0%
       - xref-pop-to-location                                       1   0%
          run-hooks                                                 1   0%
   - next-line                                                     20   4%
    - line-move                                                    20   4%
     - line-move-partial                                           19   4%
      - pos-visible-in-window-p                                     2   0%
       - eval                                                       2   0%
          flycheck-mode-line-status-text                            2   0%
      - posn-at-point                                               1   0%
         file-remote-p                                              1   0%
+ ...                                                              37   8%
+ timer-event-handler                                               3   0%
+ redisplay_internal (C function)                                   1   0%

但我觉得卡主,不一定是 CPU 处理不过来,可能是在等 IO…

根据这个 profile 结果,然后人肉看了下 lsp-mode 的部分代码。发现 lsp-find-definition 它本身就是个 blocking 的操作,它会调用 lsp-find-locations,lsp-find-locations 会调用 lsp-request 并等待它返回结果。

在 lsp-find-definition 的地方加了个 measure-time 的东西来打印时间,看到是这个函数确实会 block 很久。慢的时候需要 3s 左右,快的时候 0.x 秒。

(defmacro measure-time (&rest body)
  "Measure the time it takes to evaluate BODY."
  `(let ((time (current-time)))
     ,@body
     (message "%.06f" (float-time (time-since time)))))

(cl-defun lsp-find-definition (&key display-action)
  "Find definitions of the symbol under point."
  (interactive)
  (measure-time (lsp-find-locations "textDocument/definition" nil :display-action display-action)))

我怀疑它就是这样设计的… 有木有熟悉这块的老师来分析一下。

另外,我看到 lsp-mode 的代码里面确实有 xxx-async 的函数,它的 hover/highlight 等功能是调用这个函数,可能这些功能是非阻塞的。但 lsp-find-definition 是阻塞。

这样设计应该是为了保持一致性,免得按了跳转之后又去做其他事情了然后突然给你跳走了

其实可以按照snails的tick-id那样设计,如果异步返回结果id不对,就抛弃计算。

这个操作是阻塞式的从设计上讲是合理的,问题是在于为什么会隔很久返回。

1 个赞

我觉得“阻塞这个设计本身”不够健壮,不是最优的设计方案。因为它要依赖外部它控制不了的系统,并且 lsp spec 里面它也没规定这个请求就一定要在多久时间内返回。

我用它的 async 函数实现了一个简单的非阻塞版本,然后自己体验了一下,遇到了一个大家都能想到的问题:触发 find-definition 后,做了一些操作改变了 UI 布局,当 find-definition 处理完成后,它会突然改变我的 UI 布局,给我一个“惊喜” :neutral_face:

所以类似这样的策略「其实可以按照snails的tick-id那样设计,如果异步返回结果id不对,就抛弃计算。」就很重要。或者是一些特定的操作能够主动的 cancel 这个请求,那也挺好的。

(defun lsp-find-definition-no-wait ()
  (interactive)
  (lsp-request-async
   "textDocument/definition"
   (append (lsp--text-document-position-params) nil)
   (lambda
    (loc)
    (if (seq-empty-p loc)
        (lsp--error "Not found for: %s" (or (thing-at-point 'symbol t) ""))
      (lsp-show-xrefs (lsp--locations-to-xref-items loc) nil nil)))))

(global-set-key (kbd "C-c .") 'lsp-find-definition-no-wait)

但不得不说,如果 lsp server 反应快一点,用户体验基本是 OK 的。