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

From: Daniel Colascione <[email-masked]>
Date: Fri, 13 Jun 2025 00:28:45 -0700

Eli Zaretskii <[email-masked]> writes:

>> From: Daniel Colascione <[email-masked]>
>> Cc: Eli Zaretskii <[email-masked]>,  [email-masked],
>>   [email-masked]
>> Date: Thu, 12 Jun 2025 11:48:50 -0700
>> 
>> Pip Cet <[email-masked]> writes:
>> 
>> > 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.
>
> We should start by agreeing that the capability of interrupting a
> running Lisp program is a real need.  Are we in agreement about that?
> If not, let's first hear the arguments why not.

Which Lisp programs? One that calls (while t (read-event))?  One that
calls (let ((inhibit-quit t)) (while t (read-event))?  How about one
that calls (while t (read-key-sequence ""))?  How about one that calls
(let ((inhibit-quit t)) (while t (read-key-sequence ""))?  If you adopt
the tenet that Lisp programs always be uninterruptible, _something_ has
to change from the present, because 3/4 of my examples above cannot
be quit.

(Today's force quit doesn't count: it doesn't practically work outside
primary TTY.)

> If we _are_ in agreement about that, we should discuss how this should
> be possible if read-event (and perhaps others?) return C-g instead of
> raising quit-flag.  The alternatives mentioned until now are:
>
>  . restore the old behavior, whereby C-g interrupts read-event

Does not satisfy the "lisp programs always be
interruptible" requirement.

>  . have a variable that, if set, will restore the old behavior in

Same as above.

>    read-event and other affected primitives, to be interruptible by a
>    single C-g

Same as above.

>  . make two C-g presses "in quick succession" set quit-flag, IOW
>    "C-g C-g" will have the same effect as C-g previously

Only for read-event.

If you want to adopt a principled stance that every Lisp program be
interruptible, you necessarily, as a matter of logic, regard multiple
behaviors of current Emacs to be bugs worth fixing.

> Are there other alternatives?

Get in a time machine, go back 40 years and stop overloading C-g?

>> 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.
>
> I don't think there's disagreement on that level.  So let's drop this
> kind of arguments, because they are not useful for this discussion.
> The problem we face is how to allow Lisp code to be quittable.  A Lisp
> program that calls read-event is not different from a Lisp program
> calling any other function, so we still need such programs to be
> quittable somehow.  Let's discuss how best to do it, okay?

Yes or no: should (while t (read-key-sequence)) be quittable?

If yes, maintaining today's behavior isn't an option.  There are plenty
of other Lisp programs that cannot be quit --- even (let ((inhibit-quit
t)) (while t)) cannot be quit!

If no, what is the problem with cleaning up Emacs by regularizing how we
treat C-g as an event versus some kind of longjmp?

>> > 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.
>
> This is the last alternative I described above.  It is IMO less
> desirable, because it is not always easy to press C-g twice quickly,
> for whatever reasons.  It is even less desirable to define "quick
> succession" in terms of time, because timings can differ a lot
> depending on the free computing resources and the CPU power in
> general, so determining the best default values will be very
> challenging, to say the least.

I don't see the ambiguity being a realistic problem.  How often do you
press C-g three times while a single command is running?

We're not talking about, say, six times in multiple rounds of M-x this,
select that, deactivate mark over here.  Those are multiple commands.
In between multiple commands, a C-g will cause a keyboard-quit and
you'll regain control over Emacs.  I'm asking, during _one_ command, how
many times you typically press C-g and _don't_ mean it as a quit.

We already have a force quit mechanism that kicks in at N=3.  How often
do you activate it?

>> On your Emacs, you can set N to one and T to zero.
>
> If read-event still returns a keyboard input event when C-g is
> pressed, I don't see how N = 1 would work.  Can you describe how it
> would work?

It wouldn't.  Such a setting would prevent read-input returning C-g.
That shouldn't be the default.  Maybe some people would customize the
setting to make Emacs behave this way.  I would not.

>> >> 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? 
>
> Why not?  If it helps debugging, we could definitely do that.  We
> already have --enable-checking, which changes how some primitives
> work, in a very real sense.  As long as the feature is for debugging
> Emacs, anything useful goes, IMO.

Because when you add a user option, people expect code you write to
operate under any value of that option, increasing implementation
complexity because you have to account for the _possibility_ someone
might operate in a certain way.  Complexity is insidious, and the
solution to a technical problem is rarely to add yet another technical
knob nobody is going to realistically touch but for which logic
must account.

>> >>> 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.
>
> I'm skeptical that we will be able to count C-g presses so as to
> reliably differentiate between double or triple press and two or three
> separate C-g presses.

There is logically no difference between these two concepts.  A double
keypress *is* two keypresses in a certain window of time.  What other
concept could the combination of the words "double" and "press" convey?

> Different machines and OSes, and different
> system loads, can make it nigh impossible to do reliably.  And that's
> _bad_, because when I want something interrupted right away, I don't
> want or even cannot try again and again and again until it works.

Then define a separate key that means _only_ quit and that cannot be
bound to a regular command.  C-g can't be that command without breaking
existing code.

Treating repeated C-g presses made in a reasonable window of time within
the scope of a single command in such a way that we're almost certainly
not confusing this series of keystrokes with C-g-as-command input solves
the problem.