emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Concurrency via isolated process/thread


From: Ihor Radchenko
Subject: Re: Concurrency via isolated process/thread
Date: Mon, 10 Jul 2023 11:30:10 +0000

Eli Zaretskii <eliz@gnu.org> writes:

>> And how frequently are case-table and display-table changed? AFAIK, not
>> frequently at all.
>
> Why does it matter?  Would you disallow programs doing that just
> because you think it's infrequent?

Not disallow. But the programs setting case-table and display-table will
block. That will be only a fraction of programs.

> Processing of various protocols frequently requires to do stuff like
>
>    (with-case-table ascii-case-table
>      DO SOMETHING...)
>
> because otherwise some locales, such as the Turkish one, cause trouble
> with case conversion of the upper-case 'I'.  So changing the
> case-table of a buffer is not such an outlandish operation to do.

Yet, a lot of Elisp programs do not need to deal with case tables.

And if we talk about this particular use case, may we simply allow
let-binding case tables?

>> May you please explain a bit more about the situation you are referring
>> to? My above statement was about consing, not GC.
>
> Consing can trigger GC if we detect the memory-full situation, see
> alloc.c:memory_full.

I see.
AFAIU, we can then raise a flag that GC is necessary, so that other
threads will stop next time they reach maybe_gc, until GC is complete.

>> For GC, as I mentioned earlier, we can pause each thread once maybe_gc()
>> determines that GC is necessary, until all the threads are paused. Then,
>> GC is executed and the threads continue.
>
> If all the threads are paused, which thread will run GC?

I imagine that a dedicated thread will be used for GC. It will do
nothing until a signal, acquire a global lock, perform GC, release
the lock, and continue waiting.

>> If an async thread want to request redisplay, it should be possible. But
>> the redisplay itself must not be done by this same thread. Instead, the
>> thread will send a request that Emacs needs redisplay and optionally
>> block until that redisplay finishes (optionally, because something like
>> displaying notification may not require waiting). The redisplay requests
>> will be processed separately.
>
> Ouch! this again kills opportunities for gains from concurrent
> processing.  It means, for example, that we will be unable to run in a
> thread some processing that affects a window and reflect that
> processing on display when it's ready.

We may or may not be able to get async redisplay, depending on Emacs
display implementation:

1. A thread that requests redisplay will send a signal request to
   perform redisplay.
2. Another thread, responsible for redisplay, will retrieve the signal
   and process it. That thread may or may not do it asynchronously.

AFAIU, it is currently not possible to redisplay asynchronously.
But if we can somehow make redisplay (or parts of it) asynchronous, we
will just need to make adjustments to redisplay thread implementation.
Other async threads will not need to be changed.

I just do not know enough about redisplay to dive into possibility of
async in it. I am afraid that this discussion is already complex enough,
and discussing redisplay will add yet another layer of complexity on top.

>> We can make reading input using similar idea to the above, but it will
>> always block until the response.
>
> This will have to be designed and implemented first, since we
> currently have no provision for multiple prompt sources.

>> For non-blocking input, you said that it has been discussed.
>> I do vaguely recall such discussion in the past and I even recall some
>> ideas about it, but it would be better if you can link to that
>> discussion, so that the participants of this thread can review the
>> previously proposed ideas.
>
>   https://lists.gnu.org/archive/html/emacs-devel/2018-08/msg00456.html

Thanks!
After re-reading that discussion, it looks like the most discussed idea
was about maintaining a queue of input requests and prompting users at
appropriate times. This is similar to what I proposed - async threads
should query to process input and then the input order and time will be
decided elsewhere.

The rest of the discussion revolved around the fact that

1. Input may involve non-trivial Elisp that requires thread context - so
   the input processing must know the context of the original thread.
   Ergo, thread itself may need to be involved to process input, not
   some other thread.

   For async threads, it means that input may need to pause the whole
   async thread until scheduler (or whatever code decides when and where
   to read/display input or query) decides that it is appropriate time
   to query the user.

   We may also need to allow some API to make other thread read input
   independently while the current thread continues on. But that's not a
   requirement - I imagine that it can be done with `make-thread' from
   inside the async thread.

2. There should be some kind of indication for the user which thread is
   requesting input:

   - Emacs may display that intput is pending in modeline indicator (aka
     "unread" notifications); or Emacs may display upcoming query in the
     minibuffer until the user explicitly switches there and inputs the
     answer.
     
   - When input query is actually displayed, there should be an
     indication which thread the query belongs to - either a thread name
     or an appropriately designed prompt.

     Possibly, prompt history for the relevant thread may be displayed.

3. Threads may want to group their prompts into chunks that should be
   read without interrupt, so that we do not mix two threads like
   "Login for host1: "->"Password for host1: " and "Login for host2:
   "->"Password for host2: ".

>> > What about changes to frame-parameters?  Those don't necessarily
>> > affect display.
>> 
>> But doesn't it depend on graphic toolkit?
>
> Not necessarily: frame parameters are also used for "frame-local"
> variables, and those have nothing to do with GUI toolkits.

I see.
Then, when an async threads modifies frame parameters, it should be
allowed to do so. However, during redisplay, redisplay code should block
frame parameters - it should not be allowed to change during redisplay.

>> >>    This means that any code that is using things like
>> >>    `save-window-excursion', `display-buffer', and other display-related
>> >>    staff cannot run asynchronously.
>> >
>> > What about with-selected-window? also forbidden?
>> 
>> Yes.
>
> Too bad.

Well. In theory, this is a problem similar to set-buffer - we need to
deal with the fact that Emacs assumes that there is always a single
"current" window and frame.

I assume that this problem is not more difficult than with set-buffer.
If we can solve the problem with global state related to buffer, it
should be doable to solve async window/frame setting. (I am being
optimistic here)

>> >>    Async threads will make an assumption that
>> >>    (set-buffer "1") (goto-char 100) (set-buffer "2") (set-buffer "1")
>> >>    (= (point) 100) invalid.
>> >
> ... 
>> Hmm. I realized that it is already invalid. At least, if `thread-yield'
>> is triggered somewhere between `set-buffer' calls and other thread
>> happens to move point in buffer "1".
>
> But the programmer is in control!  If no such API is called, point
> stays put.  And if such APIs are called, the program can save and
> restore point around the calls.  By contrast, you want to be able to
> pause and resume threads at will, which means a thread can be
> suspended at any time.  So in this case, the programmer will be unable
> to do anything against such calamities.

Right. So, we may need to store per-thread history of PT, BEGV, and ZV
for each buffer that was current during thread execution.

>> >> > What if the main thread modifies buffer text, while one of the other
>> >> > threads wants to read from it?
>> >> 
>> >> Reading and writing should be blocked while buffer is being modified.
>> >
>> > This will basically mean many/most threads will be blocked most of the
>> > time.  Lisp programs in Emacs read and write buffers a lot, and the
>> > notion of forcing a thread to work only on its own single set of
>> > buffers is quite a restriction, IMO.
>> 
>> But not the same buffers!
>
> I don't see why not.

Of course, one may want to run two async threads that modify the same
buffer simultaneously. Not all the Elisp code will need this, but some
may.

And it is indeed a restriction. But I do not see that it should be the
restriction that stops us from implementing async support, even if we
cannot solve it.

> But that's the case with most Emacs Lisp programs: we rarely try too
> hard to make functions pure even if they can be pure.  What's more
> important, most useful programs cannot be pure at all, because Emacs
> is about text processing, which means modifying text, and good Emacs
> Lisp programs process text in buffers, instead of returning strings,
> which makes them not pure.

I am not sure if it is a good aim to design async threads in such a way
that _any_ (rather than explicitly designed) Elisp can work
asynchronously. It would be cool if we could achieve this, but I do not
feel like most of Elisp actually require async.

Yes, most of Elisp is about text processing. But when we really need to
utilize asynchronous code, it is usually not about reading/writing text
- it is about CPU-heavy analysis of text. This analysis is what truly
needs async threads. Writing back, if it is necessary, may be separated
from the analysis code or even done in separate buffer followed by
`buffer-swap-text' or `replace-buffer-contents'.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>



reply via email to

[Prev in Thread] Current Thread [Next in Thread]