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

From: Daniel Colascione <[email-masked]>
Date: Fri, 13 Jun 2025 06:35:40 -0700

On June 13, 2025 4:34:06 AM PDT, Eli Zaretskii <[email-masked]> wrote:
>> From: Daniel Colascione <[email-masked]>
>> Cc: [email-masked],  [email-masked],  [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?
>
>All of them.

Do you want a mechanism that can interrupt all Lisp programs or not? I have such a mechanism.

>> 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.
>
>So because we currently cannot interrupt some programs we should give
>up the ability to interrupt all of them?

My proposal allows us to interrupt all Lisp programs. I don't know how many times I have to say this to get the point across.

> Please be serious.

Please engage with what I'm saying and at least try to say things that make internal sense.

>Arguments like the above are a red herring, and don't help advancing
>this discussion towards some kind of agreement.

You just said above that we should consider the interruptabiltity of all Lisp programs. I cited examples of Lisp programs. That means these Lisp programs are relevant to the conversation, as they, being Lisp programs, belong to the category of all Lisp programs.

>Do you want the branch merged at some point? 

Do you want bugs fixed and behaviors improved at some point?

> Then please drop the
>attitude and start participating in the discussion seriously.  You
>understand very well what I meant above, even though it is somewhat
>vague. 

Actually, I have no idea what you mean: you say things like you're not sure whether we can distinguish two C-g presses from a double C-g press. There are the same thing. It's like asking whether we can distinguish #00f from #0000ff.

> We all know what Emacs can and cannot do today, so I allow
>myself not to write too many well-known details.
>
>Let's consider the current capabilities of interrupting Lisp programs
>as base line, and try to maintain that base line, if not improve on
>it.  Okay?  

I'm suggesting being able to interrupt all Lisp programs. Not all Lisp programs can be interrupted today. I have explained in multiple places how that is to be done. I am happy to explain further if you give me some substantial technical questions about the mechanisms involved.

When Lisp writes (while t (some-event-reading-function)), programmer intent is not clear. It's not clear because C-g is overloaded and can be either an event or a quit command. On the one hand, the Lisp is running Lisp and we have a general contract that Lisp can be interrupted with C-g. On the other hand, Lisp is asking to read an event, and C-g can be an event. We have to resolve the ambiguity SOMEHOW. Right now, Emacs resolves the ambiguity in an inconsistent and ad-hoc manner. If the some-event-reading-function above is read-event, we resolve the ambiguity in favor of quitting. If instead some-event-reading-function is read-key-sequence, we resolve the ambiguity in favor of event reading. This inconsistency is real, confusing, and illogical. In my proposal, we resolve the ambiguity in favor of event reading and use multiple C-g presses to indicate user intent to quit instead of provide input. I don't believe there's a practical, real world use case in which a single C-g will be insufficient, but the ability to press C-g multiple times will be there regardless. If you'd like to configure your Emacs to resolve the ambiguity in favor of quitting, you can do that by changing one variable. Either way, behavior will be consistent among all Lisp functions that read events.

Would you please engage substantively with the previous paragraph instead of calling it a distraction and then raising points germane to exactly what the previous paragraph covers?

Again, nobody is talking about making users count C-g. Nobody is talking about requiring multiple C-g presses in any real world case. The change I'm talking about will make Emacs more consistent, and I've yet to see anything in the real world it would break.

My change also makes Emacs safer, because in my model, you *can* break out of (while t (read-key-sequence)) and in master today you cannot. There will be *one* uniform and consistent (and easy to understand) model for how we address the *inherent* ambiguity about what control-g on a keyboard means.

Or does it still not satisfy your intentionally-pedantic
>interpretation of what I write?

I wouldn't have to be pedantic if you would be clear about what you want.

>> > If we _are_ in agreement about that, 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.
>
>Please reconsider your responses with a more serious and cooperative
>attitude.

Then make some sense. You talk about requirements. I explain how we meet those requirements. That's pedantry?

>> >  . 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.
>
>Why "only"?
>
>Having the behavior vary depending on whether the program does or
>doesn't call read-event will bring inconsistency, something we want to
>avoid (and which I think you argued against).

It is logically impossible to combine two things:

1) all Lisp programs can be interrupted with a single C-g

2) a Lisp program can read a C-g as an event.

This isn't pedantry. It's foundational logic. Demanding a solution that satisfies both constraints is impossible. It would require reading the user's mind.

>> 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.
>
>Not useful.  I didn't mean that, and you know it.

You just said at the start of this email that you're considering the interruptabiltity of all Lisp programs. I have a scheme to make all of them interruptible. Nobody else has proposed one.

>> > Are there other alternatives?
>> 
>> Get in a time machine, go back 40 years and stop overloading C-g?
>
>Even less useful.  Again, do you want this branch to be merged any
>time soon?  Because I'm this close to losing my patience.  Life's too
>short to waste it on "arguments" such as this one.
>

And I'm losing patience with objections that contradict themselves and fail to address my points.

>> > 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?
>
>Why is that relevant?

If you'd like to cooperate seriously, I'm happy to do so provided you do so as well, and part serious cooperation is answering clarifying technical questions instead of questioning their relevance.

So, yes or no?

> I asked about read-event, not
>read-key-sequence.  Can we first discuss the read-event case?  Once we
>are done with that, we can move to other cases.

You just said above you want to consider the interruptabiltity of all Lisp programs. Now you're calling the interruptabiltity of some Lisp programs irrelevant. Which is it?

It's important to consider the input model as a whole. I don't think we can get to a good place by focusing on one function at a time and be wilfully oblivious about whether different functions together form a coherent whole.

>> 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 inhibit-quit is bound to a non-nil value, the program cannot be
>quit, and that's a feature.

This seems like both a contradiction and poor UX: you want some Lisp programs to be interruptible and others not. Why is it a feature that some Lisp programs cannot be quit? I thought you wanted to talk about all Lisp programs being quittable. Why is it desirable that Emacs be left in an unrecoverable state?

In my proposal, users will be able to interrupt *any* program of the form (let ((inhibit-quit t)) (while t (anything))) by pressing C-g enough times to override the inhibit-quit. Emacs already provides this feature, but only (AFAICT) on primary TTYs.  

If this ability to break out of a loop wrapped in inhibit-quit is bad, should we remove the existing force-quit feature? Yes or no, please.

My proposal is a generalization of a feature we've had for decades. It is not a brand new concept.

Is it a bug to allow these programs to be interrupted? Yes or no. If yes, why isn't the existing force quit feature being removed? If not, why is being unable to quit some Lisp programs a "feature"?

You say I know what you mean. I'm really not sure I do, because from where I'm sitting, you're not answering questions that would resolve the contradictions in what you're saying.

> Why are we discussing this?

Because we're talking about interrupting Lisp programs. That's the subject of the whole conversation.

>
>> 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?
>
>Again, let's discuss the read-key-sequence case after we are done with
>the read-event case.

How can we consider what to do with read-event without thinking about how it fits into the broader picture?

>> >> > 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?
>
>I usually expect a single C-g to quit.  If it doesn't help, I can
>press C-g several times, I'm not sure I count them.

In my proposal, on every real world case, C-g returns you to the command loop when you want to go there and lets Lisp read C-g as an event when you want to do that. You can construct buggy or pathologically written programs that you can't exit with a single C-g because the meaning of that keystroke is overloaded. You can still get back to the main loop in these rare cases by pressing C-g repeatedly. You don't have to count them. You just keep pressing C-g until you are back in the command loop, just like today's force quit feature.

>> 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.
>
>See above.
>
>> We already have a force quit mechanism that kicks in at N=3.  How often
>> do you activate it?
>
>I never saw it at work, except on TTY frames. 

We can make it work everywhere. Is that not an improvement?

> But then Windows
>doesn't have SIGIO, it emulates that.  Maybe that's the reason.

SIGIO isn't the reason. The reason force quit doesn't work is the way it's implemented. We can implement it better. That's my proposal, if you'd like to consider it in more detail.

>> >> 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.
>
>Sorry, I don't understand.  If N = 1, what will read-event do when the
>user presses C-g while inside read-event?  

If N=1, it will quit after a single C-g. Specifically, it will raise a quit signal. It will raise this signal instead of returning normally. It will not return it as C-g to Lisp. If N=1, all the Lisp level input reading functions, including read-key-sequence, will interpret a single C-g as quit.

> Will it return the input
>event C-g, or will it quit?  I thought your changes make read-event
>always return the input event, was I mistaken?

You're asking about the N=1 case.  I'm not proposing that N=1 be the default.

For all values of 1 <= N <= M, I'm specifically proposing:

When inhibit-quit is nil:

#quits in [1,N-1]: event reading functions interpret C-g as an event called \?C-g, aka the number 7. They return this event without quit-flag set.

#quits in [N, infinity): event reading functions interpret C-g as a quit. They do not return. They end by calling Fsignal with quit error symbol.

When inhibit-quit is non-nil:

#quits in [1,N-1]: same as above 

#quits in [N,M-1]: return \?C-g with quit-flag set

#quits in [M, anything): exit nonlocality by calling Fsignal with quit error signal, effectively ignoring inhibit-quit. Print a message saying we've done so.

If debug-on-quit is set, we enter the debugger when the above table says the word "Fsignal".

If throw-on-input, we raise quit on any input event whatsoever, never returning to Lisp, no matter the value of #quits, N, or M.

My proposal, by the way, does not change the meaning of quit-flag.

If you find the above proposal unacceptable, can you please point to a specific behavior it encodes and talk about what you'd rather it do differently?

Assume we can reliably detect #quits, please. I'm happy to talk about *how* we detect repeated C-g, but the mechanism by which we do is independent of rhe policy we want.

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

In the exceedingly rare case that it is important to allow one C-g instead of C-g C-g C-g to break out of an read-event or read-key-sequence loop (and I have never seen one not deliberately constructed) users can customize N to be one. I anticipate exactly zero people going that, but if you want the knob, we have it.

> We
>> > already have --enable-checking, which changes how some primitives
>> > work, in a very real sense.  

I'm not aware of any interface contracts that checking changes. What might those be?

> 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.
>
>No, people need not expect the code to operate the same under those
>special debugging-oriented values.  We already have such features:
>debug-on-error = t might well cause some command cease working
>normally, and similarly condition-case-unless-debug

Because I don't think this debugging mode is needed in any real world scenario. Nobody I've seen infloops on read event outside a specially crafted scenario.

But if you want the knob to enable it, the knob is there in my proposal. Set N=1. If you set N=1, then *both* read-event and read-key-sequence will quit the first time you press C-g 

>So I think you are bothered by a problem that doesn't need to be
>solved.
>
>> >> >>> 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?
>
>I don't understand what you are saying here.  Yes, there's no logical
>difference between these, which is why I'm saying that reliably
>detecting "double C-g" is a challenge.  We already have that with
>double-mouse.  The difference between double-mouse and "double C-g" is
>that with the latter you cannot afford low reliability: when you want
>to quit, you want to do it immediately, because the runaway operation
>you want to interrupt could be harmful.

I'm honestly not sure what reliability problems you're talking about. We count the C-g key presses since we last entered the command loop and since T milliseconds ago. If you say T to be a month and N=2 then if you're in a read-event or read-key-sequence loop, press C-g once, suspend your computer for a day, and press C-g again, we'll interpret the second C-g as a quit and break out of the loop.

Can you please give me some specific and concrete scenarios you have in mind that might clarify these reliability issues you're talking about?

>> > 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.
>
>It's too late to define a separate key _instead_ of C-g.  We could
>define a separate key _in_addition_ to C-g, but that doesn't solve the
>problem: people have C-g hardwired into their muscle memory, and it
>will take a lot of time for them to re-learn.  Meanwhile, we get bug
>reports about C-g not working like it did before.

No, we didn't get a bug report. We got a specially constructed program that exercised a beneficial behavior change and called it a bug. I've yet to see a real world problem. The person who constructed that program complained that a loop that could be quit before was unquittable now. I am trying to explain that it will, in fact, be quittable.

>So minimizing the behavior changes is still a requirement, even if we
>add another key.  And that's before we even consider what other key
>could serve that purpose, which is not a trivial problem to solve
>portably

I'm not talking about adding another quit key. That's not the right solution. I don't see a real world scenario in which multiple C-g doesn't work well enough, and we have, what, decades of experience with this mechanism on TTYs? Why is taking the force quit mechanism, fixing its reliability issues, and generalizing it to GUIs suddenly a problem?

>> 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.
>
>That's possible, but it is not the best alternative IMO, for the
>reasons I explained.

I've yet to see you propose a different solution. All I've seen is resistance to change whether it's beneficial or not.

My proposal allows you to interrupt any Lisp program. In every real world scenario, you can interrupt with one C-g. In pathological cases, you can interrupt by pressing C-g multiple times. This mechanism is reliable.