Subject: Re: bug#75922: CPU hogs with pgtk

From: Pip Cet <[email-masked]>
Date: Wed, 29 Jan 2025 17:29:43 +0000

"Nicolas Sarlin" <[email-masked]> writes:

> On Wed, 29 Jan 2025 at 14:06, Eli Zaretskii <[email-masked]> wrote:
>>
>> > From: Nicolas Sarlin <[email-masked]>
>> > Date: Wed, 29 Jan 2025 12:05:16 +0100
>> >
>> > I'm using lsp-mode with corfu on the emacs-30 branch, and I get extremely
>> > frequent CPU hogs (CPU runs at 100% for a few seconds) while typing
>> > code. I report this as an emacs bug because it seems to only occurs when
>> > emacs is compiled with pgtk. If I rebuild emacs with "make bootstrap
>> > configure=default" the bug disapears.
>> >
>> > Here is a profiler record:
>> >        11152  91% - corfu--post-command
>> >        11152  91%  - corfu--exhibit
>> >        11043  90%   - corfu--update
>> >        10891  88%    - corfu--recompute
>> >        10891  88%     - corfu--filter-completions
>> >        10891  88%      - completion-all-completions
>> >        10891  88%       - completion--nth-completion
>> >        10891  88%        - seq-some
>> >        10891  88%         - seq-do
>> >        10891  88%          - mapc
>> >        10891  88%           - #<byte-code-function AC1>
>> >        10891  88%            - #<byte-code-function AD0>
>> >        10891  88%             - lsp-completion-passthrough-all-completions
>> >        10891  88%              - #<byte-code-function 4B5>
>> >        10891  88%               - #<byte-code-function 4DE>
>> >        10888  88%                - lsp-request-while-no-input
>> >        10888  88%                 - sit-for
>>
>> The profile says most of the time is spent in sit-for called from
>> lsp-request-while-no-input.  First, sit-for is not supposed to consume
>> CPU, because it's a waiting function.  Are you sure this profile was
>> taken when Emacs was hogging CPU?
>>
>> And second, lsp-mode is not part of Emacs, so if indeed the above is a
>> profile representative of high CPU load, I suggest to report this to
>> the developers of lsp-mode first, even though the problem appears only
>> in the PGTK build.
>>
>> Thanks.
>
> Hi, thank you for your answer.
> Yes, this profile was collected during an emacs freeze. It took me a
> few second (maybe 10) after `profiler-start` to trigger the freeze,
> then again a few seconds of freeze, then I ran `profiler-stop`
> directly after. During the freeze I had one CPU core constantly
> running at 100%.

My guess is it's this code in lsp-mode.el:

              (while (not (or resp-error resp-result (input-pending-p)))
                (catch 'lsp-done
                  (sit-for
                   (if expected-time (- expected-time send-time) 1)))
                (setq send-time (float-time))
                (when (and expected-time (< expected-time send-time))
                  (error "Timeout while waiting for response.  Method: %s" method)))

This code appears to want to reliquish the CPU for the next
lsp-response-timeout (default 10) seconds, so it calls sit-for with an
argument close to 10.0, or 1 if no timeout is set.

The behavior of sit-for is to return immediately if there is pending
input, ignoring the timeout argument.

In this case, this loop will use all of the CPU until whatever it is
actually waiting for happens, assuming a single "input event" (a
keypress is one, but certain kinds of mouse movement or a similar event
can also cause sit-for to exit immediately) has happened.

Some other places also in that file may work less than optimally when
sit-for returns immediately, also.

So this seems most likely like a bug there, and it may be triggered more
by pgtk/wayland because of such details as key repeat being handled by
the application rather than what used to be the X server.

I'm slightly surprised at your perf report, but at least the last part
doesn't seem inconsistent with that interpretation.

Once the bug is fixed, it would be great to hear how the documentation
of sit-for could be improved.  If that doesn't work, we might even want
to detect situations in which sit-for is called repeatedly with a
timeout argument even though no input was handled in the meantime.

Pip