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

From: Stefan Monnier <[email-masked]>
Date: Fri, 13 Jun 2025 11:37:51 -0400

Ladies and gentlemen, please let's try and keep this civil.
Let's remember that misunderstandings and implicit assumptions are part
of human life.

        Stefan

Daniel Colascione [2025-06-13 06:35:40] wrote:

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