Subject: bug#70221: [PATCH] New function `funcall-later`

From: Eli Zaretskii <[email-masked]>
Date: Sat, 06 Apr 2024 18:07:04 +0300

> From: Stefan Monnier <[email-masked]>
> Cc: [email-masked]
> Date: Sat, 06 Apr 2024 10:33:05 -0400
> 
> >> +DEFUN ("internal--run-pending-funcalls", Frun_pending_funcalls, Srun_pending_funcalls, 0, 0, 0,
> >> +       doc: /* Run the still pending `funcall-later'.  */)
> >> + (void)
> >> +{
> >> +  while (CONSP (pending_funcalls) || CONSP (pending_funcalls_r))
> >> +    if (CONSP (pending_funcalls))
> >> +      {
> >> +        Lisp_Object funcall = XCAR (pending_funcalls);
> >> +        pending_funcalls = XCDR (pending_funcalls);
> >> +        CALLN (Fapply, funcall);
> >> +      }
> >> +    else
> >
> > You are using CALLN here, whereas the previous implementation used
> > safe_calln.  Is that intended?  Calling Lisp in unsafe ways in that
> > place might not be a good idea.
> 
> I found it annoying that the debugger isn't brought up when a bug
> occurs in such delayed evaluations.  Note also the subsequent call to
> `timer-event-handler` uses `calln` rather than `safe_calln`, so I think
> my original use of `safe_call2` in commit 58555d8187f3425 was a mistake.

timer-event-handler uses condition-case-unless-debug, so I don't see a
problem with timers, and I think your use of safe_call2 was not a
mistake.

> I can't see any good reason why we'd need to protect the
> C code from non-local exits in `timer_check_2`.

Because it will prevent timers from being called?

> > You didn't even inhibit QUIT.
> 
> I'm torn on this one:
> 
> OT1H while the time at which the code is run is loosely specified, it's
> not really asynchronous: it's run the next time Emacs "waits", which is
> usually about the same time as `post-command-hook` (which does use
> `inhibit-quit`), so there doesn't seem to be a good justification for
> `inhibit-quit`.
> 
> OTOH when the `funcall-later` is performed from asynchronous code
> (e.g. from a timer), then running the delayed function call without
> `inhibit-quit` ends up "hoisting" that code outside of its original
> `inhibit-quit` context.
> 
> For users such as `track-changes.el`, `inhibit-quit` is not
> needed/desired.  But indeed when I use it in `futur.el` it's common to
> call `funcall-later` from process filters and timers.
> 
> Usually part of the purpose of `funcall-later` is to run the code in
> a different (dynamic) context, but maybe `funcall-later` should
> preserve `inhibit-quit`?

>From my POV, any code that runs from some background facility must
inhibit QUIT, because the user can type C-g at any moment.

> > As another difference between run-with-time and this mechanism, the
> > former took care of preserving deactivate-mark around the call, wheres
> > funcall-later doesn't
> 
> This is again because `run-with-time` is designed for the general case
> where the delay is not 0s, so the code will be run asynchronously,
> wheres `funcall-later` is designed for "different time but not async".

I don't think it's a wise approach.  You are converting several places
to using this new facility, so the prudent thing, in a program as
complex and devious as Emacs, is to keep compatibility even if our
hubris tempts us to think we are smarter.  Bitter experience has
taught me that we are not smarter, so I now prefer humility instead.

> > -- this is at least one difference that we
> > should document (assuming we want it).
> 
> AFAICT we don't document anywhere that `deactive-mark` is bound
> around timers, so I guess fixing that would be the best way to document
> the difference.

I mean where we explain the differences between zero-delay timers and
funcall-later.  We _will_ explain those, right?

> >> No: beside the fact that they are run at slightly different times
> >> (which might be OK), `funcall-later` doesn't return a handle you can
> >> then use to cancel the call.
> > This should be mentioned in the documentation.
> 
> How/where?

In the same place, see above, where we compare those two features.

> Maybe the doc can just say that it returns nil?
> Other than that, it would seem odd to write something like
> "There is no `cancel-funcall-later`".

Not "out of the blue", but in the context of explaining how it is
different (which is important for Lisp programmers to decide which one
to use in what situations), it is reasonable.

> > Thanks.  We should document this in the ELisp manual, and should
> > explain there the meaning of "at the next convenient time".  (I think
> > "convenient" here is very much misleading and thus inappropriate.)
> 
> That's the big question, really.  E.g. I currently use `funcall-later`
> in `track-changes.el` and in `futur.el`.  I use it because it's handy.
> But fundamentally those two need different things:
> 
> - `track-changes.el` uses `funcall-later` because it wants to delay the
>   execution in order to combine the current buffer modification with
>   potential soon-to-come buffer modifications.
> - `futur.el` uses `funcall-later` to "wake up" a piece of code that was
>   waiting for the current event.  In this case it uses `funcall-later`
>   not because it wants to delay execution but because it wants that
>   execution to be unaffected by the current dynamic scoping and it
>   doesn't want that execution to block the current execution.
> 
> So, if we were to change `funcall-later` so that the code gets run more
> promptly (e.g. via true concurrency [ let's pretend that this was
> actually possible and that the race-condition problems were magically
> solved somehow ]), it would be great for `futur.el` but would defeat the
> purpose for `track-changes.el`.
> 
> Current users of `funcall-later` (well: of its underlying mechanism):
> - We use it in `comp.c` to launch the "jit" compilation of a package.
> - We use it in `frame.c` and `terminal.c` to run
>   `(after-)delete-frame-functions` and `delete-terminal-functions` when
>   the current context does not allow running ELisp.
> 
> These are more like `futur.el` in the sense that the main purpose is
> to run the code in a different context, without blocking the
> current execution.

I don't think I disagree with what you write, but I fail to see how it
is relevant to the need to explain better what is that "next
convenient time" when the function will run.  In particular, I don't
see how the different uses of funcall-later affect that "next
convenient time".