Subject: Re: bug#78737: sit-for behavior changes when byte-compiled

From: Daniel Colascione <[email-masked]>
Date: Thu, 12 Jun 2025 11:48:50 -0700

Pip Cet <[email-masked]> writes:

> "Eli Zaretskii" <[email-masked]> writes:
>
>>> Date: Thu, 12 Jun 2025 13:58:51 +0000
>>> From: Pip Cet <[email-masked]>
>>> Cc: Eli Zaretskii <[email-masked]>, [email-masked], [email-masked]
>>>
>>> I'd say breaking (read-event) called in a loop is bad enough, because
>>> how else are you supposed to start developing code which uses it?
>>
>> Maybe this regression should be fixed, then.
>
> I agree it should be, if we can agree it is a regression; the other
> issues I see are SIGUSR2 handling (which seems fixable) and the question
> of whether saving unhandled events in unread-command-events is always
> the right choice.
>
>>> I think this discussion is concerned too much with what existing code
>>> will break if we change quit not to quit, not enough with how much more
>>> difficult it will be to develop code if we do, and not at all, so far,
>>> with what the advantages of handling quit in Lisp (if Lisp decides to
>>> handle it at all) are.
>>>
>>> C-g isn't an input event in the same way that kicking someone in the
>>> shin is not a dance move.  I want it to kick Emacs in the shin, and
>>> break out of bad Lisp code, in *more* situations than it does now.
>>
>> Please describe the situations where you'd like it to throw to top
>> level and it doesn't now.
>
> One situation for ordinary quits; three for force-quit.
>
> Situation 1:
>
> I'd like read-event, when called while inhibit-quit is t, to report
> quits by setting quit-flag in addition to returning quit_char: it'll
> simplify the C code, and it would make
>
> (while t
>   (let ((inhibit-quit t))
>     (read-event)))

I strongly disagree.  read-event should read an event.
Setting quit-flag by side effect when it happens to read one key and not
others makes the interface less regular and understandable.

read-key-sequence is the high-level function.  read-event is the
low-level function.  It makes zero sense for the high-level function to
report a key event as a low-level event and for the low-level function
to do special non-local flow control in response to that event.

> quittable, as I naively expected it to be.

The naive expectation is that this function do its job.

> The old behavior would
> remain available, but require an extra let binding.
>
> Note that this isn't
>
> (let ((inhibit-quit t))
>   (while t
>     (read-event)))

Then fix read-key-sequence, which acts the same way.

> While I'd like to change the C code to make this second case
> force-quittable, I see no way to perform an ordinary quit for this code.
> The reason I mention it is that removing
>
>       /* If we report the quit char as an event,
> 	 don't do so more than once.  */
>       if (!NILP (Vinhibit_quit))
> 	Vquit_flag = Qnil;
>
> changes behavior for both loops: the first becomes quittable, the second
> becomes force-quittable.

There is no reason for these loops to behave differently.  If we want to
make both force-quittable, as I described in
https://lists.gnu.org/archive/html/bug-gnu-emacs/2025-06/msg00629.html,
but an ordinary press of C-g should not cause a quit when Lisp has
explicitly asked to read an event by calling a function whose job it is
to read events.

> Situation 2:
>
> (while t
>   (read-key-sequence ""))
>
> It'd be nice for this situation to be force-quittable; I don't see why
> it shouldn't be, even though what I implemented is a bit of a hack...
>
> Situation 3:
>
> Several force-quits in the same session.  Reset force_quit_count to 0
> once it reaches 3.  I've seen force_quit_count reach higher values than
> 3 (there was no regular quit in between force quit attempts).

Get rid of force_quit_count entirely and just detect (by writing into a
ring buffer) whether we've received N quits in the last T milliseconds.
That'll work the same way regardless of how quits gets detected.  We can
change N and T freely.

On your Emacs, you can set N to one and T to zero.

>> Also, can this behavior be optional, like debug-on-error and friends
>> are?  Not everyone debugs Lisp code all the time, so we definitely can
>> have an "easy-break-out" feature that is by default off.

> Absolutely.  We could easily make it customizable whether read-event
> sets quit-flag after a quit:
>
> 1. never
> 2. only when !inhibit-quit
> 3. always

We can customize thresholds for general behavior, but I don't think we
should not have preferences that alter the operation of fundamental
Emacs primitives.  You couldn't add a preference that made if regard 0
as well as nil as false, would you? 

>>> Maybe a compromise would be to continue the arms race and downgrade C-g
>>> to normal input, C-g C-g C-g to a quit, and require even more C-g's for
>>> a force-quit?
>>
>> That's also possible, though less desirable: counting C-g presses when
>> you are desperate is not easy and we cannot rely on users to do that
>> reliably.
>
> And we'd need a way to detect when a quit is handled (however we define
> "handled") so we could reset force_quit_counter.  Not a trivial change.

You don't.  You just upgrade any quit that happens under the N and T
threshold above.