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

From: Eli Zaretskii <[email-masked]>
Date: Fri, 13 Jun 2025 14:34:06 +0300

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

> 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?  Please be serious.
Arguments like the above are a red herring, and don't help advancing
this discussion towards some kind of agreement.

Do you want the branch merged 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.  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?  Or does it still not satisfy your intentionally-pedantic
interpretation of what I write?

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

Please reconsider your responses with a more serious and cooperative
attitude.

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

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

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

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

> 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.  Why are we discussing this?

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

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

> 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.  But then Windows
doesn't have SIGIO, it emulates that.  Maybe that's the reason.

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

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

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.

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.

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

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.

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