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

From: Pip Cet <[email-masked]>
Date: Thu, 12 Jun 2025 18:37:58 +0000

"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)))

quittable, as I naively expected it to be.  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)))

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.

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).

> 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

>> 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.

Pip