unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Concurrency via isolated process/thread
@ 2023-07-04 16:58 Ihor Radchenko
  2023-07-04 17:12 ` Eli Zaretskii
  2023-07-05  0:33 ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-04 16:58 UTC (permalink / raw)
  To: emacs-devel


[ Discussion moved from
https://debbugs.gnu.org/cgi/bugreport.cgi?bug=64423 ]

> > I feel that I am repeating an already proposed idea, but what about
> > concurrent isolated process that interacts with the main process?
> 
> (This stuff should not be discussed on the bug tracker, but on
> emacs-devel.)

Ok.
 
> If you mean what the async package does, then yes, this is a workable
> idea.  But it doesn't need to change anything in Emacs, and it has
> some downsides, like the difficulties in sharing state with the other
> process.

I had something similar in mind, but slightly different.

emacs-async package explicitly transfers pre-defined set of variables to
the async Emacs process and cannot transfer non-printable variables
(like markers or buffers).

But may it be possible to

1. Limit the async process memory to a small lexical subset of symbols
   (within a function).

2. Every time the async process needs to read/write a symbol slot
   outside its lexical scope, query the main Emacs process.

More concretely, is it possible to copy internal Elisp object
representations between Emacs processes and arrange mutability to query
the right Emacs process that "owns" the object?

The inter-process communication does not have to be asynchronous, but
may work similar to the existing thread implementation.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-04 16:58 Concurrency via isolated process/thread Ihor Radchenko
@ 2023-07-04 17:12 ` Eli Zaretskii
  2023-07-04 17:29   ` Ihor Radchenko
  2023-07-05  0:33 ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-04 17:12 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Date: Tue, 04 Jul 2023 16:58:49 +0000
> 
> emacs-async package explicitly transfers pre-defined set of variables to
> the async Emacs process and cannot transfer non-printable variables
> (like markers or buffers).
> 
> But may it be possible to
> 
> 1. Limit the async process memory to a small lexical subset of symbols
>    (within a function).
> 
> 2. Every time the async process needs to read/write a symbol slot
>    outside its lexical scope, query the main Emacs process.

If it queries the main process, it will have to wait when the main
process is busy.  So this is not really asynchronous.

> More concretely, is it possible to copy internal Elisp object
> representations between Emacs processes and arrange mutability to query
> the right Emacs process that "owns" the object?

This is software: anything's possible ;-).  But Someone™ needs to
write the code, like marshalling and unmarshalling of such objects
between two processes.  (We do something like that when we write then
load the pdumper file.)  There's more than one way of skinning this
particular cat.

> The inter-process communication does not have to be asynchronous, but
> may work similar to the existing thread implementation.

I wouldn't recommend designing anything by the example of Lisp
threads.  'Nough said.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-04 17:12 ` Eli Zaretskii
@ 2023-07-04 17:29   ` Ihor Radchenko
  2023-07-04 17:35     ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-04 17:29 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> 1. Limit the async process memory to a small lexical subset of symbols
>>    (within a function).
>> 
>> 2. Every time the async process needs to read/write a symbol slot
>>    outside its lexical scope, query the main Emacs process.
>
> If it queries the main process, it will have to wait when the main
> process is busy.  So this is not really asynchronous.

Sure. But not every asynchronous process would need to query the main
process frequently. As a dumb example:

(defun test/F (n) "Factorial"
  (declare (pure t))
  (let ((mult 1))
    (dotimes (i n)
      (setq mult (* mult i)))
    mult))

This function, if called asynchronously, will need to query input (N)
and later return the output. The main loop will not require any
interactions and will take most of the CPU time.

If the async process have a separate garbage collector, such process
will free the main Emacs process from allocating all the memory for i
and mult values at each loop iteration.

>> More concretely, is it possible to copy internal Elisp object
>> representations between Emacs processes and arrange mutability to query
>> the right Emacs process that "owns" the object?
>
> This is software: anything's possible ;-).  But Someone™ needs to
> write the code, like marshalling and unmarshalling of such objects
> between two processes.  (We do something like that when we write then
> load the pdumper file.)  There's more than one way of skinning this
> particular cat.

As the first step, I wanted to hear if there is any blocker that
prevents memcpy between processes without going through print/read.

>> The inter-process communication does not have to be asynchronous, but
>> may work similar to the existing thread implementation.
>
> I wouldn't recommend designing anything by the example of Lisp
> threads.  'Nough said.

IMHO, the main problem with threads is that they cannot be interrupted
or fire too frequently.

In the proposed idea, this will not be such a big deal - inter-process
communication is a known bottleneck for any asynchronous code and should
be avoided anyway.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-04 17:29   ` Ihor Radchenko
@ 2023-07-04 17:35     ` Eli Zaretskii
  2023-07-04 17:52       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-04 17:35 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: emacs-devel@gnu.org
> Date: Tue, 04 Jul 2023 17:29:07 +0000
> 
> > This is software: anything's possible ;-).  But Someone™ needs to
> > write the code, like marshalling and unmarshalling of such objects
> > between two processes.  (We do something like that when we write then
> > load the pdumper file.)  There's more than one way of skinning this
> > particular cat.
> 
> As the first step, I wanted to hear if there is any blocker that
> prevents memcpy between processes without going through print/read.

I don't think you can design on this base.  Security and all that.
Also, complex structures include pointers and references, which you
cannot safely copy as-is anyway.

> >> The inter-process communication does not have to be asynchronous, but
> >> may work similar to the existing thread implementation.
> >
> > I wouldn't recommend designing anything by the example of Lisp
> > threads.  'Nough said.
> 
> IMHO, the main problem with threads is that they cannot be interrupted
> or fire too frequently.

I wish this were the only problem with threads.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-04 17:35     ` Eli Zaretskii
@ 2023-07-04 17:52       ` Ihor Radchenko
  2023-07-04 18:24         ` Eli Zaretskii
  2023-07-05  0:34         ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-04 17:52 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> As the first step, I wanted to hear if there is any blocker that
>> prevents memcpy between processes without going through print/read.
>
> I don't think you can design on this base.  Security and all that.

But why is it a problem? Isn't it normal for a C++ thread to get pointer
to the parent heap? For Emacs async process, it can, for example, be a
pointer to obarray.

> Also, complex structures include pointers and references, which you
> cannot safely copy as-is anyway.

May you please elaborate?

>> IMHO, the main problem with threads is that they cannot be interrupted
>> or fire too frequently.
>
> I wish this were the only problem with threads.

Maybe. But I haven't seen other problems preventing threads from being used.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-04 17:52       ` Ihor Radchenko
@ 2023-07-04 18:24         ` Eli Zaretskii
  2023-07-05 11:23           ` Ihor Radchenko
  2023-07-05  0:34         ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-04 18:24 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: emacs-devel@gnu.org
> Date: Tue, 04 Jul 2023 17:52:23 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> As the first step, I wanted to hear if there is any blocker that
> >> prevents memcpy between processes without going through print/read.
> >
> > I don't think you can design on this base.  Security and all that.
> 
> But why is it a problem?

I'm not an expert, but AFAIK reading from, and writing to, the memory
of another process is something allowed basically only for debuggers.

And how would you know the address in another process anyway, given
today's ASLR techniques?

> Isn't it normal for a C++ thread to get pointer to the parent heap?

That's inside the same process: a huge difference.

> > Also, complex structures include pointers and references, which you
> > cannot safely copy as-is anyway.
> 
> May you please elaborate?

For example, a list whose member is a string includes a pointer to
that string's data.

> >> IMHO, the main problem with threads is that they cannot be interrupted
> >> or fire too frequently.
> >
> > I wish this were the only problem with threads.
> 
> Maybe. But I haven't seen other problems preventing threads from being used.

I have, too many of them.  Some are semi-fixed, but I'm afraid we only
don't hear about them because threads are not used in serious,
production-quality programs.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-04 16:58 Concurrency via isolated process/thread Ihor Radchenko
  2023-07-04 17:12 ` Eli Zaretskii
@ 2023-07-05  0:33 ` Po Lu
  2023-07-05  2:31   ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-05  0:33 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> More concretely, is it possible to copy internal Elisp object
> representations between Emacs processes and arrange mutability to query
> the right Emacs process that "owns" the object?

It is not.

But anyway I have a sinking suspicion that any solution that involves
special IPC implemented in C code will prove to be more trouble than
allowing multiple Lisp threads to run simultaneously and interlocking
Emacs itself.  It would require a lot of manpower, but it isn't
impossible: other large programs have been interlocked to run on SMPs,
most notably Unix.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-04 17:52       ` Ihor Radchenko
  2023-07-04 18:24         ` Eli Zaretskii
@ 2023-07-05  0:34         ` Po Lu
  2023-07-05 11:26           ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-05  0:34 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> But why is it a problem? Isn't it normal for a C++ thread to get pointer
> to the parent heap? For Emacs async process, it can, for example, be a
> pointer to obarray.

How will you interlock access to obarray?  What if both processes call
gensym at the same time?



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05  0:33 ` Po Lu
@ 2023-07-05  2:31   ` Eli Zaretskii
  2023-07-17 20:43     ` Hugo Thunnissen
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-05  2:31 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: emacs-devel@gnu.org
> Date: Wed, 05 Jul 2023 08:33:33 +0800
> 
> But anyway I have a sinking suspicion that any solution that involves
> special IPC implemented in C code will prove to be more trouble than
> allowing multiple Lisp threads to run simultaneously and interlocking
> Emacs itself.

No, because we already handle sub-process output in a way that doesn't
require true concurrency.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-04 18:24         ` Eli Zaretskii
@ 2023-07-05 11:23           ` Ihor Radchenko
  2023-07-05 11:49             ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-05 11:23 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> > I don't think you can design on this base.  Security and all that.
>> 
>> But why is it a problem?
>
> I'm not an expert, but AFAIK reading from, and writing to, the memory
> of another process is something allowed basically only for debuggers.
>
> And how would you know the address in another process anyway, given
> today's ASLR techniques?

I am looking at
https://stackoverflow.com/questions/5656530/how-to-use-shared-memory-with-linux-in-c

AFAIU, it is possible to create shared memory only readable by child
processes.

Then, exchanging data between the two Emacs processes may be done using
memcpy to/from shared memory.

It may be dumb (I have no experience with processes in C), but I have
something like the following in mind:

1. Main Emacs process has a normal Elisp thread that watches for async
   Emacs process requests.
2. Once a request arrives, asking to get/modify main Emacs process data,
   the request is fulfilled synchronously and signaled back by writing
   to memory accessible by the async process.

>> > Also, complex structures include pointers and references, which you
>> > cannot safely copy as-is anyway.
>> 
>> May you please elaborate?
>
> For example, a list whose member is a string includes a pointer to
> that string's data.

I imagine that instead of trying to copy Lisp objects recursively, there
will need to be a special "remote" Lisp object type. Getting/setting its
value will involve talking to other Emacs process.

>> > I wish this were the only problem with threads.
>> 
>> Maybe. But I haven't seen other problems preventing threads from being used.
>
> I have, too many of them.  Some are semi-fixed, but I'm afraid we only
> don't hear about them because threads are not used in serious,
> production-quality programs.

You are talking about bugs? If nobody goes far enough to discover those,
they are probably not the real reason why people do not use Elisp threads.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05  0:34         ` Po Lu
@ 2023-07-05 11:26           ` Ihor Radchenko
  2023-07-05 12:11             ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-05 11:26 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> But why is it a problem? Isn't it normal for a C++ thread to get pointer
>> to the parent heap? For Emacs async process, it can, for example, be a
>> pointer to obarray.
>
> How will you interlock access to obarray?  What if both processes call
> gensym at the same time?

I imagine that only a single, main Emacs process will truly own the
obarray.

The child async Emacs process will (1) partially have its own obarray
for lexical bindings; (2) query the parent process when there is a need
to read/write the shared obarray.  The query will be processed by the
parent process synchronously, so the situation you described will be
impossible.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 11:23           ` Ihor Radchenko
@ 2023-07-05 11:49             ` Eli Zaretskii
  2023-07-05 12:40               ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-05 11:49 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: emacs-devel@gnu.org
> Date: Wed, 05 Jul 2023 11:23:40 +0000
> 
> AFAIU, it is possible to create shared memory only readable by child
> processes.
> 
> Then, exchanging data between the two Emacs processes may be done using
> memcpy to/from shared memory.
> 
> It may be dumb (I have no experience with processes in C), but I have
> something like the following in mind:
> 
> 1. Main Emacs process has a normal Elisp thread that watches for async
>    Emacs process requests.
> 2. Once a request arrives, asking to get/modify main Emacs process data,
>    the request is fulfilled synchronously and signaled back by writing
>    to memory accessible by the async process.

That solves part of the problem, maybe (assuming we'd want to allow
shared memory in Emacs).  The other parts -- how to implement async
process requests so that they don't suffer from the same problem, and
how to reference objects outside of the shared memory -- are still
there.

> > I have, too many of them.  Some are semi-fixed, but I'm afraid we only
> > don't hear about them because threads are not used in serious,
> > production-quality programs.
> 
> You are talking about bugs?

Bugs that are there "by design".

> If nobody goes far enough to discover those,
> they are probably not the real reason why people do not use Elisp threads.

I'm saying that it could be the other way around: we don't hear about
those bugs because threads are not used seriously.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 11:26           ` Ihor Radchenko
@ 2023-07-05 12:11             ` Po Lu
  2023-07-05 12:44               ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-05 12:11 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I imagine that only a single, main Emacs process will truly own the
> obarray.
>
> The child async Emacs process will (1) partially have its own obarray
> for lexical bindings; (2) query the parent process when there is a need
> to read/write the shared obarray.  The query will be processed by the
> parent process synchronously, so the situation you described will be
> impossible.

The obarray contains symbols, not their values.  So I don't understand
what you refer to by a separate obarray for lexical bindings.




^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 11:49             ` Eli Zaretskii
@ 2023-07-05 12:40               ` Ihor Radchenko
  2023-07-05 13:02                 ` Lynn Winebarger
  2023-07-05 13:33                 ` Eli Zaretskii
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-05 12:40 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> It may be dumb (I have no experience with processes in C), but I have
>> something like the following in mind:
>> 
>> 1. Main Emacs process has a normal Elisp thread that watches for async
>>    Emacs process requests.
>> 2. Once a request arrives, asking to get/modify main Emacs process data,
>>    the request is fulfilled synchronously and signaled back by writing
>>    to memory accessible by the async process.
>
> That solves part of the problem, maybe (assuming we'd want to allow
> shared memory in Emacs).

My idea is basically similar to the current schema of interacting
between process input/output and Emacs. But using data stream rather
than text stream.

Shared memory is one way. Or it may be something like sockets.
It's just that shared memory will be faster, AFAIU.

> ... The other parts -- how to implement async
> process requests so that they don't suffer from the same problem, and
> how to reference objects outside of the shared memory -- are still
> there.

I imagine that there will be a special "remote Lisp object" type.

1. Imagine that child Emacs process asks for a value of variable `foo',
   which is a list (1 2 3 4).
2. The child process requests parent Emacs to put the variable value
   into shared memory.
3. The parent process creates a new variable storing a link to (1 2 3
   4), to prevent (1 . (2 3 4)) cons cell from GC in the parent process
   - `foo#'. Then, it informs the child process about this variable.
4. The child process creates a new remote Lisp object #<remote cons foo#>.

5. Now consider that child process tries (setcar #<remote cons foo#> value).
   The `setcar' and other primitives will be modified to query parent
   process to perform the actual modification to
   (#<remote value> . (2 3 4))

6. Before exiting the child thread, or every time we need to copy remote
   object, #<remote ...> will be replaced by an actual newly created
   traditional object.

>> If nobody goes far enough to discover those,
>> they are probably not the real reason why people do not use Elisp threads.
>
> I'm saying that it could be the other way around: we don't hear about
> those bugs because threads are not used seriously.

I feel like this part of the discussion is not contributing to the main
topic. At the end, it is not critical if Elisp threads are used to
implement the discussed idea. Or timers, or something else.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 12:11             ` Po Lu
@ 2023-07-05 12:44               ` Ihor Radchenko
  2023-07-05 13:21                 ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-05 12:44 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> I imagine that only a single, main Emacs process will truly own the
>> obarray.
>>
>> The child async Emacs process will (1) partially have its own obarray
>> for lexical bindings; (2) query the parent process when there is a need
>> to read/write the shared obarray.  The query will be processed by the
>> parent process synchronously, so the situation you described will be
>> impossible.
>
> The obarray contains symbols, not their values.  So I don't understand
> what you refer to by a separate obarray for lexical bindings.

I imagine that the child Emacs will have a very limited obarray.
If a symbol is not found in that process-local obarray, a query to
parent Emacs process will be sent to retrieve the symbol slot values.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 12:40               ` Ihor Radchenko
@ 2023-07-05 13:02                 ` Lynn Winebarger
  2023-07-05 13:10                   ` Ihor Radchenko
  2023-07-05 13:33                 ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Lynn Winebarger @ 2023-07-05 13:02 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 2759 bytes --]

On Wed, Jul 5, 2023, 8:41 AM Ihor Radchenko <yantar92@posteo.net> wrote:

> Eli Zaretskii <eliz@gnu.org> writes:
>
> >> It may be dumb (I have no experience with processes in C), but I have
> >> something like the following in mind:
> >>
> >> 1. Main Emacs process has a normal Elisp thread that watches for async
> >>    Emacs process requests.
> >> 2. Once a request arrives, asking to get/modify main Emacs process data,
> >>    the request is fulfilled synchronously and signaled back by writing
> >>    to memory accessible by the async process.
> >
> > That solves part of the problem, maybe (assuming we'd want to allow
> > shared memory in Emacs).
>
> My idea is basically similar to the current schema of interacting
> between process input/output and Emacs. But using data stream rather
> than text stream.
>
> Shared memory is one way. Or it may be something like sockets.
> It's just that shared memory will be faster, AFAIU.
>
> > ... The other parts -- how to implement async
> > process requests so that they don't suffer from the same problem, and
> > how to reference objects outside of the shared memory -- are still
> > there.
>
> I imagine that there will be a special "remote Lisp object" type.
>
> 1. Imagine that child Emacs process asks for a value of variable `foo',
>    which is a list (1 2 3 4).
> 2. The child process requests parent Emacs to put the variable value
>    into shared memory.
> 3. The parent process creates a new variable storing a link to (1 2 3
>    4), to prevent (1 . (2 3 4)) cons cell from GC in the parent process
>    - `foo#'. Then, it informs the child process about this variable.
> 4. The child process creates a new remote Lisp object #<remote cons foo#>.
>
> 5. Now consider that child process tries (setcar #<remote cons foo#>
> value).
>    The `setcar' and other primitives will be modified to query parent
>    process to perform the actual modification to
>    (#<remote value> . (2 3 4))
>
> 6. Before exiting the child thread, or every time we need to copy remote
>    object, #<remote ...> will be replaced by an actual newly created
>    traditional object.


The best idea I've had for a general solution would be to make "concurrent"
versions of the fundamental lisp objects that act like immutable git
repositories, with the traditional versions of the objects acting as
working copies but only recording changes.  Then each checked out copy
could push charges back, and if the merge fails an exception would be
thrown in the thread of that working copy which the elisp code could decide
how to handle.  That would work for inter-process shared memory or plain
in-process memory between threads.  Then locks are only needed for updating
the main reference to the concurrent object.

Lynn

[-- Attachment #2: Type: text/html, Size: 3623 bytes --]

^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 13:02                 ` Lynn Winebarger
@ 2023-07-05 13:10                   ` Ihor Radchenko
  2023-07-06 18:35                     ` Lynn Winebarger
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-05 13:10 UTC (permalink / raw)
  To: Lynn Winebarger; +Cc: Eli Zaretskii, emacs-devel

Lynn Winebarger <owinebar@gmail.com> writes:

> The best idea I've had for a general solution would be to make "concurrent"
> versions of the fundamental lisp objects that act like immutable git
> repositories, with the traditional versions of the objects acting as
> working copies but only recording changes.  Then each checked out copy
> could push charges back, and if the merge fails an exception would be
> thrown in the thread of that working copy which the elisp code could decide
> how to handle.  That would work for inter-process shared memory or plain
> in-process memory between threads.  Then locks are only needed for updating
> the main reference to the concurrent object.

Honestly, it sounds overengineered.
Even if not, it is probably easier to implement a more limited version
first and only then think about fancier staff like you described (not
that I understand your idea fully).

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 12:44               ` Ihor Radchenko
@ 2023-07-05 13:21                 ` Po Lu
  2023-07-05 13:26                   ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-05 13:21 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I imagine that the child Emacs will have a very limited obarray.
> If a symbol is not found in that process-local obarray, a query to
> parent Emacs process will be sent to retrieve the symbol slot values.

How will you retrieve the value of a symbol that does not exist in the
child process?  And what about the negative performance implications of
contacting another process to retrieve a symbol's value?  The object
will have to be copied from the parent, and the parent may also be busy.

Anyway, we already have sufficient mechanisms for communicating with
subprocesses.  If Emacs is to take any more advantage of SMP systems, it
must be properly interlocked, with multiple processors sharing the same
memory.  This lesson was learned decades ago with another program:
vmunix.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 13:21                 ` Po Lu
@ 2023-07-05 13:26                   ` Ihor Radchenko
  2023-07-05 13:51                     ` Eli Zaretskii
  2023-07-06  0:27                     ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-05 13:26 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> I imagine that the child Emacs will have a very limited obarray.
>> If a symbol is not found in that process-local obarray, a query to
>> parent Emacs process will be sent to retrieve the symbol slot values.
>
> How will you retrieve the value of a symbol that does not exist in the
> child process?

See my other reply.
The idea is to create a special Lisp Object type.

> And what about the negative performance implications of
> contacting another process to retrieve a symbol's value?  The object
> will have to be copied from the parent, and the parent may also be busy.

Yes, such requests will be synchronous and will need to be avoided.
Ideally, child process just need to query input at the beginning and
transfer the output at the end without communicating with parent
process. Any other communication will be costly.

> Anyway, we already have sufficient mechanisms for communicating with
> subprocesses.

I am only aware of text-based communication. Is there anything else?

> If Emacs is to take any more advantage of SMP systems, it
> must be properly interlocked, with multiple processors sharing the same
> memory.  This lesson was learned decades ago with another program:
> vmunix.

AFAIU, one of the big blockers of this is single-threaded GC. Emacs
cannot free/allocate memory asynchronously. Or do I miss something?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 12:40               ` Ihor Radchenko
  2023-07-05 13:02                 ` Lynn Winebarger
@ 2023-07-05 13:33                 ` Eli Zaretskii
  2023-07-05 13:35                   ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-05 13:33 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: emacs-devel@gnu.org
> Date: Wed, 05 Jul 2023 12:40:51 +0000
> 
> I imagine that there will be a special "remote Lisp object" type.
> 
> 1. Imagine that child Emacs process asks for a value of variable `foo',
>    which is a list (1 2 3 4).
> 2. The child process requests parent Emacs to put the variable value
>    into shared memory.
> 3. The parent process creates a new variable storing a link to (1 2 3
>    4), to prevent (1 . (2 3 4)) cons cell from GC in the parent process
>    - `foo#'. Then, it informs the child process about this variable.
> 4. The child process creates a new remote Lisp object #<remote cons foo#>.
> 
> 5. Now consider that child process tries (setcar #<remote cons foo#> value).
>    The `setcar' and other primitives will be modified to query parent
>    process to perform the actual modification to
>    (#<remote value> . (2 3 4))
> 
> 6. Before exiting the child thread, or every time we need to copy remote
>    object, #<remote ...> will be replaced by an actual newly created
>    traditional object.

How is this different from communicating via stdout, like we do with
start-process today?  You don't have to send only textual data via the
pipe, you can send binary data stream as well, if what bothers you is
the conversion.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 13:33                 ` Eli Zaretskii
@ 2023-07-05 13:35                   ` Ihor Radchenko
  0 siblings, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-05 13:35 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> I imagine that there will be a special "remote Lisp object" type.
> ...
> How is this different from communicating via stdout, like we do with
> start-process today?  You don't have to send only textual data via the
> pipe, you can send binary data stream as well, if what bothers you is
> the conversion.

Piping binary data will also work.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 13:26                   ` Ihor Radchenko
@ 2023-07-05 13:51                     ` Eli Zaretskii
  2023-07-05 14:00                       ` Ihor Radchenko
  2023-07-06  0:27                     ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-05 13:51 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
> Date: Wed, 05 Jul 2023 13:26:54 +0000
> 
> > If Emacs is to take any more advantage of SMP systems, it
> > must be properly interlocked, with multiple processors sharing the same
> > memory.  This lesson was learned decades ago with another program:
> > vmunix.
> 
> AFAIU, one of the big blockers of this is single-threaded GC. Emacs
> cannot free/allocate memory asynchronously. Or do I miss something?

Why is that a "big blocker"?  GC runs "whenever it is convenient"
anyway, so it can be delayed until that time, and run from the main
thread, perhaps sacrificing the memory footprint a bit.

The real "big blocker" is that a typical Emacs session has a huge
global state, which cannot be safely modified by more than a single
thread at a time, and even if one thread writes while the other reads
is in many cases problematic.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 13:51                     ` Eli Zaretskii
@ 2023-07-05 14:00                       ` Ihor Radchenko
  2023-07-06  0:32                         ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-05 14:00 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> AFAIU, one of the big blockers of this is single-threaded GC. Emacs
>> cannot free/allocate memory asynchronously. Or do I miss something?
>
> Why is that a "big blocker"?  GC runs "whenever it is convenient"
> anyway, so it can be delayed until that time, and run from the main
> thread, perhaps sacrificing the memory footprint a bit.

Emm. I meant memory allocation. AFAIK, just like GC allocating heap
cannot be asynchronous.

For the GC, most of the memory objects in long-running Emacs sessions
are short-living. So, having a separate GC and memory heap in async
process will likely improve the main thread performance GC-wise, as a
side effect.

> The real "big blocker" is that a typical Emacs session has a huge
> global state, which cannot be safely modified by more than a single
> thread at a time, and even if one thread writes while the other reads
> is in many cases problematic.

This too, although isn't is already solved by mutexes?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 13:26                   ` Ihor Radchenko
  2023-07-05 13:51                     ` Eli Zaretskii
@ 2023-07-06  0:27                     ` Po Lu
  2023-07-06 10:48                       ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-06  0:27 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I am only aware of text-based communication. Is there anything else?

It doesn't have to be text, no, it could be binary data as well, on top
of which any RPC mechanism could be built.

> AFAIU, one of the big blockers of this is single-threaded GC. Emacs
> cannot free/allocate memory asynchronously. Or do I miss something?

Garbage collection can be made to suspend all other threads and ``pin''
string blocks that are referenced from other threads as a temporary
measure.  It's hardly the reason that makes Emacs difficult to
interlock.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 14:00                       ` Ihor Radchenko
@ 2023-07-06  0:32                         ` Po Lu
  2023-07-06 10:46                           ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-06  0:32 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Emm. I meant memory allocation. AFAIK, just like GC allocating heap
> cannot be asynchronous.

The garbage collector and object allocation can be interlocked, as with
everything else...

> This too, although isn't is already solved by mutexes?

... which you proceed to admit here, and is the crux of the problem.
Getting rid of the Lisp interpreter state that is still not thread-local
(BLV redirects come to mind) is only a minor challenge, compared to the
painstaking and careful work that will be required to interlock access
to the rest of the global state and objects like buffers.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06  0:32                         ` Po Lu
@ 2023-07-06 10:46                           ` Ihor Radchenko
  2023-07-06 12:24                             ` Po Lu
  2023-07-06 14:08                             ` Eli Zaretskii
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 10:46 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> Emm. I meant memory allocation. AFAIK, just like GC allocating heap
>> cannot be asynchronous.
>
> The garbage collector and object allocation can be interlocked, as with
> everything else...

I may be wrong, but from my previous experience with performance
benchmarks, memory allocation often takes a significant fraction of CPU
time. And memory allocation is a routine process on pretty much every
iteration of CPU-intensive code.

I am afraid that interlocking object allocation will practically create
race condition between threads and make Emacs unresponsive.

Or am I missing something?
Is there a way to measure how much CPU time is spent allocating memory?

>> This too, although isn't is already solved by mutexes?
>
> ... which you proceed to admit here, and is the crux of the problem.
> Getting rid of the Lisp interpreter state that is still not thread-local
> (BLV redirects come to mind) is only a minor challenge, compared to the
> painstaking and careful work that will be required to interlock access
> to the rest of the global state and objects like buffers.

Would it be of interest to allow locking objects for read/write using
semantics similar to `with-mutex'?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06  0:27                     ` Po Lu
@ 2023-07-06 10:48                       ` Ihor Radchenko
  2023-07-06 12:15                         ` Po Lu
  2023-07-06 14:10                         ` Eli Zaretskii
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 10:48 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> I am only aware of text-based communication. Is there anything else?
>
> It doesn't have to be text, no, it could be binary data as well, on top
> of which any RPC mechanism could be built.

Do you mean that binary communication is already possible? If so, is it
documented somewhere?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 10:48                       ` Ihor Radchenko
@ 2023-07-06 12:15                         ` Po Lu
  2023-07-06 14:10                         ` Eli Zaretskii
  1 sibling, 0 replies; 192+ messages in thread
From: Po Lu @ 2023-07-06 12:15 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Do you mean that binary communication is already possible? If so, is it
> documented somewhere?

Binary data can be sent and read from any subprocess, as with text
input.  The only special requirement is that the process must not have
an associated coding system, which is achieved by setting its coding
system to `no-conversion' AFAIK.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 10:46                           ` Ihor Radchenko
@ 2023-07-06 12:24                             ` Po Lu
  2023-07-06 12:31                               ` Ihor Radchenko
  2023-07-06 14:08                             ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-06 12:24 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I may be wrong, but from my previous experience with performance
> benchmarks, memory allocation often takes a significant fraction of CPU
> time. And memory allocation is a routine process on pretty much every
> iteration of CPU-intensive code.
>
> I am afraid that interlocking object allocation will practically create
> race condition between threads and make Emacs unresponsive.
>
> Or am I missing something?
> Is there a way to measure how much CPU time is spent allocating memory?

The detail of the interlocking can be increased if and when this is
demonstrated to be problematic.  Allocating individual Lisp objects
usually takes a short amount of time: even if no two threads can do so
at the same time, they will all have ample opportunities to run in
between consings.

> Would it be of interest to allow locking objects for read/write using
> semantics similar to `with-mutex'?

The problem is interlocking access to low level C state within objects
and not from Lisp code itself, and also avoiding constructs such as:

  CHECK_STRING (XCAR (foo));
  foo = XSTRING (XCAR (foo));

where the second load from XCAR (foo)->u.s.car might load a different
pointer from the one whose type was checked.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 12:24                             ` Po Lu
@ 2023-07-06 12:31                               ` Ihor Radchenko
  2023-07-06 12:41                                 ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 12:31 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> Or am I missing something?
>> Is there a way to measure how much CPU time is spent allocating memory?
>
> The detail of the interlocking can be increased if and when this is
> demonstrated to be problematic.  Allocating individual Lisp objects
> usually takes a short amount of time: even if no two threads can do so
> at the same time, they will all have ample opportunities to run in
> between consings.

That's why I asked if there is a way to measure how much CPU time
allocating takes.

>> Would it be of interest to allow locking objects for read/write using
>> semantics similar to `with-mutex'?
>
> The problem is interlocking access to low level C state within objects
> and not from Lisp code itself, and also avoiding constructs such as:
>
>   CHECK_STRING (XCAR (foo));
>   foo = XSTRING (XCAR (foo));
>
> where the second load from XCAR (foo)->u.s.car might load a different
> pointer from the one whose type was checked.

I am thinking about some kind of extra flag that will mark an object
locked:

   LOCK_OBJECT (foo);
   LOCK_OBJECT (XCAR (foo));
   CHECK_STRING (XCAR (foo));
   foo = XSTRING (XCAR (foo));
   UNLOCK_OBJECT (XCAR (foo));
   UNLOCK_OBJECT (foo);

LOCK_OBJECT will block until the object is available for use.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 12:31                               ` Ihor Radchenko
@ 2023-07-06 12:41                                 ` Po Lu
  2023-07-06 12:51                                   ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-06 12:41 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I am thinking about some kind of extra flag that will mark an object
> locked:
>
>    LOCK_OBJECT (foo);
>    LOCK_OBJECT (XCAR (foo));
>    CHECK_STRING (XCAR (foo));
>    foo = XSTRING (XCAR (foo));
>    UNLOCK_OBJECT (XCAR (foo));
>    UNLOCK_OBJECT (foo);
>
> LOCK_OBJECT will block until the object is available for use.

This is unnecessary.  Loads and stores of Lisp_Object values are cache
coherent except on 32 bit systems --with-wide-int.  XCAR (foo) will
always load one of the values previously written.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 12:41                                 ` Po Lu
@ 2023-07-06 12:51                                   ` Ihor Radchenko
  2023-07-06 12:58                                     ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 12:51 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>>    LOCK_OBJECT (foo);
>>    LOCK_OBJECT (XCAR (foo));
> ...
> This is unnecessary.  Loads and stores of Lisp_Object values are cache
> coherent except on 32 bit systems --with-wide-int.  XCAR (foo) will
> always load one of the values previously written.

Do you mean that locking XCAR (foo) is unnecessary when foo is locked?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 12:51                                   ` Ihor Radchenko
@ 2023-07-06 12:58                                     ` Po Lu
  2023-07-06 13:13                                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-06 12:58 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Po Lu <luangruo@yahoo.com> writes:
>
>>>    LOCK_OBJECT (foo);
>>>    LOCK_OBJECT (XCAR (foo));
>> ...
>> This is unnecessary.  Loads and stores of Lisp_Object values are cache
>> coherent except on 32 bit systems --with-wide-int.  XCAR (foo) will
>> always load one of the values previously written.
>
> Do you mean that locking XCAR (foo) is unnecessary when foo is locked?

No, that there is no need to lock a cons (or a vector, or anything else
with a fixed number of Lisp_Object slots) before reading or writing to
it.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 12:58                                     ` Po Lu
@ 2023-07-06 13:13                                       ` Ihor Radchenko
  2023-07-06 14:13                                         ` Eli Zaretskii
  2023-07-07  0:21                                         ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 13:13 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>>>>    LOCK_OBJECT (foo);
>>>>    LOCK_OBJECT (XCAR (foo));
>>> ...
>> ...
>> Do you mean that locking XCAR (foo) is unnecessary when foo is locked?
>
> No, that there is no need to lock a cons (or a vector, or anything else
> with a fixed number of Lisp_Object slots) before reading or writing to
> it.

I feel confused here.

My understanding is

  CHECK_STRING (XCAR (foo));
  <we do not want XCAR (foo) to be altered here>
  foo = XSTRING (XCAR (foo));

So, locking is needed to ensure that CHECK_STRING assertion remains valid.

Or did you refer to something else?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 10:46                           ` Ihor Radchenko
  2023-07-06 12:24                             ` Po Lu
@ 2023-07-06 14:08                             ` Eli Zaretskii
  2023-07-06 15:01                               ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 14:08 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 10:46:47 +0000
> 
> Po Lu <luangruo@yahoo.com> writes:
> 
> >> Emm. I meant memory allocation. AFAIK, just like GC allocating heap
> >> cannot be asynchronous.
> >
> > The garbage collector and object allocation can be interlocked, as with
> > everything else...
> 
> I may be wrong, but from my previous experience with performance
> benchmarks, memory allocation often takes a significant fraction of CPU
> time. And memory allocation is a routine process on pretty much every
> iteration of CPU-intensive code.

Do you have any evidence for that which you can share?  GC indeed
takes significant time, but memory allocation? never heard of that.

> Is there a way to measure how much CPU time is spent allocating memory?

If you don't know about such a way, then how did you conclude it could
take significant time?

> Would it be of interest to allow locking objects for read/write using
> semantics similar to `with-mutex'?

Locking slows down software and should be avoided, I'm sure you know
it.  But the global lock used by the Lisp threads we have is actually
such a lock, and the results are well known.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 10:48                       ` Ihor Radchenko
  2023-07-06 12:15                         ` Po Lu
@ 2023-07-06 14:10                         ` Eli Zaretskii
  2023-07-06 15:09                           ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 14:10 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 10:48:04 +0000
> 
> Po Lu <luangruo@yahoo.com> writes:
> 
> > Ihor Radchenko <yantar92@posteo.net> writes:
> >
> >> I am only aware of text-based communication. Is there anything else?
> >
> > It doesn't have to be text, no, it could be binary data as well, on top
> > of which any RPC mechanism could be built.
> 
> Do you mean that binary communication is already possible? If so, is it
> documented somewhere?

What is the difference between binary and text in this context, in
your interpretation?  (I'm surprised to hear they are perceived as
different by someone who comes from Posix background, not MS-Windows
background.)

Because there's no difference, there's nothing to document.  For the
same reason we don't document that "C-x C-f" can visit binary files,
not just text files.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 13:13                                       ` Ihor Radchenko
@ 2023-07-06 14:13                                         ` Eli Zaretskii
  2023-07-06 14:47                                           ` Ihor Radchenko
  2023-07-07  0:21                                         ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 14:13 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 13:13:07 +0000
> 
> Po Lu <luangruo@yahoo.com> writes:
> 
> >>>>    LOCK_OBJECT (foo);
> >>>>    LOCK_OBJECT (XCAR (foo));
> >>> ...
> >> ...
> >> Do you mean that locking XCAR (foo) is unnecessary when foo is locked?
> >
> > No, that there is no need to lock a cons (or a vector, or anything else
> > with a fixed number of Lisp_Object slots) before reading or writing to
> > it.
> 
> I feel confused here.
> 
> My understanding is
> 
>   CHECK_STRING (XCAR (foo));
>   <we do not want XCAR (foo) to be altered here>
>   foo = XSTRING (XCAR (foo));
> 
> So, locking is needed to ensure that CHECK_STRING assertion remains valid.
> 
> Or did you refer to something else?

I don't know what Po Lu had in mind, but one aspect of this is that a
string object might keep its memory address, but its data could be
relocated.  This can happen as part of GC, and is the reason why
string data is kept separate from the string itself.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 14:13                                         ` Eli Zaretskii
@ 2023-07-06 14:47                                           ` Ihor Radchenko
  2023-07-06 15:10                                             ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 14:47 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> My understanding is
>> 
>>   CHECK_STRING (XCAR (foo));
>>   <we do not want XCAR (foo) to be altered here>
>>   foo = XSTRING (XCAR (foo));
>> 
>> So, locking is needed to ensure that CHECK_STRING assertion remains valid.
>> 
>> Or did you refer to something else?
>
> I don't know what Po Lu had in mind, but one aspect of this is that a
> string object might keep its memory address, but its data could be
> relocated.  This can happen as part of GC, and is the reason why
> string data is kept separate from the string itself.

This does not change my understanding.
Locking should prevent manipulations with object data over the time span
the code expects the object to be unchanged.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 14:08                             ` Eli Zaretskii
@ 2023-07-06 15:01                               ` Ihor Radchenko
  2023-07-06 15:16                                 ` Eli Zaretskii
  2023-07-07  0:27                                 ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 15:01 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> I may be wrong, but from my previous experience with performance
>> benchmarks, memory allocation often takes a significant fraction of CPU
>> time. And memory allocation is a routine process on pretty much every
>> iteration of CPU-intensive code.
>
> Do you have any evidence for that which you can share?  GC indeed
> takes significant time, but memory allocation? never heard of that.

This is from my testing of Org parser.
I noticed that storing a pair of buffer positions is noticeably faster
compared to storing string copies of buffer text.

The details usually do not show up in M-x profiler reports, but I now
tried perf out of curiosity:

    14.76%  emacs         emacs                                  [.] re_match_2_internal
     9.39%  emacs         emacs                                  [.] re_compile_pattern
     4.45%  emacs         emacs                                  [.] re_search_2
     3.98%  emacs         emacs                                  [.] funcall_subr

     
     AFAIU, this is memory allocation. Taking a good one second in this case.
     3.37%  emacs         emacs                                  [.] allocate_vectorlike


     3.17%  emacs         emacs                                  [.] Ffuncall
     3.01%  emacs         emacs                                  [.] exec_byte_code
     2.90%  emacs         emacs                                  [.] buf_charpos_to_bytepos
     2.82%  emacs         emacs                                  [.] find_interval
     2.74%  emacs         emacs                                  [.] re_iswctype
     2.57%  emacs         emacs                                  [.] set_default_internal
     2.48%  emacs         emacs                                  [.] plist_get
     2.24%  emacs         emacs                                  [.] Fmemq
     1.95%  emacs         emacs                                  [.] process_mark_stack

These are just CPU cycles. I am not sure if there are any other
overheads related to memory allocation that translate into extra user time. 

>> Would it be of interest to allow locking objects for read/write using
>> semantics similar to `with-mutex'?
>
> Locking slows down software and should be avoided, I'm sure you know
> it.

I am not sure anymore.
Po Lu appears to advocate for locking instead of isolated process
approach, referring to other software.

> ... But the global lock used by the Lisp threads we have is actually
> such a lock, and the results are well known.

To be fair, global lock is an extreme worst-case scenario.
Locking specific Lisp objects is unlikely to lock normal Emacs usage,
especially when the async code is written carefully. Except when there
is a need to lock something from global and frequently used Emacs state,
like heap or obarray. Which is why I asked about memory allocation.

The need to avoid locked heap is the main reason I proposed isolated
processes.

... well, also things like lisp_eval_depth and other global interpreter
state variables.

But, AFAIU, Po Lu is advocating that trying isolated processes is not
worth the effort and it is better to byte the bullet and implement
proper locking.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 14:10                         ` Eli Zaretskii
@ 2023-07-06 15:09                           ` Ihor Radchenko
  2023-07-06 15:18                             ` Eli Zaretskii
  2023-07-07  0:22                             ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 15:09 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Do you mean that binary communication is already possible? If so, is it
>> documented somewhere?
>
> What is the difference between binary and text in this context, in
> your interpretation?

AFAIK, process communication is now implemented using buffers that, even
in the absence of coding system, index the data stream into byte array.
I am not sure if it is something that can be directly fed to memcpy (thus
avoiding too much of extra cost for passing Lisp data around).

> (I'm surprised to hear they are perceived as
> different by someone who comes from Posix background, not MS-Windows
> background.)

I was looking at this from C perspective.

And I am used to see Posix as text-based. At most, structured text like
https://www.nushell.sh/ Maybe I am too young.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 14:47                                           ` Ihor Radchenko
@ 2023-07-06 15:10                                             ` Eli Zaretskii
  2023-07-06 16:17                                               ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 15:10 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 14:47:13 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> My understanding is
> >> 
> >>   CHECK_STRING (XCAR (foo));
> >>   <we do not want XCAR (foo) to be altered here>
> >>   foo = XSTRING (XCAR (foo));
> >> 
> >> So, locking is needed to ensure that CHECK_STRING assertion remains valid.
> >> 
> >> Or did you refer to something else?
> >
> > I don't know what Po Lu had in mind, but one aspect of this is that a
> > string object might keep its memory address, but its data could be
> > relocated.  This can happen as part of GC, and is the reason why
> > string data is kept separate from the string itself.
> 
> This does not change my understanding.
> Locking should prevent manipulations with object data over the time span
> the code expects the object to be unchanged.

That could defeat GC, should Emacs decide to run it while the lock is
in place.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 15:01                               ` Ihor Radchenko
@ 2023-07-06 15:16                                 ` Eli Zaretskii
  2023-07-06 16:32                                   ` Ihor Radchenko
  2023-07-07  0:27                                 ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 15:16 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 15:01:39 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> I may be wrong, but from my previous experience with performance
> >> benchmarks, memory allocation often takes a significant fraction of CPU
> >> time. And memory allocation is a routine process on pretty much every
> >> iteration of CPU-intensive code.
> >
> > Do you have any evidence for that which you can share?  GC indeed
> > takes significant time, but memory allocation? never heard of that.
> 
> This is from my testing of Org parser.
> I noticed that storing a pair of buffer positions is noticeably faster
> compared to storing string copies of buffer text.
> 
> The details usually do not show up in M-x profiler reports, but I now
> tried perf out of curiosity:
> 
>     14.76%  emacs         emacs                                  [.] re_match_2_internal
>      9.39%  emacs         emacs                                  [.] re_compile_pattern
>      4.45%  emacs         emacs                                  [.] re_search_2
>      3.98%  emacs         emacs                                  [.] funcall_subr
> 
>      
>      AFAIU, this is memory allocation. Taking a good one second in this case.
>      3.37%  emacs         emacs                                  [.] allocate_vectorlike

It is?  Which part(s) of allocate_vectorlike take these 3.37% of run
time?  It does much more than just allocate memory.

> These are just CPU cycles. I am not sure if there are any other
> overheads related to memory allocation that translate into extra user time. 

Well, we need to be pretty damn sure before we consider this a fact,
don't we?

> > ... But the global lock used by the Lisp threads we have is actually
> > such a lock, and the results are well known.
> 
> To be fair, global lock is an extreme worst-case scenario.

If you consider the fact that the global state in Emacs is huge, maybe
it is a good approximation to what will need to be locked anyway?

> Locking specific Lisp objects is unlikely to lock normal Emacs usage,
> especially when the async code is written carefully. Except when there
> is a need to lock something from global and frequently used Emacs state,
> like heap or obarray. Which is why I asked about memory allocation.

You forget buffers, windows, frames, variables, and other global stuff.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 15:09                           ` Ihor Radchenko
@ 2023-07-06 15:18                             ` Eli Zaretskii
  2023-07-06 16:36                               ` Ihor Radchenko
  2023-07-07  0:22                             ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 15:18 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 15:09:06 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> Do you mean that binary communication is already possible? If so, is it
> >> documented somewhere?
> >
> > What is the difference between binary and text in this context, in
> > your interpretation?
> 
> AFAIK, process communication is now implemented using buffers that, even
> in the absence of coding system, index the data stream into byte array.

Yes, but isn't binary data also a stream of bytes?

> I am not sure if it is something that can be directly fed to memcpy (thus
> avoiding too much of extra cost for passing Lisp data around).

If you don't want the incoming data to be inserted into a buffer or
produce a string from it, then what do you want to do with it instead?
To use something in Emacs, we _must_ make some Lisp object out of it,
right?

> > (I'm surprised to hear they are perceived as
> > different by someone who comes from Posix background, not MS-Windows
> > background.)
> 
> I was looking at this from C perspective.

There's no difference there as well.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 15:10                                             ` Eli Zaretskii
@ 2023-07-06 16:17                                               ` Ihor Radchenko
  2023-07-06 18:19                                                 ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 16:17 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> This does not change my understanding.
>> Locking should prevent manipulations with object data over the time span
>> the code expects the object to be unchanged.
>
> That could defeat GC, should Emacs decide to run it while the lock is
> in place.

May you elaborate?

From my understanding, GC is now called in specific places in subr code.
If we consider scenario when multiple Emacs threads are running and one
is requesting GC, it should be acceptable to delay that request and wait
until all other threads eventually arrive to GC call. Once that is done,
GC is safe to run.

Of course, GC calls must not be done while Object lock is in place. But
that's not too different from the existing requirement for GC calls -
they are not sprinkled in arbitrary places.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 15:16                                 ` Eli Zaretskii
@ 2023-07-06 16:32                                   ` Ihor Radchenko
  2023-07-06 17:50                                     ` Eli Zaretskii
  2023-07-07  0:41                                     ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 16:32 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>>      AFAIU, this is memory allocation. Taking a good one second in this case.
>>      3.37%  emacs         emacs                                  [.] allocate_vectorlike
>
> It is?  Which part(s) of allocate_vectorlike take these 3.37% of run
> time?  It does much more than just allocate memory.

Sorry, but I have no idea. The above is what I see from perf report.

For comparison, this is how things look like with Org parser version
that allocated 1.5-2x more memory (proper strings instead of buffer
positions and proper strings instead of interned constant strings):

    18.39%  emacs         emacs                           [.] exec_byte_code
    13.80%  emacs         emacs                           [.] re_match_2_internal
     6.56%  emacs         emacs                           [.] re_compile_pattern

     5.09%  emacs         emacs                           [.] allocate_vectorlike

     4.35%  emacs         emacs                           [.] re_search_2
     3.57%  emacs         emacs                           [.] Fmemq
     3.13%  emacs         emacs                           [.] find_interval

So, my efforts did reduce the time spent in allocate_vectorlike.
Note, however, that these two datapoints differ more than just by how
memory is allocated.

But 5% CPU time spend allocating memory is not insignificant.

>> These are just CPU cycles. I am not sure if there are any other
>> overheads related to memory allocation that translate into extra user time. 
>
> Well, we need to be pretty damn sure before we consider this a fact,
> don't we?

Sure. Though my argument was less about how long Emacs spends allocating
memory and more about how frequently a typical Elisp code requests such
allocations. I have a gut feeling that even if taking short time,
frequent interrupts may create intermittent typing delays.

>> > ... But the global lock used by the Lisp threads we have is actually
>> > such a lock, and the results are well known.
>> 
>> To be fair, global lock is an extreme worst-case scenario.
>
> If you consider the fact that the global state in Emacs is huge, maybe
> it is a good approximation to what will need to be locked anyway?

Not every thread will need to use global state, except maybe memory
allocation. Or do I miss an elephant in the room?

> You forget buffers, windows, frames, variables, and other global stuff.

Those will only matter when we try to access them from multiple threads,
no? If a thread is working with a temporary buffer and locks it, that
buffer has almost 0 chance to be accessed by another thread.
Same with variables - even if some global variable needs to be locked,
it is unlikely that it will need to be accessed by another thread.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 15:18                             ` Eli Zaretskii
@ 2023-07-06 16:36                               ` Ihor Radchenko
  2023-07-06 17:53                                 ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-06 16:36 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> AFAIK, process communication is now implemented using buffers that, even
>> in the absence of coding system, index the data stream into byte array.
>
> Yes, but isn't binary data also a stream of bytes?

It is, but storing that data in buffer will involve non-trivial extra
memory allocation.

>> I am not sure if it is something that can be directly fed to memcpy (thus
>> avoiding too much of extra cost for passing Lisp data around).
>
> If you don't want the incoming data to be inserted into a buffer or
> produce a string from it, then what do you want to do with it instead?
> To use something in Emacs, we _must_ make some Lisp object out of it,
> right?

What I had in mind is, for example, memcpy wide_int_object -> child
process -> memcpy to child process own heap.

So that we do not need to go through creating a new wide_int object in
the child process.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 16:32                                   ` Ihor Radchenko
@ 2023-07-06 17:50                                     ` Eli Zaretskii
  2023-07-07 12:30                                       ` Ihor Radchenko
  2023-07-07  0:41                                     ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 17:50 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 16:32:03 +0000
> 
> > It is?  Which part(s) of allocate_vectorlike take these 3.37% of run
> > time?  It does much more than just allocate memory.
> 
> Sorry, but I have no idea. The above is what I see from perf report.
> 
> For comparison, this is how things look like with Org parser version
> that allocated 1.5-2x more memory (proper strings instead of buffer
> positions and proper strings instead of interned constant strings):
> 
>     18.39%  emacs         emacs                           [.] exec_byte_code
>     13.80%  emacs         emacs                           [.] re_match_2_internal
>      6.56%  emacs         emacs                           [.] re_compile_pattern
> 
>      5.09%  emacs         emacs                           [.] allocate_vectorlike
> 
>      4.35%  emacs         emacs                           [.] re_search_2
>      3.57%  emacs         emacs                           [.] Fmemq
>      3.13%  emacs         emacs                           [.] find_interval
> 
> So, my efforts did reduce the time spent in allocate_vectorlike.
> Note, however, that these two datapoints differ more than just by how
> memory is allocated.
> 
> But 5% CPU time spend allocating memory is not insignificant.

Once again, it isn't necessarily memory allocation per se.  For
example, it could be find_suspicious_object_in_range, called from
allocate_vectorlike.

> Sure. Though my argument was less about how long Emacs spends allocating
> memory and more about how frequently a typical Elisp code requests such
> allocations. I have a gut feeling that even if taking short time,
> frequent interrupts may create intermittent typing delays.

I very much doubt these interrupts are because Emacs waits for memory
allocation.

> >> > ... But the global lock used by the Lisp threads we have is actually
> >> > such a lock, and the results are well known.
> >> 
> >> To be fair, global lock is an extreme worst-case scenario.
> >
> > If you consider the fact that the global state in Emacs is huge, maybe
> > it is a good approximation to what will need to be locked anyway?
> 
> Not every thread will need to use global state, except maybe memory
> allocation. Or do I miss an elephant in the room?
> 
> > You forget buffers, windows, frames, variables, and other global stuff.
> 
> Those will only matter when we try to access them from multiple threads,
> no?

Aren't we talking precisely about several threads running
concurrently?

> If a thread is working with a temporary buffer and locks it, that
> buffer has almost 0 chance to be accessed by another thread.

But "working on a buffer" requires access and modification of many
global structures.  Just walk the code in set-buffer and its
subroutines, and you will see that.

> Same with variables - even if some global variable needs to be locked,
> it is unlikely that it will need to be accessed by another thread.

I think you misunderstand the frequency of such collisions.
case-fold-search comes to mind.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 16:36                               ` Ihor Radchenko
@ 2023-07-06 17:53                                 ` Eli Zaretskii
  0 siblings, 0 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 17:53 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 16:36:04 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> AFAIK, process communication is now implemented using buffers that, even
> >> in the absence of coding system, index the data stream into byte array.
> >
> > Yes, but isn't binary data also a stream of bytes?
> 
> It is, but storing that data in buffer will involve non-trivial extra
> memory allocation.

And in your memcpy idea, from where will the buffer come to which you
copy? won't you need to allocate memory?

Besides, if you need to insert reasonably small amounts of data, the
Emacs buffer-with-gap model avoids allocating too much, sometimes
nothing at all.

> >> I am not sure if it is something that can be directly fed to memcpy (thus
> >> avoiding too much of extra cost for passing Lisp data around).
> >
> > If you don't want the incoming data to be inserted into a buffer or
> > produce a string from it, then what do you want to do with it instead?
> > To use something in Emacs, we _must_ make some Lisp object out of it,
> > right?
> 
> What I had in mind is, for example, memcpy wide_int_object -> child
> process -> memcpy to child process own heap.

Emacs Lisp objects are never just one int.  The integer you see in C
are just a kind of handles -- they are pointers in disguise, and the
pointer points to a reasonably large struct.

> So that we do not need to go through creating a new wide_int object in
> the child process.

I don't see how you can avoid that.  I feel that there's some serious
misunderstanding here.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 16:17                                               ` Ihor Radchenko
@ 2023-07-06 18:19                                                 ` Eli Zaretskii
  2023-07-07 12:04                                                   ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-06 18:19 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Thu, 06 Jul 2023 16:17:02 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> This does not change my understanding.
> >> Locking should prevent manipulations with object data over the time span
> >> the code expects the object to be unchanged.
> >
> > That could defeat GC, should Emacs decide to run it while the lock is
> > in place.
> 
> May you elaborate?

GC doesn't only free memory used by dead objects.  It also performs
bookkeeping on live objects: compacts data of strings, relocates text
of buffers, compacts the gap in buffers where it became too large,
etc.  This bookkeeping is more important when Emacs is short on
memory: in those cases these bookkeeping tasks might mean the
difference between being able to keep the session healthy enough to
allow the user to shut down in an orderly fashion.

Locking objects means these bookkeeping tasks will be disabled.  That
could adversely affect the available memory and the memory footprint
in general.

> Of course, GC calls must not be done while Object lock is in place. But
> that's not too different from the existing requirement for GC calls -
> they are not sprinkled in arbitrary places.

Currently, once GC starts it is free to do all of its parts.  If
locking prevents GC altogether, your proposal is even worse than I
feared.  Especially since as long as some thread runs, some objects
will be locked, and GC will not be able to run at all.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05 13:10                   ` Ihor Radchenko
@ 2023-07-06 18:35                     ` Lynn Winebarger
  2023-07-07 11:48                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Lynn Winebarger @ 2023-07-06 18:35 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

On Wed, Jul 5, 2023 at 9:10 AM Ihor Radchenko <yantar92@posteo.net> wrote:
>
> Lynn Winebarger <owinebar@gmail.com> writes:
>
> > The best idea I've had for a general solution would be to make "concurrent"
> > versions of the fundamental lisp objects that act like immutable git
> > repositories, with the traditional versions of the objects acting as
> > working copies but only recording changes.  Then each checked out copy
> > could push charges back, and if the merge fails an exception would be
> > thrown in the thread of that working copy which the elisp code could decide
> > how to handle.  That would work for inter-process shared memory or plain
> > in-process memory between threads.  Then locks are only needed for updating
> > the main reference to the concurrent object.
>
> Honestly, it sounds overengineered.
> Even if not, it is probably easier to implement a more limited version
> first and only then think about fancier staff like you described (not
> that I understand your idea fully).
>

Maybe - I'm not claiming it's trivial.  I do think the locking for
that kind of design is generally much simpler and less prone to
deadlocks than trying to make operations on the current set of
fundamental objects individually atomic.  Given how many lisp objects,
e.g. buffers and functions, are referenceable by name through global
tables, it's difficult to see how emacs could ever have fine-grained
parallelism that's efficient, correct, and deadlock-free any other
way.

The inspiration for this approach (for me) was from reviewing the
version control section of the emacs manual on approaches to
concurrent access.  Version control systems like git are essentially
"concurrent editing in the large".  What is required for emacs is
"concurrent editing in the small", but the issues with sharing and
updating are very much the same.

Lynn



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 13:13                                       ` Ihor Radchenko
  2023-07-06 14:13                                         ` Eli Zaretskii
@ 2023-07-07  0:21                                         ` Po Lu
  1 sibling, 0 replies; 192+ messages in thread
From: Po Lu @ 2023-07-07  0:21 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Po Lu <luangruo@yahoo.com> writes:
>
>>>>>    LOCK_OBJECT (foo);
>>>>>    LOCK_OBJECT (XCAR (foo));
>>>> ...
>>> ...
>>> Do you mean that locking XCAR (foo) is unnecessary when foo is locked?
>>
>> No, that there is no need to lock a cons (or a vector, or anything else
>> with a fixed number of Lisp_Object slots) before reading or writing to
>> it.
>
> I feel confused here.
>
> My understanding is
>
>   CHECK_STRING (XCAR (foo));
>   <we do not want XCAR (foo) to be altered here>
>   foo = XSTRING (XCAR (foo));
>
> So, locking is needed to ensure that CHECK_STRING assertion remains valid.

No, what we want to make sure is that the same string whose type was
checked is extracted.  By not loading XCAR (foo) twice.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 15:09                           ` Ihor Radchenko
  2023-07-06 15:18                             ` Eli Zaretskii
@ 2023-07-07  0:22                             ` Po Lu
  1 sibling, 0 replies; 192+ messages in thread
From: Po Lu @ 2023-07-07  0:22 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I was looking at this from C perspective.

C distinguishes between text and binary data even less than POSIX...



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 15:01                               ` Ihor Radchenko
  2023-07-06 15:16                                 ` Eli Zaretskii
@ 2023-07-07  0:27                                 ` Po Lu
  2023-07-07 12:45                                   ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-07  0:27 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

>      3.37%  emacs         emacs                                  [.] allocate_vectorlike
>      2.90%  emacs         emacs                                  [.] buf_charpos_to_bytepos
>      2.82%  emacs         emacs                                  [.] find_interval

Out of all those functions, I think only these three will require some
form of interlocking.  So assuming that the Org parser is being run
concurrently, less than 10% of it will be unable to run simultaneously.

Admittedly these ballpark estimates are somewhat contrived, but they're
enough to get my point across.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 16:32                                   ` Ihor Radchenko
  2023-07-06 17:50                                     ` Eli Zaretskii
@ 2023-07-07  0:41                                     ` Po Lu
  2023-07-07 12:42                                       ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-07  0:41 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Same with variables - even if some global variable needs to be locked,
> it is unlikely that it will need to be accessed by another thread.

I doubt symbol value cells will need to be individually interlocked.
Emacs Lisp code will need to insert the appropriate instructions to
prevent the CPU from undertaking optimizations on load and store
operations on those cells when necessary.  Imagine a situation where a
secondary thread writes the result of an expensive computation to X, and
then sets a flag to indicate its completion:

(defvar X nil)
(defvar Y nil)

thread 1:

       (setq X (some-expensive-computation))
       ;; __machine_w_barrier ()
       (setq Y t)
       ;; On machines that don't do this automatically, flush the cache
       ;; eventually.

A second thread then waits for Y to be set, indicating completion:

       (when Y
	 ;; __machine_r_barrier ()
	 (do-something-with X))

If the barrier instructions are not inserted, Y could be set to t before
X is set to the result of the computation, and the main thread could
also load X prior to loading (and inspecting) Y.  But there would be no
chance of a previously read value of either X or Y appearing after a new
value becoming visible or a partially written value of X or Y being
read, forgoing the need for any locking being performed on the
individual symbols themselves.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 18:35                     ` Lynn Winebarger
@ 2023-07-07 11:48                       ` Ihor Radchenko
  0 siblings, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 11:48 UTC (permalink / raw)
  To: Lynn Winebarger; +Cc: Eli Zaretskii, emacs-devel

Lynn Winebarger <owinebar@gmail.com> writes:

>> Honestly, it sounds overengineered.
>> Even if not, it is probably easier to implement a more limited version
>> first and only then think about fancier staff like you described (not
>> that I understand your idea fully).
>>
>
> Maybe - I'm not claiming it's trivial.

It is not very clear for me how to implement your suggestion at all.
So, unless you do it yourself or elaborate in much more details, it will
not happen.
I am not even sure how it can work. Especially given that Elisp heap is
not a persistent structure.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 18:19                                                 ` Eli Zaretskii
@ 2023-07-07 12:04                                                   ` Ihor Radchenko
  2023-07-07 13:16                                                     ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 12:04 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> > That could defeat GC, should Emacs decide to run it while the lock is
>> > in place.
>> 
>> May you elaborate?
>
> GC doesn't only free memory used by dead objects.  It also performs
> bookkeeping on live objects: compacts data of strings, relocates text
> of buffers, compacts the gap in buffers where it became too large,
> etc.  This bookkeeping is more important when Emacs is short on
> memory: in those cases these bookkeeping tasks might mean the
> difference between being able to keep the session healthy enough to
> allow the user to shut down in an orderly fashion.

What you are describing will only affect subr primitives that work
directly with C structs and address space.

So, we can distinguish two locks: (1) low-level, only available to C
subroutines; (2) Elisp-level, where the lock merely prevents other Elisp
code from modifying the data. GC is safe to run when type-2 lock is in
place as it will never clear the data in use and never alter the data in
any way visible on Elisp level.

> Locking objects means these bookkeeping tasks will be disabled.  That
> could adversely affect the available memory and the memory footprint
> in general.

I do not think that it is that bad if we consider type-1 locks.

Consider two parallel threads:

----- 1 ------
(let ((i 0)) (while t (cl-incf i)))
--------------

----- 2 -----
(while t (read-char))
-------------

Both the threads will call eval_sub frequently that will trigger
maybe_gc.

I consider that maybe_gc, when decided that GC is necessary, but cannot
continue because an object is locked using type-1 lock, will pause the
current thread.

Let's consider the current thread to be thread 2 paused because thread 1
is doing (setq i ...) at the same time and locked object corresponding
to obarray variable slot for "i".

Thread 1 will continue executing until (very soon) it calls maybe_gc
itself. This time, no further object lock is active and gc may proceed,
continuing both the threads once GC is done.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-06 17:50                                     ` Eli Zaretskii
@ 2023-07-07 12:30                                       ` Ihor Radchenko
  2023-07-07 13:34                                         ` Eli Zaretskii
  2023-07-07 13:35                                         ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 12:30 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> So, my efforts did reduce the time spent in allocate_vectorlike.
>> Note, however, that these two datapoints differ more than just by how
>> memory is allocated.
>> 
>> But 5% CPU time spend allocating memory is not insignificant.
>
> Once again, it isn't necessarily memory allocation per se.  For
> example, it could be find_suspicious_object_in_range, called from
> allocate_vectorlike.

I did not have ENABLE_CHECKING in this benchmark.
It is just ./configure --with-native-compilation
So, find_suspicious_object_in_range should not run at all.

>> Sure. Though my argument was less about how long Emacs spends allocating
>> memory and more about how frequently a typical Elisp code requests such
>> allocations. I have a gut feeling that even if taking short time,
>> frequent interrupts may create intermittent typing delays.
>
> I very much doubt these interrupts are because Emacs waits for memory
> allocation.

I guess we can be optimistic. And if not, maybe need to have multiple heaps.

>> If a thread is working with a temporary buffer and locks it, that
>> buffer has almost 0 chance to be accessed by another thread.
>
> But "working on a buffer" requires access and modification of many
> global structures.  Just walk the code in set-buffer and its
> subroutines, and you will see that.

I was only able to identify the following:

interrupt_input_blocked
current_buffer
last_known_column_point

AFAIU, current_buffer might be made thread-local and
last_known_column_point can be made buffer-local.

interrupt_input_blocked is more tricky. But it is just one global state
variable. Surely we can find a solution to make it work with multiple threads.

>> Same with variables - even if some global variable needs to be locked,
>> it is unlikely that it will need to be accessed by another thread.
>
> I think you misunderstand the frequency of such collisions.
> case-fold-search comes to mind.

How so? What is the problem with a buffer-local variable that is rarely
set directly (other than by major modes)? let-binding is common, but it
is not a problem.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07  0:41                                     ` Po Lu
@ 2023-07-07 12:42                                       ` Ihor Radchenko
  2023-07-07 13:31                                         ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 12:42 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> Same with variables - even if some global variable needs to be locked,
>> it is unlikely that it will need to be accessed by another thread.
>
> I doubt symbol value cells will need to be individually interlocked.
> Emacs Lisp code will need to insert the appropriate instructions to
> prevent the CPU from undertaking optimizations on load and store
> operations on those cells when necessary. ...

I had a different simple scenario in mind:

(let ((oldval case-fold-search))
  (unwind-protect
    (progn (setq case-fold-search nil)
      ;; We surely do not want the value of `case-fold-search' to be
      ;; changed in the middle of `expensive-computation'.
      (expensive-computation))
    (setq case-fold-search oldval))

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07  0:27                                 ` Po Lu
@ 2023-07-07 12:45                                   ` Ihor Radchenko
  0 siblings, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 12:45 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>>      3.37%  emacs         emacs                                  [.] allocate_vectorlike
>>      2.90%  emacs         emacs                                  [.] buf_charpos_to_bytepos
>>      2.82%  emacs         emacs                                  [.] find_interval
>
> Out of all those functions, I think only these three will require some
> form of interlocking.  So assuming that the Org parser is being run
> concurrently, less than 10% of it will be unable to run simultaneously.

No, the whole buffer will need to be locked for modification for the
duration of the parsing. Or the parser state info about AST buffer
positions will be broken.

But my point was not about the details of how Org parser works. I just
wanted to show that memory allocation is not necessarily negligible.
Is it slow enough to block interlocking idea? Maybe, maybe not.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 12:04                                                   ` Ihor Radchenko
@ 2023-07-07 13:16                                                     ` Eli Zaretskii
  2023-07-07 14:29                                                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-07 13:16 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Fri, 07 Jul 2023 12:04:36 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > GC doesn't only free memory used by dead objects.  It also performs
> > bookkeeping on live objects: compacts data of strings, relocates text
> > of buffers, compacts the gap in buffers where it became too large,
> > etc.  This bookkeeping is more important when Emacs is short on
> > memory: in those cases these bookkeeping tasks might mean the
> > difference between being able to keep the session healthy enough to
> > allow the user to shut down in an orderly fashion.
> 
> What you are describing will only affect subr primitives that work
> directly with C structs and address space.

But that's how _everything_ works in Emacs.  No Lisp runs except by
calling primitives.

> So, we can distinguish two locks: (1) low-level, only available to C
> subroutines; (2) Elisp-level, where the lock merely prevents other Elisp
> code from modifying the data. GC is safe to run when type-2 lock is in
> place as it will never clear the data in use and never alter the data in
> any way visible on Elisp level.

Emacs doesn't know whether some C code which runs was invoked from C
or from Lisp.  (Basically, everything is invoked from Lisp, one way or
another, as soon as we call recursive-edit from 'main' for the first
time after startup.)

> > Locking objects means these bookkeeping tasks will be disabled.  That
> > could adversely affect the available memory and the memory footprint
> > in general.
> 
> I do not think that it is that bad if we consider type-1 locks.

There are no type-1 and type-2 locks.  They are indistinguishable.

> Let's consider the current thread to be thread 2 paused because thread 1
> is doing (setq i ...) at the same time and locked object corresponding
> to obarray variable slot for "i".
> 
> Thread 1 will continue executing until (very soon) it calls maybe_gc
> itself. This time, no further object lock is active and gc may proceed,
> continuing both the threads once GC is done.

You are trying to solve what constitutes a very small, almost
negligible, part of the problem.  The elephant in the room is
something else.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 12:42                                       ` Ihor Radchenko
@ 2023-07-07 13:31                                         ` Po Lu
  0 siblings, 0 replies; 192+ messages in thread
From: Po Lu @ 2023-07-07 13:31 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Po Lu <luangruo@yahoo.com> writes:
>
>> Ihor Radchenko <yantar92@posteo.net> writes:
>>
>>> Same with variables - even if some global variable needs to be locked,
>>> it is unlikely that it will need to be accessed by another thread.
>>
>> I doubt symbol value cells will need to be individually interlocked.
>> Emacs Lisp code will need to insert the appropriate instructions to
>> prevent the CPU from undertaking optimizations on load and store
>> operations on those cells when necessary. ...
>
> I had a different simple scenario in mind:
>
> (let ((oldval case-fold-search))
>   (unwind-protect
>     (progn (setq case-fold-search nil)
>       ;; We surely do not want the value of `case-fold-search' to be
>       ;; changed in the middle of `expensive-computation'.
>       (expensive-computation))
>     (setq case-fold-search oldval))

That's easy: case-fold-search can be made local to each thread.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 12:30                                       ` Ihor Radchenko
@ 2023-07-07 13:34                                         ` Eli Zaretskii
  2023-07-07 15:17                                           ` Ihor Radchenko
  2023-07-07 13:35                                         ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-07 13:34 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Fri, 07 Jul 2023 12:30:16 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> But 5% CPU time spend allocating memory is not insignificant.
> >
> > Once again, it isn't necessarily memory allocation per se.  For
> > example, it could be find_suspicious_object_in_range, called from
> > allocate_vectorlike.
> 
> I did not have ENABLE_CHECKING in this benchmark.
> It is just ./configure --with-native-compilation
> So, find_suspicious_object_in_range should not run at all.

Then maybe you should invest some serious time looking into this and
figuring out why this happens.  Although in my book 5% of run time or
even 10% of run time is not the first place where I'd look for
optimizations.

> maybe need to have multiple heaps.

All modern implementation of malloc already do use several different
heaps internally.

> >> If a thread is working with a temporary buffer and locks it, that
> >> buffer has almost 0 chance to be accessed by another thread.
> >
> > But "working on a buffer" requires access and modification of many
> > global structures.  Just walk the code in set-buffer and its
> > subroutines, and you will see that.
> 
> I was only able to identify the following:
> 
> interrupt_input_blocked
> current_buffer
> last_known_column_point

There are much more:

  buffer-alist
  buffer's base-buffer
  buffer's undo-list
  buffer's point and begv/zv markers
  buffer's marker list
  buffer's local variables

(Where the above says "buffer's", it means the buffer that was current
before set-buffer.)

> AFAIU, current_buffer might be made thread-local and
> last_known_column_point can be made buffer-local.

The current buffer is already thread-local.

> interrupt_input_blocked is more tricky. But it is just one global state
> variable. Surely we can find a solution to make it work with multiple threads.

Yes, but we have just looked at a single primitive: set-buffer.  Once
in the buffer, any useful Lisp program will do gobs of stuff, and each
one of those accesses more and more globals.  How do you protect all
that in a 100% reliable way? by identifying the variables and
structures one by one? what if tomorrow some change in Emacs adds one
more?

> >> Same with variables - even if some global variable needs to be locked,
> >> it is unlikely that it will need to be accessed by another thread.
> >
> > I think you misunderstand the frequency of such collisions.
> > case-fold-search comes to mind.
> 
> How so? What is the problem with a buffer-local variable that is rarely
> set directly (other than by major modes)? let-binding is common, but it
> is not a problem.

Searching for "setq case-fold-search" finds more than 30 hits in Emacs
alone.  And this variable is just an example.

Like I said: your mental model of the Emacs global state is too
optimistic.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 12:30                                       ` Ihor Radchenko
  2023-07-07 13:34                                         ` Eli Zaretskii
@ 2023-07-07 13:35                                         ` Po Lu
  2023-07-07 15:31                                           ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-07 13:35 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> AFAIU, current_buffer might be made thread-local and

It _is_ thread local.

> interrupt_input_blocked is more tricky. But it is just one global state
> variable. Surely we can find a solution to make it work with multiple threads.

interrupt_input_blocked should only be relevant to the thread reading
input, i.e. the main thread.

The problem with buffer modification lies in two threads writing to the
same buffer at the same time, changing not only the buffer text, but
also PT, ZV, GPT, its markers and its overlay lists.  No two threads may
be allowed to modify the buffer simultaneously.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 13:16                                                     ` Eli Zaretskii
@ 2023-07-07 14:29                                                       ` Ihor Radchenko
  2023-07-07 14:47                                                         ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 14:29 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> What you are describing will only affect subr primitives that work
>> directly with C structs and address space.
>
> But that's how _everything_ works in Emacs.  No Lisp runs except by
> calling primitives.

Looks like we have some misunderstanding here, because I do not clearly
see how this kind of catch-all argument can be obvious.

>> So, we can distinguish two locks: (1) low-level, only available to C
>> subroutines; (2) Elisp-level, where the lock merely prevents other Elisp
>> code from modifying the data. GC is safe to run when type-2 lock is in
>> place as it will never clear the data in use and never alter the data in
>> any way visible on Elisp level.
>
> Emacs doesn't know whether some C code which runs was invoked from C
> or from Lisp.  (Basically, everything is invoked from Lisp, one way or
> another, as soon as we call recursive-edit from 'main' for the first
> time after startup.)

Let me elaborate.

What GC is doing may affect C pointers to internal representations of
Elisp objects. But never the Lisp representations.

So, GC running only matters during a subroutine execution. And not every
subroutine - just for a subset where we directly work with internal
object structs.

The subroutines that are GC-sensitive will need to set and release the
object lock before/after they are done working with that object. That
object lock type will be set in C code directly and will not be
available from Elisp.

This approach will, in the worst case, delay the GC by N_threads *
time_between_maybe_gc_calls_in_code. This is a rather small price to pay
in my book. GC is much less frequent (orders less) than the time between
calls to maybe_gc.

>> I do not think that it is that bad if we consider type-1 locks.
>
> There are no type-1 and type-2 locks.  They are indistinguishable.

I suggest to create two distinguishable locks - one that prevents GC and
one that does not. Type-1 will ever only be set from some subset of
C subroutines and will generally not lock for too long. Type-2 can be
set from the Elisp code, can hold for a long time, but will not prevent
GC.

> You are trying to solve what constitutes a very small, almost
> negligible, part of the problem.  The elephant in the room is
> something else.

Ok. Please, describe the elephant in details. Then, we will be able to
focus on this real big problem.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 14:29                                                       ` Ihor Radchenko
@ 2023-07-07 14:47                                                         ` Eli Zaretskii
  2023-07-07 15:21                                                           ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-07 14:47 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Fri, 07 Jul 2023 14:29:48 +0000
> 
> What GC is doing may affect C pointers to internal representations of
> Elisp objects. But never the Lisp representations.

What do you mean by "Lisp representation"?

> So, GC running only matters during a subroutine execution. And not every
> subroutine - just for a subset where we directly work with internal
> object structs.

We always do work with the internal object structs.

> The subroutines that are GC-sensitive will need to set and release the
> object lock before/after they are done working with that object. That
> object lock type will be set in C code directly and will not be
> available from Elisp.

You are describing something that is not Emacs.

> > You are trying to solve what constitutes a very small, almost
> > negligible, part of the problem.  The elephant in the room is
> > something else.
> 
> Ok. Please, describe the elephant in details.

I already did: it's the huge global state used implicitly by every
Lisp program out there.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 13:34                                         ` Eli Zaretskii
@ 2023-07-07 15:17                                           ` Ihor Radchenko
  2023-07-07 19:31                                             ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 15:17 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> maybe need to have multiple heaps.
>
> All modern implementation of malloc already do use several different
> heaps internally.

I was talking about Elisp heaps.
AFAIU, Elisp memory management is conceptually single-threaded.

>> I was only able to identify the following:
>> 
>> interrupt_input_blocked
>> current_buffer
>> last_known_column_point
>
> There are much more:
>
>   buffer-alist

I do not see how this is a problem to lock/unlock this variable.

>   buffer's base-buffer

I do not see it. May you point me to where this is changed?

>   buffer's undo-list

That's just a synchronization between old_buffer and
old_buffer->base_buffer.
I am not 100% sure why it is necessary to be done this way and manually
instead of making undo-list values in indirect buffers point to base
buffer.

>   buffer's point and begv/zv markers

AFAIU, these store the last point position and narrowing state.
I do not see much problem here, except a need to lock these variables
while writing them. They will not affect PT, BEGZ, and ZV in other
threads, even if those operate on the same buffer now.

>   buffer's marker list

May you point me where it is?

>   buffer's local variables

I admit that I do not understand what the following comment is talking
about:

  /* Look down buffer's list of local Lisp variables
     to find and update any that forward into C variables.  */

>> AFAIU, current_buffer might be made thread-local and
>> last_known_column_point can be made buffer-local.
>
> The current buffer is already thread-local.

Thanks for the pointer. I did not expect
#define current_buffer (current_thread->m_current_buffer)

>> interrupt_input_blocked is more tricky. But it is just one global state
>> variable. Surely we can find a solution to make it work with multiple threads.
>
> Yes, but we have just looked at a single primitive: set-buffer.  Once
> in the buffer, any useful Lisp program will do gobs of stuff, and each
> one of those accesses more and more globals.  How do you protect all
> that in a 100% reliable way? by identifying the variables and
> structures one by one? what if tomorrow some change in Emacs adds one
> more?

This sounds like a problem that is already solved by any program that
uses async threads. Maybe Po Lu can provide good insights.

>> > I think you misunderstand the frequency of such collisions.
>> > case-fold-search comes to mind.
>> 
>> How so? What is the problem with a buffer-local variable that is rarely
>> set directly (other than by major modes)? let-binding is common, but it
>> is not a problem.
>
> Searching for "setq case-fold-search" finds more than 30 hits in Emacs
> alone.  And this variable is just an example.

These are mostly major/minor modes and constructs like

(setq case-fold-search ...)
(do staff)
(setq case-fold-search old-value)

The last one is legitimate problem with logic. Although not much
different from Elisp threads doing the same and changing buffer-local
variables in the midst of other thread running in the same buffer.
So, I do not see how this should prevent async threads. We just need to
take care about setq not attempting to write into buffer-local variables
at the same time, which is solved by locking.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 14:47                                                         ` Eli Zaretskii
@ 2023-07-07 15:21                                                           ` Ihor Radchenko
  2023-07-07 18:04                                                             ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 15:21 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> What GC is doing may affect C pointers to internal representations of
>> Elisp objects. But never the Lisp representations.
>
> What do you mean by "Lisp representation"?

We clearly misunderstand each other.
May you please provide a concrete example how running GC breaks things?

>> Ok. Please, describe the elephant in details.
>
> I already did: it's the huge global state used implicitly by every
> Lisp program out there.

I am interested in specific details. The way you say it now is general
and sounds like "it is impossible and there is no point trying to do
anything".

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 13:35                                         ` Po Lu
@ 2023-07-07 15:31                                           ` Ihor Radchenko
  2023-07-08  0:44                                             ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 15:31 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> interrupt_input_blocked is more tricky. But it is just one global state
>> variable. Surely we can find a solution to make it work with multiple threads.
>
> interrupt_input_blocked should only be relevant to the thread reading
> input, i.e. the main thread.

So, we can hold when interrupt_input_blocked is changed until the thread
becomes main thread?

> The problem with buffer modification lies in two threads writing to the
> same buffer at the same time, changing not only the buffer text, but
> also PT, ZV, GPT, its markers and its overlay lists.  No two threads may
> be allowed to modify the buffer simultaneously.

Hmm. What about locking thread->current_buffer for all other threads?
This appears to solve the majority of the discussed problems.

I am not sure how to deal with buffer->pt for multiple threads running
in the same buffer.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 15:21                                                           ` Ihor Radchenko
@ 2023-07-07 18:04                                                             ` Eli Zaretskii
  2023-07-07 18:24                                                               ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-07 18:04 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Fri, 07 Jul 2023 15:21:04 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> What GC is doing may affect C pointers to internal representations of
> >> Elisp objects. But never the Lisp representations.
> >
> > What do you mean by "Lisp representation"?
> 
> We clearly misunderstand each other.
> May you please provide a concrete example how running GC breaks things?

I already did: see my description of compacting strings, relocating
buffer text, and other stuff GC does.

> >> Ok. Please, describe the elephant in details.
> >
> > I already did: it's the huge global state used implicitly by every
> > Lisp program out there.
> 
> I am interested in specific details. The way you say it now is general
> and sounds like "it is impossible and there is no point trying to do
> anything".

That's indeed what I want to say.  You cannot have true concurrency as
long as Emacs Lisp programs use this huge global state.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 18:04                                                             ` Eli Zaretskii
@ 2023-07-07 18:24                                                               ` Ihor Radchenko
  2023-07-07 19:36                                                                 ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 18:24 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> May you please provide a concrete example how running GC breaks things?
>
> I already did: see my description of compacting strings, relocating
> buffer text, and other stuff GC does.

But how exactly does, say, relocating buffer text can break things? May
you provide a pseudo-code example?

>> I am interested in specific details. The way you say it now is general
>> and sounds like "it is impossible and there is no point trying to do
>> anything".
>
> That's indeed what I want to say.  You cannot have true concurrency as
> long as Emacs Lisp programs use this huge global state.

Then the question is: can the global state be reduced?
May it be acceptable to have a limited concurrency where async threads
are only allowed to modify a portion of the global state?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 15:17                                           ` Ihor Radchenko
@ 2023-07-07 19:31                                             ` Eli Zaretskii
  2023-07-07 20:01                                               ` Ihor Radchenko
  2023-07-08  0:51                                               ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-07 19:31 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Fri, 07 Jul 2023 15:17:23 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> maybe need to have multiple heaps.
> >
> > All modern implementation of malloc already do use several different
> > heaps internally.
> 
> I was talking about Elisp heaps.

I don't understand what you mean by "Elisp heaps".  Emacs allocates
memory by using the system's memory-allocation routines.  We don't
have our own heaps.

> AFAIU, Elisp memory management is conceptually single-threaded.

Again, I don't understand why you say that and what you mean by that.
We just call malloc, and malloc on modern systems is thread-safe.

> > There are much more:
> >
> >   buffer-alist
> 
> I do not see how this is a problem to lock/unlock this variable.

So your solution to each such problem is to lock variables?  If so,
you will end up locking a lot of them, and how is this different from
using the global lock we do today with Lisp threads?

> >   buffer's base-buffer
> 
> I do not see it. May you point me to where this is changed?

See set_buffer_internal_2.

How do you investigate this stuff?  I type M-. on every macro and
function call I see, recursively, and look what they do.  If you do
the same, how come you overlook all these details?  And if you do not
use M-., how do you expect to learn what the code does internally?

> >   buffer's undo-list
> 
> That's just a synchronization between old_buffer and
> old_buffer->base_buffer.
> I am not 100% sure why it is necessary to be done this way and manually
> instead of making undo-list values in indirect buffers point to base
> buffer.

So you are now saying that code which worked in Emacs for decades does
unnecessary stuff, and should be removed or ignored?

How is it useful, in the context of this discussion, to take such a
stance?  IMO, we should assume that whatever the current code does it
does for a reason, and look at the effects of concurrency on the code
as it is.

> >   buffer's point and begv/zv markers
> 
> AFAIU, these store the last point position and narrowing state.
> I do not see much problem here, except a need to lock these variables
> while writing them. They will not affect PT, BEGZ, and ZV in other
> threads, even if those operate on the same buffer now.

Oh, yes, they will: see fetch_buffer_markers, called by
set_buffer_internal_2.

> >   buffer's marker list
> 
> May you point me where it is?

In fetch_buffer_markers.  Again, I don't understand how you missed
that.

> >   buffer's local variables
> 
> I admit that I do not understand what the following comment is talking
> about:
> 
>   /* Look down buffer's list of local Lisp variables
>      to find and update any that forward into C variables.  */

The C code accesses some buffer-local variables via Vfoo_bar C
variables.  Those need to be updated when the current buffer changes.

> > Yes, but we have just looked at a single primitive: set-buffer.  Once
> > in the buffer, any useful Lisp program will do gobs of stuff, and each
> > one of those accesses more and more globals.  How do you protect all
> > that in a 100% reliable way? by identifying the variables and
> > structures one by one? what if tomorrow some change in Emacs adds one
> > more?
> 
> This sounds like a problem that is already solved by any program that
> uses async threads. Maybe Po Lu can provide good insights.

Programs that use async threads avoid global variables like the
plague.  Emacs is full of them.

> > Searching for "setq case-fold-search" finds more than 30 hits in Emacs
> > alone.  And this variable is just an example.
> 
> These are mostly major/minor modes and constructs like
> 
> (setq case-fold-search ...)
> (do staff)
> (setq case-fold-search old-value)
> 
> The last one is legitimate problem with logic. Although not much
> different from Elisp threads doing the same and changing buffer-local
> variables in the midst of other thread running in the same buffer.
> So, I do not see how this should prevent async threads. We just need to
> take care about setq not attempting to write into buffer-local variables
> at the same time, which is solved by locking.

That "we just need to" is the problem, because it is multiplied by the
number of such variables, and they are a lot in Emacs.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 18:24                                                               ` Ihor Radchenko
@ 2023-07-07 19:36                                                                 ` Eli Zaretskii
  2023-07-07 20:05                                                                   ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-07 19:36 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Fri, 07 Jul 2023 18:24:04 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> May you please provide a concrete example how running GC breaks things?
> >
> > I already did: see my description of compacting strings, relocating
> > buffer text, and other stuff GC does.
> 
> But how exactly does, say, relocating buffer text can break things? May
> you provide a pseudo-code example?

If by "lock the buffer" you mean that buffer text cannot be relocated,
then you hurt GC and eventually the Emacs memory footprint.  If you
consider allowing relocation when the buffer is locked, then some of
the threads will be surprised when they try to resume accessing the
buffer text.

> >> I am interested in specific details. The way you say it now is general
> >> and sounds like "it is impossible and there is no point trying to do
> >> anything".
> >
> > That's indeed what I want to say.  You cannot have true concurrency as
> > long as Emacs Lisp programs use this huge global state.
> 
> Then the question is: can the global state be reduced?

By what measures?  Please suggest something concrete here.

> May it be acceptable to have a limited concurrency where async threads
> are only allowed to modify a portion of the global state?

I don't see how one can write a useful Lisp program with such
restrictions.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 19:31                                             ` Eli Zaretskii
@ 2023-07-07 20:01                                               ` Ihor Radchenko
  2023-07-08  6:50                                                 ` Eli Zaretskii
  2023-07-08  0:51                                               ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 20:01 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> I was talking about Elisp heaps.
>
> I don't understand what you mean by "Elisp heaps".  Emacs allocates
> memory by using the system's memory-allocation routines.  We don't
> have our own heaps.
>
>> AFAIU, Elisp memory management is conceptually single-threaded.
>
> Again, I don't understand why you say that and what you mean by that.
> We just call malloc, and malloc on modern systems is thread-safe.

Does it mean that we can safely call, for example, Fcons asynchronously?

(I am saying the above because my understanding is limited, hoping that
you can give some pointers when I happen to be wrong.)

>> >   buffer-alist
>> 
>> I do not see how this is a problem to lock/unlock this variable.
>
> So your solution to each such problem is to lock variables?  If so,
> you will end up locking a lot of them, and how is this different from
> using the global lock we do today with Lisp threads?

The idea is to prevent simultaneous write, which will only lock for a
small fraction of time.

It is not always sufficient, of course. When the code expects the value
to be unchanged, the lock will take much longer, and may cause global
locking eventually.

>> >   buffer's base-buffer
>> 
>> I do not see it. May you point me to where this is changed?
>
> See set_buffer_internal_2.
>
> How do you investigate this stuff?  I type M-. on every macro and
> function call I see, recursively, and look what they do.  If you do
> the same, how come you overlook all these details?  And if you do not
> use M-., how do you expect to learn what the code does internally?

Yes, I use M-. and C-x p g. And I do follow the code. But it does not
mean that I fully understand it, sorry.

And I still fail to see where base-buffer is _changed_. Is base buffer
ever supposed to be changed?

>> >   buffer's undo-list
>> 
>> That's just a synchronization between old_buffer and
>> old_buffer->base_buffer.
>> I am not 100% sure why it is necessary to be done this way and manually
>> instead of making undo-list values in indirect buffers point to base
>> buffer.
>
> So you are now saying that code which worked in Emacs for decades does
> unnecessary stuff, and should be removed or ignored?

No, I am saying that the current logic of updating the undo-list will not work
when multiple async threads are involved. It will no longer be safe to
assume that we can safely update undo-list right before/after switching
current_buffer.

So, I asked if an alternative approach could be used instead.

>> I admit that I do not understand what the following comment is talking
>> about:
>> 
>>   /* Look down buffer's list of local Lisp variables
>>      to find and update any that forward into C variables.  */
>
> The C code accesses some buffer-local variables via Vfoo_bar C
> variables.  Those need to be updated when the current buffer changes.

Now, when you explained this, it is also a big problem. Such C variables
are a global state that needs to be kept up to date. Async will break
the existing logic of these updates.

>> >   buffer's point and begv/zv markers
>> 
>> AFAIU, these store the last point position and narrowing state.
>> I do not see much problem here, except a need to lock these variables
>> while writing them. They will not affect PT, BEGZ, and ZV in other
>> threads, even if those operate on the same buffer now.
>
> Oh, yes, they will: see fetch_buffer_markers, called by
> set_buffer_internal_2.

Do you mean that in the existing cooperative Elisp threads, if one
thread moves the point and yields to other thread, the other thread will
be left with point in the same position (arbitrary, from the point of
view of this other thread)?

>> >   buffer's marker list
>> 
>> May you point me where it is?
>
> In fetch_buffer_markers.  Again, I don't understand how you missed
> that.

Is it buffer's marker list? I thought that you are referring to
BUF_MARKERS, not to PT, BEGV, and ZV.

>> This sounds like a problem that is already solved by any program that
>> uses async threads. Maybe Po Lu can provide good insights.
>
> Programs that use async threads avoid global variables like the
> plague.  Emacs is full of them.

Fair. I still hope that Po Lu can comment on this.

(Do note that my original proposal is not about "true" async thread and
it is Po Lu who were pushing for "proper interlocking". But the current
discussion is still helpful either way.).

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 19:36                                                                 ` Eli Zaretskii
@ 2023-07-07 20:05                                                                   ` Ihor Radchenko
  2023-07-08  7:05                                                                     ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-07 20:05 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> But how exactly does, say, relocating buffer text can break things? May
>> you provide a pseudo-code example?
>
> ...  If you
> consider allowing relocation when the buffer is locked, then some of
> the threads will be surprised when they try to resume accessing the
> buffer text.

Can you please provide an example about "surprised"? Do you mean that
buffer->pt will no longer be accurate? Something else?

>> Then the question is: can the global state be reduced?
>
> By what measures?  Please suggest something concrete here.

By transforming some of the global state variables into thread-local
variables.

>> May it be acceptable to have a limited concurrency where async threads
>> are only allowed to modify a portion of the global state?
>
> I don't see how one can write a useful Lisp program with such
> restrictions.

Pure, side-effect-free functions, for example.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 15:31                                           ` Ihor Radchenko
@ 2023-07-08  0:44                                             ` Po Lu
  2023-07-08  4:29                                               ` tomas
  2023-07-08  7:21                                               ` Eli Zaretskii
  0 siblings, 2 replies; 192+ messages in thread
From: Po Lu @ 2023-07-08  0:44 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> So, we can hold when interrupt_input_blocked is changed until the thread
> becomes main thread?

IMHO there should only be a single main thread processing input and
display, since that's required by most GUI toolkits.

> Hmm. What about locking thread->current_buffer for all other threads?
> This appears to solve the majority of the discussed problems.

If you're referring to prohibiting two threads from sharing the same
current_buffer, I doubt that's necessary.

> I am not sure how to deal with buffer->pt for multiple threads running
> in the same buffer.

C functions which modify the buffer should be interlocked to prevent
them from running simultaneously with other modifications to the same
buffer.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 19:31                                             ` Eli Zaretskii
  2023-07-07 20:01                                               ` Ihor Radchenko
@ 2023-07-08  0:51                                               ` Po Lu
  2023-07-08  4:18                                                 ` tomas
  2023-07-08  6:25                                                 ` Eli Zaretskii
  1 sibling, 2 replies; 192+ messages in thread
From: Po Lu @ 2023-07-08  0:51 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Ihor Radchenko, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> Programs that use async threads avoid global variables like the
> plague.  Emacs is full of them.

That's not true.  Look at any modern Unix kernel, and their detailed
locking around traditional Unix data structures, such as allproc, the
run queue, the vnode cache, and et cetera.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  0:51                                               ` Po Lu
@ 2023-07-08  4:18                                                 ` tomas
  2023-07-08  5:51                                                   ` Po Lu
  2023-07-08  6:25                                                 ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: tomas @ 2023-07-08  4:18 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1015 bytes --]

On Sat, Jul 08, 2023 at 08:51:48AM +0800, Po Lu wrote:
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > Programs that use async threads avoid global variables like the
> > plague.  Emacs is full of them.
> 
> That's not true.  Look at any modern Unix kernel, and their detailed
> locking around traditional Unix data structures, such as allproc, the
> run queue, the vnode cache, and et cetera.

The tendency, though, seems to be to avoid interlocking as much as possible
and use "transactional" data structures [1]. Which is an order of magnitude
more "interesting" :-)

But this is a kernel. I have the impression that this discussion has exploded
in scope, from taking the blocking out of "long" (network, external procs)
waits to fine-grained parallelism and multithreading.

I think the first makes sense in Emacs, the second... not so much. But that's
just one random opinion :-)

Cheers

[1] https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html
-- 
t

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  0:44                                             ` Po Lu
@ 2023-07-08  4:29                                               ` tomas
  2023-07-08  7:21                                               ` Eli Zaretskii
  1 sibling, 0 replies; 192+ messages in thread
From: tomas @ 2023-07-08  4:29 UTC (permalink / raw)
  To: Po Lu; +Cc: Ihor Radchenko, Eli Zaretskii, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 728 bytes --]

On Sat, Jul 08, 2023 at 08:44:17AM +0800, Po Lu wrote:
> Ihor Radchenko <yantar92@posteo.net> writes:
> 
> > So, we can hold when interrupt_input_blocked is changed until the thread
> > becomes main thread?
> 
> IMHO there should only be a single main thread processing input and
> display, since that's required by most GUI toolkits.

This is a hard lesson Sun had to learn while developing the GUI toolkits
for the Java platform. After trying hard to have a multithreaded GUI
(Swing, if I remember correctly [1]) they had to back out due to lots of
hard-to-chase bugs.

Since 
Cheers

[1] They had a strong motivation, since their hardware (remember Niagara?)
   was going the massive-parallel path
-- 
t

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  4:18                                                 ` tomas
@ 2023-07-08  5:51                                                   ` Po Lu
  2023-07-08  6:01                                                     ` tomas
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-08  5:51 UTC (permalink / raw)
  To: tomas; +Cc: emacs-devel

<tomas@tuxteam.de> writes:

> The tendency, though, seems to be to avoid interlocking as much as possible
> and use "transactional" data structures [1]. Which is an order of magnitude
> more "interesting" :-)

They are unfortunately not relevant to Emacs, since their use pertains
to programs that were designed to run in multiple processor environments
almost from the very start.  On the other hand, Unix is a large and
ancient program with vast amounts of global state, that was still
modified to run in SMP environments, making its development experiences
directly relevant to Emacs.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  5:51                                                   ` Po Lu
@ 2023-07-08  6:01                                                     ` tomas
  2023-07-08 10:02                                                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: tomas @ 2023-07-08  6:01 UTC (permalink / raw)
  To: Po Lu; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1036 bytes --]

On Sat, Jul 08, 2023 at 01:51:42PM +0800, Po Lu wrote:
> <tomas@tuxteam.de> writes:
> 
> > The tendency, though, seems to be to avoid interlocking as much as possible
> > and use "transactional" data structures [1]. Which is an order of magnitude
> > more "interesting" :-)
> 
> They are unfortunately not relevant to Emacs, since their use pertains
> to programs that were designed to run in multiple processor environments
> almost from the very start.  On the other hand, Unix is a large and
> ancient program with vast amounts of global state, that was still
> modified to run in SMP environments, making its development experiences
> directly relevant to Emacs.

My point, too. Very few user space applications need the techniques
discussed in that book. I'd even venture that very few need heavy
interlocking, but this has been a topic in this thread.

Reducing blocking waits for external stuff (processes, network) would
already be a lofty goal for Emacs, and far more attainable, I think.

Cheers
-- 
t

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  0:51                                               ` Po Lu
  2023-07-08  4:18                                                 ` tomas
@ 2023-07-08  6:25                                                 ` Eli Zaretskii
  2023-07-08  6:38                                                   ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08  6:25 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: Ihor Radchenko <yantar92@posteo.net>,  emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 08:51:48 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > Programs that use async threads avoid global variables like the
> > plague.  Emacs is full of them.
> 
> That's not true.  Look at any modern Unix kernel, and their detailed
> locking around traditional Unix data structures, such as allproc, the
> run queue, the vnode cache, and et cetera.

I said "programs", not "OSes".

It _is_ possible to have threads with global variables, but that
requires locking, which punishes performance.  We even do that in
Emacs, with Lisp threads.  I thought this discussion was about less
painful implementations.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  6:25                                                 ` Eli Zaretskii
@ 2023-07-08  6:38                                                   ` Ihor Radchenko
  2023-07-08  7:45                                                     ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-08  6:38 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Po Lu, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> ....  I thought this discussion was about less
> painful implementations.

My idea with isolated thread is similar to having a bunch of state
variables coped to the thread before executing it. Interlocking will
still be necessary if the isolated thread wants to do anything with the
actual global state (like buffer modification, for example).

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 20:01                                               ` Ihor Radchenko
@ 2023-07-08  6:50                                                 ` Eli Zaretskii
  2023-07-08 11:55                                                   ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08  6:50 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Fri, 07 Jul 2023 20:01:24 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> AFAIU, Elisp memory management is conceptually single-threaded.
> >
> > Again, I don't understand why you say that and what you mean by that.
> > We just call malloc, and malloc on modern systems is thread-safe.
> 
> Does it mean that we can safely call, for example, Fcons asynchronously?

Not necessarily, because Fcons is not just about allocating memory.

I think we once again have a misunderstanding here, because when you
say "memory allocation" you mean something very different than I do,
which is a call to malloc to get more memory from the system.  It
sounds like you think that Fcons _is_ memory allocation?  But if so,
this terminology is so confusing that it is not useful in a detailed
technical discussion such as this one.  We use the term "consing" to
refer to creation of Lisp objects, which includes memory allocation,
but also other stuff.

In particular, consing modifies memory blocks (already available to
Emacs, so no "memory allocation" per se) used to keep track of live
and dead Lisp objects, and those modifications cannot be concurrently
done by more than one thread, at least in some cases.

> (I am saying the above because my understanding is limited, hoping that
> you can give some pointers when I happen to be wrong.)

I'm happy to give pointers, once I understand pointers to what.
Before I have a chance of understanding that, we need to have a common
terminology, though.

> >> >   buffer-alist
> >> 
> >> I do not see how this is a problem to lock/unlock this variable.
> >
> > So your solution to each such problem is to lock variables?  If so,
> > you will end up locking a lot of them, and how is this different from
> > using the global lock we do today with Lisp threads?
> 
> The idea is to prevent simultaneous write, which will only lock for a
> small fraction of time.

If one thread writes to a data structure, reading from it could also
need to block, or else the reader will risk getting inconsistent data.
So this is not just about simultaneous writing, it's much more
general.

> And I still fail to see where base-buffer is _changed_. Is base buffer
> ever supposed to be changed?

Another thread might change it while this thread examines it.

> >> >   buffer's undo-list
> >> 
> >> That's just a synchronization between old_buffer and
> >> old_buffer->base_buffer.
> >> I am not 100% sure why it is necessary to be done this way and manually
> >> instead of making undo-list values in indirect buffers point to base
> >> buffer.
> >
> > So you are now saying that code which worked in Emacs for decades does
> > unnecessary stuff, and should be removed or ignored?
> 
> No, I am saying that the current logic of updating the undo-list will not work
> when multiple async threads are involved. It will no longer be safe to
> assume that we can safely update undo-list right before/after switching
> current_buffer.
> 
> So, I asked if an alternative approach could be used instead.

Undo records changes in text properties and markers, and those are
different in the indirect buffers from the base buffers.  Does this
explain why we cannot simply point to the base buffer?

If this is clear, then what other approach except locking do you
suggest for that?

> >> I admit that I do not understand what the following comment is talking
> >> about:
> >> 
> >>   /* Look down buffer's list of local Lisp variables
> >>      to find and update any that forward into C variables.  */
> >
> > The C code accesses some buffer-local variables via Vfoo_bar C
> > variables.  Those need to be updated when the current buffer changes.
> 
> Now, when you explained this, it is also a big problem. Such C variables
> are a global state that needs to be kept up to date. Async will break
> the existing logic of these updates.

Exactly.

> >> >   buffer's point and begv/zv markers
> >> 
> >> AFAIU, these store the last point position and narrowing state.
> >> I do not see much problem here, except a need to lock these variables
> >> while writing them. They will not affect PT, BEGZ, and ZV in other
> >> threads, even if those operate on the same buffer now.
> >
> > Oh, yes, they will: see fetch_buffer_markers, called by
> > set_buffer_internal_2.
> 
> Do you mean that in the existing cooperative Elisp threads, if one
> thread moves the point and yields to other thread, the other thread will
> be left with point in the same position (arbitrary, from the point of
> view of this other thread)?

That's one problem, yes.  There are others.  Emacs Lisp uses point,
both explicitly and implicitly, all over the board.  It is unthinkable
that a thread will find point not in a place where it last moved it.

> >> >   buffer's marker list
> >> 
> >> May you point me where it is?
> >
> > In fetch_buffer_markers.  Again, I don't understand how you missed
> > that.
> 
> Is it buffer's marker list? I thought that you are referring to
> BUF_MARKERS, not to PT, BEGV, and ZV.

Buffer's marker list are referenced in subroutines of
record_buffer_markers.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-07 20:05                                                                   ` Ihor Radchenko
@ 2023-07-08  7:05                                                                     ` Eli Zaretskii
  2023-07-08 10:53                                                                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08  7:05 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Fri, 07 Jul 2023 20:05:53 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> But how exactly does, say, relocating buffer text can break things? May
> >> you provide a pseudo-code example?
> >
> > ...  If you
> > consider allowing relocation when the buffer is locked, then some of
> > the threads will be surprised when they try to resume accessing the
> > buffer text.
> 
> Can you please provide an example about "surprised"? Do you mean that
> buffer->pt will no longer be accurate? Something else?

Not pt but the pointers to buffer text and the gap.  Those determine
the address of a given buffer position in memory, and are used when a
Lisp program accesses buffer text in any way.  GC can change them if
it decides to relocate buffer text or compact the gap.

> >> Then the question is: can the global state be reduced?
> >
> > By what measures?  Please suggest something concrete here.
> 
> By transforming some of the global state variables into thread-local
> variables.

Which variables can safely and usefully be made thread-local?  I
invite you to look at all the defvar's in the ELisp manual that are
not buffer-local, and consider whether making them thread-local will
make sense, i.e. will still allow you to write useful Lisp programs.
(And if we are thinking about more than one thread working on the same
buffer, then buffer-local variables are also part of this.)

As an exercise, how about finding _one_ variable routinely used in
Lisp programs, which you think can be made thread-local?  Then let's
talk about it.

> >> May it be acceptable to have a limited concurrency where async threads
> >> are only allowed to modify a portion of the global state?
> >
> > I don't see how one can write a useful Lisp program with such
> > restrictions.
> 
> Pure, side-effect-free functions, for example.

I don't see how this could be practically useful.  Besides the basic
question of whether a useful Lisp program can be written in Emacs
using only side-effect-free functions, there's the large body of
subroutines and primitives any Lisp program uses to do its job, and
how do you know which ones of them are side-effect-free or async-safe?
To take just one example which came up in recent discussions, look at
string-pixel-width.  Or even at string-width.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  0:44                                             ` Po Lu
  2023-07-08  4:29                                               ` tomas
@ 2023-07-08  7:21                                               ` Eli Zaretskii
  2023-07-08  7:48                                                 ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08  7:21 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 08:44:17 +0800
> 
> Ihor Radchenko <yantar92@posteo.net> writes:
> 
> > So, we can hold when interrupt_input_blocked is changed until the thread
> > becomes main thread?
> 
> IMHO there should only be a single main thread processing input and
> display, since that's required by most GUI toolkits.

That is already a problem, as long as we are talking about leaving
most of Emacs application code intact.  How do you ensure only the
main thread can process input and display?  A non-main thread can
easily call some function which prompts the user, e.g., with
yes-or-no-p, or force redisplay with sit-for, and what do you do when
that happens?

This is a problem even with the current Lisp threads, and we don't
have a satisfactory solution for it.  (We discussed this a couple of
years back, but didn't arrive at any useful conclusions, AFAIR.)

Personally, I don't believe this aspect can be solved without very
significant redesign of Emacs and -- what's more important -- without
rewriting many Lisp programs to adhere to the new design.  Searching
for yes-or-no-p and y-or-n-p are in Emacs alone brings 1500 hits.

> > Hmm. What about locking thread->current_buffer for all other threads?
> > This appears to solve the majority of the discussed problems.
> 
> If you're referring to prohibiting two threads from sharing the same
> current_buffer, I doubt that's necessary.
> 
> > I am not sure how to deal with buffer->pt for multiple threads running
> > in the same buffer.
> 
> C functions which modify the buffer should be interlocked to prevent
> them from running simultaneously with other modifications to the same
> buffer.

That's not enough!  Interlocking will prevent disastrous changes to
the buffer object which risk leaving the buffer in inconsistent state,
but it cannot prevent one thread from changing point under the feet of
another thread.  Consider this sequence:

  . thread A moves point to position P1
  . thread A yields
  . thread B moves point of the same buffer to position P2
  . thread B yields
  . thread A resumes and performs some processing assuming point is at P1

Without some kind of critical section, invoked on the Lisp level,
whereby moving point and the subsequent processing cannot be
interrupted, how will asynchronous processing by several threads that
use the same buffer ever work?  And please note that the above is
problematic even if none of the threads change buffer text, i.e. they
all are just _reading_ buffer text.

It follows that such asynchronous processing will have to be
explicitly accounted for on the level of Lisp programs, which means
thorough rewrite of most of Lisp code (and also a lot of C code).
IOW, we are no longer talking about some change to Emacs, we are
talking about rewriting most or all of Emacs!



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  6:38                                                   ` Ihor Radchenko
@ 2023-07-08  7:45                                                     ` Eli Zaretskii
  2023-07-08  8:16                                                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08  7:45 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Po Lu <luangruo@yahoo.com>, emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 06:38:26 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > ....  I thought this discussion was about less
> > painful implementations.
> 
> My idea with isolated thread is similar to having a bunch of state
> variables coped to the thread before executing it. Interlocking will
> still be necessary if the isolated thread wants to do anything with the
> actual global state (like buffer modification, for example).

How would we know which part(s) of the global state to copy, and how
will the Lisp program running in the thread know which variables it
can safely access?  If I am the Lisp programmer writing code for such
a thread, how can I know what is and what isn't allowed?  And what
happens if I do something that is not allowed?  And finally, does it
mean we cannot run existing Lisp programs in such threads, but must
program for them from scratch?



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  7:21                                               ` Eli Zaretskii
@ 2023-07-08  7:48                                                 ` Po Lu
  2023-07-08 10:02                                                   ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-08  7:48 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: yantar92, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> That is already a problem, as long as we are talking about leaving
> most of Emacs application code intact.  How do you ensure only the
> main thread can process input and display?  A non-main thread can
> easily call some function which prompts the user, e.g., with
> yes-or-no-p, or force redisplay with sit-for, and what do you do when
> that happens?

To signal an error.  Threads other than the main thread should not call
such functions, which is the approach taken by most toolkits and window
systems.  (X being a notable exception, where every Xlib display is
surrounded by one massive lock.)

This will mean that most user facing Lisp won't be able to run in
parallel with the main thread, but that can be fixed, given enough time.
And it's not disasterous, seeing as _no_ Lisp can presently run in
parallel with the main thread.

> That's not enough!  Interlocking will prevent disastrous changes to
> the buffer object which risk leaving the buffer in inconsistent state,
> but it cannot prevent one thread from changing point under the feet of
> another thread.  Consider this sequence:
>
>   . thread A moves point to position P1
>   . thread A yields
>   . thread B moves point of the same buffer to position P2
>   . thread B yields
>   . thread A resumes and performs some processing assuming point is at P1

Lisp code that is interested in making edits this way will need to
utilize synchronization mechanisms of their own, yes.

> Without some kind of critical section, invoked on the Lisp level,
> whereby moving point and the subsequent processing cannot be
> interrupted, how will asynchronous processing by several threads that
> use the same buffer ever work?  And please note that the above is
> problematic even if none of the threads change buffer text, i.e. they
> all are just _reading_ buffer text.
>
> It follows that such asynchronous processing will have to be
> explicitly accounted for on the level of Lisp programs, which means
> thorough rewrite of most of Lisp code (and also a lot of C code).

Insofar as that Lisp code is actually interested in making use of
multiple threads.

> IOW, we are no longer talking about some change to Emacs, we are
> talking about rewriting most or all of Emacs!

I think that as long as we make the C text editing primitives robust
against such problems, authors of Lisp code that needs to edit buffer
text from multiple threads will devise appropriate synchronization
procedures for their specific use cases.  For example, to parse a buffer
in the background, Semantic may chose to create a new thread with a
temporary copy of the buffer that is being read.  Or, Gnus might do the
same to fontify a Diff attachment in an article.  Lisp changes can all
be accomplished gradually, of course, whereas the C-level changes will
have to be completed all in one go.

Thanks.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  7:45                                                     ` Eli Zaretskii
@ 2023-07-08  8:16                                                       ` Ihor Radchenko
  2023-07-08 10:13                                                         ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-08  8:16 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> My idea with isolated thread is similar to having a bunch of state
>> variables coped to the thread before executing it. Interlocking will
>> still be necessary if the isolated thread wants to do anything with the
>> actual global state (like buffer modification, for example).
>
> How would we know which part(s) of the global state to copy, and how
> will the Lisp program running in the thread know which variables it
> can safely access?

This is a difficult question.
On one hand, it does not make sense to copy everything - it will imply
multiplying Emacs memory footprint.
On the other hand, we cannot know in advance which variables will
actually be used by a thread.

So, it can be something like copy-on-read - the child thread will copy
the necessary values from parent thread as running Elisp code needs
them.

By default, such copy will not be synchronized with the parent Emacs
thread, so that we do not need to worry about parent thread changing the
values asynchronously. 

Manually, it may also be allowed to create "remote" values that will
query the other thread: thread 1 will hold (foo = '(1 2 3)) and thread 2
will hold (foo = #<remote foo>). Then, thread 2 requesting to read/write
value will query thread 1.
The range of variables that can be made remote should be minimized and
possibly also restricted to a safe subset of variables.

> ... If I am the Lisp programmer writing code for such
> a thread, how can I know what is and what isn't allowed?

Everything is allowed, but global state changes will not necessarily
propagate to the parent Emacs thread. They will be confined to that
child async thread.

> ...And what
> happens if I do something that is not allowed?  And finally, does it
> mean we cannot run existing Lisp programs in such threads, but must
> program for them from scratch?

Non-trivial programs that need to modify the global Emacs state will
have to be written specially. But programs that only return a value will
work as usual, without a need to modify them.

The idea is similar to https://github.com/jwiegley/emacs-async (or to
`org-export-async-start'), but with more tight communication between
Emacs processes. The original emacs-async requires a new Emacs instance
that will also need to re-load all the necessary packages manually from
startup.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  6:01                                                     ` tomas
@ 2023-07-08 10:02                                                       ` Ihor Radchenko
  2023-07-08 19:39                                                         ` tomas
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-08 10:02 UTC (permalink / raw)
  To: tomas; +Cc: Po Lu, emacs-devel

tomas@tuxteam.de writes:

> Reducing blocking waits for external stuff (processes, network) would
> already be a lofty goal for Emacs, and far more attainable, I think.

Would you mind elaborating?
AFAIU, the bottlenecks in these scenarios are still on Lisp side and are
not specific to process/network handling.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  7:48                                                 ` Po Lu
@ 2023-07-08 10:02                                                   ` Eli Zaretskii
  2023-07-08 11:54                                                     ` Po Lu
  2023-07-08 12:01                                                     ` Ihor Radchenko
  0 siblings, 2 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08 10:02 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: yantar92@posteo.net,  emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 15:48:02 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > That is already a problem, as long as we are talking about leaving
> > most of Emacs application code intact.  How do you ensure only the
> > main thread can process input and display?  A non-main thread can
> > easily call some function which prompts the user, e.g., with
> > yes-or-no-p, or force redisplay with sit-for, and what do you do when
> > that happens?
> 
> To signal an error.

Great! that means in practice no existing Lisp program could ever run
in a non-main thread.  It isn't a very practical solution.

Besides, non-main threads do sometimes legitimately need to prompt
the user.  It is not a programmer's error when they do.

> Threads other than the main thread should not call
> such functions, which is the approach taken by most toolkits and window
> systems.  (X being a notable exception, where every Xlib display is
> surrounded by one massive lock.)

I don't think such a simplistic solution suits a program such as
Emacs.

> This will mean that most user facing Lisp won't be able to run in
> parallel with the main thread, but that can be fixed, given enough time.

Fixed how?

> > That's not enough!  Interlocking will prevent disastrous changes to
> > the buffer object which risk leaving the buffer in inconsistent state,
> > but it cannot prevent one thread from changing point under the feet of
> > another thread.  Consider this sequence:
> >
> >   . thread A moves point to position P1
> >   . thread A yields
> >   . thread B moves point of the same buffer to position P2
> >   . thread B yields
> >   . thread A resumes and performs some processing assuming point is at P1
> 
> Lisp code that is interested in making edits this way will need to
> utilize synchronization mechanisms of their own, yes.

The above doesn't do any editing, it just accesses buffer text without
changing it.

> > Without some kind of critical section, invoked on the Lisp level,
> > whereby moving point and the subsequent processing cannot be
> > interrupted, how will asynchronous processing by several threads that
> > use the same buffer ever work?  And please note that the above is
> > problematic even if none of the threads change buffer text, i.e. they
> > all are just _reading_ buffer text.
> >
> > It follows that such asynchronous processing will have to be
> > explicitly accounted for on the level of Lisp programs, which means
> > thorough rewrite of most of Lisp code (and also a lot of C code).
> 
> Insofar as that Lisp code is actually interested in making use of
> multiple threads.

Why are we talking about multiple threads at all? don't we want to
allow some Lisp code run from non-main threads?

> > IOW, we are no longer talking about some change to Emacs, we are
> > talking about rewriting most or all of Emacs!
> 
> I think that as long as we make the C text editing primitives robust
> against such problems, authors of Lisp code that needs to edit buffer
> text from multiple threads will devise appropriate synchronization
> procedures for their specific use cases.  For example, to parse a buffer
> in the background, Semantic may chose to create a new thread with a
> temporary copy of the buffer that is being read.  Or, Gnus might do the
> same to fontify a Diff attachment in an article.  Lisp changes can all
> be accomplished gradually, of course, whereas the C-level changes will
> have to be completed all in one go.

Using a snapshot of some global resource, such as buffer text, works
only up to a point, and basically prohibits many potentially
interesting uses of threads.  That's because such snapshotting assumes
no significant changes happen in the original objects while processing
the snapshot, and that is only sometimes true.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  8:16                                                       ` Ihor Radchenko
@ 2023-07-08 10:13                                                         ` Eli Zaretskii
  0 siblings, 0 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08 10:13 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 08:16:39 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> So, it can be something like copy-on-read - the child thread will copy
> the necessary values from parent thread as running Elisp code needs
> them.
> 
> By default, such copy will not be synchronized with the parent Emacs
> thread, so that we do not need to worry about parent thread changing the
> values asynchronously. 

When does such synchronization happen, though?

Imagine some long-running thread which does some periodical
processing: when and how will such a thread synchronize with the
actual state of the variables it needs?  How will this look from the
POV of Lisp code running in this thread?

(This proposal also means significant implementation problems.)

> Manually, it may also be allowed to create "remote" values that will
> query the other thread: thread 1 will hold (foo = '(1 2 3)) and thread 2
> will hold (foo = #<remote foo>). Then, thread 2 requesting to read/write
> value will query thread 1.

A thread running Lisp cannot be queried, unless it somehow "listens"
to such queries.  This isn't magic, it has to be implemented, and how
do you envision to implement it?  You envision some kind of scheduler
as part of Emacs? something else?

> The range of variables that can be made remote should be minimized and
> possibly also restricted to a safe subset of variables.

I very much doubt such a "safe subset" exists that can support useful
Lisp programs.

> > ... If I am the Lisp programmer writing code for such
> > a thread, how can I know what is and what isn't allowed?
> 
> Everything is allowed, but global state changes will not necessarily
> propagate to the parent Emacs thread. They will be confined to that
> child async thread.

So it could be that a thread decides, for example, that some buffer B
is the return value of its function, whereas buffer B no longer exists
because it was killed?  How's that useful?

> > ...And what
> > happens if I do something that is not allowed?  And finally, does it
> > mean we cannot run existing Lisp programs in such threads, but must
> > program for them from scratch?
> 
> Non-trivial programs that need to modify the global Emacs state will
> have to be written specially.

So you basically agree that to have useful enough multi-threading,
most of Emacs will have to be redesigned and rewritten?

> But programs that only return a value will work as usual, without a
> need to modify them.

How many programs like that exist and are useful?

> The idea is similar to https://github.com/jwiegley/emacs-async (or to
> `org-export-async-start'), but with more tight communication between
> Emacs processes. The original emacs-async requires a new Emacs instance
> that will also need to re-load all the necessary packages manually from
> startup.

If we will have the same disadvantages and restrictions as in
emacs-async, then why bother? emacs-async already exists.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  7:05                                                                     ` Eli Zaretskii
@ 2023-07-08 10:53                                                                       ` Ihor Radchenko
  2023-07-08 14:26                                                                         ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-08 10:53 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Can you please provide an example about "surprised"? Do you mean that
>> buffer->pt will no longer be accurate? Something else?
>
> Not pt but the pointers to buffer text and the gap.  Those determine
> the address of a given buffer position in memory, and are used when a
> Lisp program accesses buffer text in any way.  GC can change them if
> it decides to relocate buffer text or compact the gap.

Ok. I tried to find an example myself by looking into `char-after'.
I can see CHAR_TO_BYTE->buf_charpos_to_bytepos->buf_next_char_len->BUF_BYTE_ADDRESS
and
FETCH_CHAR->FETCH_MULTIBYTE_CHAR->BYTE_POS_ADDR

Will blocking GC for the duration of CHAR_TO_BYTE and buf_next_char_len
be a problem? GC between the calls to CHAR_TO_BYTE and
buf_next_char_len, if it relocates buffer text/gap will not break
anything, AFAIU.

>> >> Then the question is: can the global state be reduced?
>> >
>> > By what measures?  Please suggest something concrete here.
>> 
>> By transforming some of the global state variables into thread-local
>> variables.
>
> Which variables can safely and usefully be made thread-local?

PT, ZV, BEGV, and the buffer-local variables that are represented by the
global C variables.

>> > I don't see how one can write a useful Lisp program with such
>> > restrictions.
>> 
>> Pure, side-effect-free functions, for example.
>
> I don't see how this could be practically useful.

For example, `org-element-interpret-data' converts Org mode AST to
string. Just now, I tried it using AST of one of my large Org buffers.
It took 150seconds to complete, while blocking Emacs.

Or consider building large completion table.
Or parsing HTML DOM string into sexp.
Or JSON passed by LSP server.

> ... Besides the basic
> question of whether a useful Lisp program can be written in Emacs
> using only side-effect-free functions

Yes. I mean... look at Haskell. There is no shortage of pure functional
libraries there.

> .. , there's the large body of
> subroutines and primitives any Lisp program uses to do its job, and
> how do you know which ones of them are side-effect-free or async-safe?

(declare (pure t))

> To take just one example which came up in recent discussions, look at
> string-pixel-width.  Or even at string-width.

Those must either be blocking or throw an error when called from async
thread.

---

Just to be clear, it is not a useful aim to make any arbitrary Elisp
code asynchronous. But if we can at least allow specially designed Elisp
to be asynchronous, it will already be a good breakthrough.

And later, in future, if we can manage to reduce the amount global state
in Emacs, more Elisp code may be converted (or even work as is)
asynchronously.

Remember the lexical binding transition. I was not refused because some
Elisp code failed to work with it.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 10:02                                                   ` Eli Zaretskii
@ 2023-07-08 11:54                                                     ` Po Lu
  2023-07-08 14:12                                                       ` Eli Zaretskii
  2023-07-08 12:01                                                     ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-08 11:54 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: yantar92, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> Great! that means in practice no existing Lisp program could ever run
> in a non-main thread.  It isn't a very practical solution.

Number and text crunching tasks (think Semantic, or JSON parsing for
LSP) don't need to sleep or read keyboard input.

> Besides, non-main threads do sometimes legitimately need to prompt
> the user.  It is not a programmer's error when they do.

They should then devise mechanisms for communicating with the main
thread.

> I don't think such a simplistic solution suits a program such as
> Emacs.

It is the only possible solution, as long as Emacs wants to keep working
with other window systems.  Even our limited threads cannot work with NS
and GTK in their present state: the toolkit aborts or enters an
inconsistent state the instant a GUI function is called from a thread
other than the main thread.

> Fixed how?

By replacing `sit-for' with `sleep-for' (and in general avoiding
functions that call redisplay or GUI functions.)

> The above doesn't do any editing, it just accesses buffer text without
> changing it.

I intended to include ``changing point'' in my definition of ``modifying
the buffer''.

> Why are we talking about multiple threads at all? don't we want to
> allow some Lisp code run from non-main threads?

That code will have to be specifically written for running outside the
main thread, of course, obviating the need to rewrite all of our
existing code.

> Using a snapshot of some global resource, such as buffer text, works
> only up to a point, and basically prohibits many potentially
> interesting uses of threads.  That's because such snapshotting assumes
> no significant changes happen in the original objects while processing
> the snapshot, and that is only sometimes true.

We could also allow Lisp to lock a buffer by hand.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08  6:50                                                 ` Eli Zaretskii
@ 2023-07-08 11:55                                                   ` Ihor Radchenko
  2023-07-08 14:43                                                     ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-08 11:55 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Does it mean that we can safely call, for example, Fcons asynchronously?
>
> Not necessarily, because Fcons is not just about allocating memory.
>
> I think we once again have a misunderstanding here, because when you
> say "memory allocation" you mean something very different than I do,
> which is a call to malloc to get more memory from the system.  It
> sounds like you think that Fcons _is_ memory allocation?  But if so,
> this terminology is so confusing that it is not useful in a detailed
> technical discussion such as this one.  We use the term "consing" to
> refer to creation of Lisp objects, which includes memory allocation,
> but also other stuff.

> In particular, consing modifies memory blocks (already available to
> Emacs, so no "memory allocation" per se) used to keep track of live
> and dead Lisp objects, and those modifications cannot be concurrently
> done by more than one thread, at least in some cases.

Thanks for the clarification.
I heard this term, but was unsure what exactly it refers to.

>> > So your solution to each such problem is to lock variables?  If so,
>> > you will end up locking a lot of them, and how is this different from
>> > using the global lock we do today with Lisp threads?
>> 
>> The idea is to prevent simultaneous write, which will only lock for a
>> small fraction of time.
>
> If one thread writes to a data structure, reading from it could also
> need to block, or else the reader will risk getting inconsistent data.
> So this is not just about simultaneous writing, it's much more
> general.

Sure. Of course, locking should be on write.
May you elaborate what you mean by inconsistent data?

>> And I still fail to see where base-buffer is _changed_. Is base buffer
>> ever supposed to be changed?
>
> Another thread might change it while this thread examines it.

I was able to identify a single place in C code where buffer's base
buffer is being set: in make-indirect-buffer, when the buffer is just
created. So, it is safe to assume that buffer->base_buffer remain
constant for any given live buffer. Unless I miss something.

>> No, I am saying that the current logic of updating the undo-list will not work
>> when multiple async threads are involved. It will no longer be safe to
>> assume that we can safely update undo-list right before/after switching
>> current_buffer.
>> 
>> So, I asked if an alternative approach could be used instead.
>
> Undo records changes in text properties and markers, and those are
> different in the indirect buffers from the base buffers.  Does this
> explain why we cannot simply point to the base buffer?

Are you sure? Text properties are certainly shared between indirect buffers.

bset_undo_list (old_buf->base_buffer, BVAR (old_buf, undo_list));
INLINE void
bset_undo_list (struct buffer *b, Lisp_Object val)
{
  b->undo_list_ = val;
}

The markers that are not shared are pt_marker, begv_marker, and
zv_marker. But those could probably be made attached to a given thread.

>> >>   /* Look down buffer's list of local Lisp variables
>> >>      to find and update any that forward into C variables.  */
>> >
>> > The C code accesses some buffer-local variables via Vfoo_bar C
>> > variables.  Those need to be updated when the current buffer changes.
>> 
>> Now, when you explained this, it is also a big problem. Such C variables
>> are a global state that needs to be kept up to date. Async will break
>> the existing logic of these updates.
>
> Exactly.

I now looked a bit further, and what you are talking about are the
variables defined via DEFVAR_PER_BUFFER. These global variables have the
following type:

/* Forwarding pointer to a Lisp_Object variable.
   This is allowed only in the value cell of a symbol,
   and it means that the symbol's value really lives in the
   specified variable.  */
struct Lisp_Objfwd
  {
    enum Lisp_Fwd_Type type;	/* = Lisp_Fwd_Obj */
    Lisp_Object *objvar;
  };

The code in set_buffer calls
Fsymbol_value->find_symbol_value->swap_in_symval_forwarding for every symbol
with C variable equivalent.

These calls update internal pointer to Lisp object corresponding to
variable value in current_buffer.

If my understanding is correct, it should be safe to convert them into
thread-local variables and update them within current thread when
current_buffer (already thread-local) is altered.

>> > Oh, yes, they will: see fetch_buffer_markers, called by
>> > set_buffer_internal_2.
>> 
>> Do you mean that in the existing cooperative Elisp threads, if one
>> thread moves the point and yields to other thread, the other thread will
>> be left with point in the same position (arbitrary, from the point of
>> view of this other thread)?
>
> That's one problem, yes.  There are others.  Emacs Lisp uses point,
> both explicitly and implicitly, all over the board.  It is unthinkable
> that a thread will find point not in a place where it last moved it.

It is exactly what happens with current cooperative threads, AFAIK.
Will it make sense to convert PT, ZV, and BEGV into thread-local variables?

>> Is it buffer's marker list? I thought that you are referring to
>> BUF_MARKERS, not to PT, BEGV, and ZV.
>
> Buffer's marker list are referenced in subroutines of
> record_buffer_markers.

Do you mean record_buffer_markers->set_marker_both->attach_marker->
  if (m->buffer != b)
    {
      unchain_marker (m);
      m->buffer = b;
      m->next = BUF_MARKERS (b);
      BUF_MARKERS (b) = m;
    }

But will this `if' ever trigger for PT, BEGV, and ZV?

Also, it looks reasonable to block BUF_MARKERS when we need to change
BUF_MARKERS.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 10:02                                                   ` Eli Zaretskii
  2023-07-08 11:54                                                     ` Po Lu
@ 2023-07-08 12:01                                                     ` Ihor Radchenko
  2023-07-08 14:45                                                       ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-08 12:01 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Po Lu, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> > That is already a problem, as long as we are talking about leaving
>> > most of Emacs application code intact.  How do you ensure only the
>> > main thread can process input and display?  A non-main thread can
>> > easily call some function which prompts the user, e.g., with
>> > yes-or-no-p, or force redisplay with sit-for, and what do you do when
>> > that happens?
>> 
>> To signal an error.
>
> Great! that means in practice no existing Lisp program could ever run
> in a non-main thread.  It isn't a very practical solution.
>
> Besides, non-main threads do sometimes legitimately need to prompt
> the user.  It is not a programmer's error when they do.

As an alternative async threads can be temporarily changed to
cooperative in such scenario.

They will already have to wait synchronously when consing new objects
anyway.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 11:54                                                     ` Po Lu
@ 2023-07-08 14:12                                                       ` Eli Zaretskii
  2023-07-09  0:37                                                         ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08 14:12 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: yantar92@posteo.net,  emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 19:54:59 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > Great! that means in practice no existing Lisp program could ever run
> > in a non-main thread.  It isn't a very practical solution.
> 
> Number and text crunching tasks (think Semantic, or JSON parsing for
> LSP) don't need to sleep or read keyboard input.

Are they allowed to, say, write some data to a file?  If so, they
might need to ask the user whether its okay to overwrite an existing
file.

IOW, I think you have a very narrow idea of "number and text crunching
tasks" that could benefit from threads.  For example, one of the
frequent requests is to run the part of Gnus that fetches email and
articles in a separate thread -- if this is okay for "number and text
crunching tasks", then it is likely to prompt users.

> > Besides, non-main threads do sometimes legitimately need to prompt
> > the user.  It is not a programmer's error when they do.
> 
> They should then devise mechanisms for communicating with the main
> thread.

We are mis-communicating.  My point is that it is almost impossible to
take an existing non-trivial Lisp program and let it run from a
non-main thread without bumping into this issue.  Your responses
indicate that you agree with me: such Lisp programs need to be written
from scratch under the assumption that they will run from a non-main
thread.

How is this compatible with the goal of having threads in Emacs, which
are to allow running Lisp code with less hair than the existing timers
or emacs-async?

> > Fixed how?
> 
> By replacing `sit-for' with `sleep-for' (and in general avoiding
> functions that call redisplay or GUI functions.)

So programs running in non-main threads will be unable to do stuff
like show progress etc.?  That's not very encouraging, to say the
least.

> That code will have to be specifically written for running outside the
> main thread, of course, obviating the need to rewrite all of our
> existing code.

QED

This basically means rewriting most of Emacs.  Because most APIs and
subroutines we use in every Lisp program were not "specifically
written for running outside the main thread".  So we'll need special
variants of all of those to do those simple jobs.

> > Using a snapshot of some global resource, such as buffer text, works
> > only up to a point, and basically prohibits many potentially
> > interesting uses of threads.  That's because such snapshotting assumes
> > no significant changes happen in the original objects while processing
> > the snapshot, and that is only sometimes true.
> 
> We could also allow Lisp to lock a buffer by hand.

Same problem here.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 10:53                                                                       ` Ihor Radchenko
@ 2023-07-08 14:26                                                                         ` Eli Zaretskii
  2023-07-09  9:36                                                                           ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08 14:26 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 10:53:59 +0000
> 
> Ok. I tried to find an example myself by looking into `char-after'.
> I can see CHAR_TO_BYTE->buf_charpos_to_bytepos->buf_next_char_len->BUF_BYTE_ADDRESS
> and
> FETCH_CHAR->FETCH_MULTIBYTE_CHAR->BYTE_POS_ADDR
> 
> Will blocking GC for the duration of CHAR_TO_BYTE and buf_next_char_len
> be a problem?

Not by itself, no.  But if you go this way, you eventually will lock
everything all the time.

> >> By transforming some of the global state variables into thread-local
> >> variables.
> >
> > Which variables can safely and usefully be made thread-local?
> 
> PT, ZV, BEGV

Even that is not enough: you forgot the gap.

> and the buffer-local variables that are represented by the
> global C variables.

That's a lot!

> >> > I don't see how one can write a useful Lisp program with such
> >> > restrictions.
> >> 
> >> Pure, side-effect-free functions, for example.
> >
> > I don't see how this could be practically useful.
> 
> For example, `org-element-interpret-data' converts Org mode AST to
> string. Just now, I tried it using AST of one of my large Org buffers.
> It took 150seconds to complete, while blocking Emacs.

It isn't side-effect-free, though.

I don't believe any useful Lisp program in Emacs can be
side-effect-free, for the purposes of this discussion.  Every single
one of them accesses the global state and changes the global state.

> > ... Besides the basic
> > question of whether a useful Lisp program can be written in Emacs
> > using only side-effect-free functions
> 
> Yes. I mean... look at Haskell. There is no shortage of pure functional
> libraries there.

I cannot follow you there: I don't know Haskell.

> > .. , there's the large body of
> > subroutines and primitives any Lisp program uses to do its job, and
> > how do you know which ones of them are side-effect-free or async-safe?
> 
> (declare (pure t))

How many of these do we have, and can useful programs be written using
only those?  More importantly, when you call some function from
simple.el, how do you know whether all of its subroutines and
primitives are 'pure'?

> > To take just one example which came up in recent discussions, look at
> > string-pixel-width.  Or even at string-width.
> 
> Those must either be blocking or throw an error when called from async
> thread.

So we will always block.  Which is what we already have with the Lisp
threads.

> Just to be clear, it is not a useful aim to make any arbitrary Elisp
> code asynchronous. But if we can at least allow specially designed Elisp
> to be asynchronous, it will already be a good breakthrough.

No, it will not.  It will be a fancy feature with little or no use.

> And later, in future, if we can manage to reduce the amount global state
> in Emacs, more Elisp code may be converted (or even work as is)
> asynchronously.

If we don't have a clear path towards that goal today, and don't even
know whether such a path is possible, it will never happen.

> Remember the lexical binding transition. I was not refused because some
> Elisp code failed to work with it.

Lexical binding is nothing compared to this.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 11:55                                                   ` Ihor Radchenko
@ 2023-07-08 14:43                                                     ` Eli Zaretskii
  2023-07-09  9:57                                                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08 14:43 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 11:55:34 +0000
> 
> >> And I still fail to see where base-buffer is _changed_. Is base buffer
> >> ever supposed to be changed?
> >
> > Another thread might change it while this thread examines it.
> 
> I was able to identify a single place in C code where buffer's base
> buffer is being set: in make-indirect-buffer, when the buffer is just
> created. So, it is safe to assume that buffer->base_buffer remain
> constant for any given live buffer. Unless I miss something.

C code can change.  It is not carved in stone.  Are we going to treat
the current state of the code as if it can never change?  That's
unwise.

> > Undo records changes in text properties and markers, and those are
> > different in the indirect buffers from the base buffers.  Does this
> > explain why we cannot simply point to the base buffer?
> 
> Are you sure? Text properties are certainly shared between indirect buffers.

That's not what the documentation says.

> >> > The C code accesses some buffer-local variables via Vfoo_bar C
> >> > variables.  Those need to be updated when the current buffer changes.
> >> 
> >> Now, when you explained this, it is also a big problem. Such C variables
> >> are a global state that needs to be kept up to date. Async will break
> >> the existing logic of these updates.
> >
> > Exactly.
> 
> I now looked a bit further, and what you are talking about are the
> variables defined via DEFVAR_PER_BUFFER.

Non necessarily.  Example: show-trailing-whitespace.

> If my understanding is correct, it should be safe to convert them into
> thread-local variables and update them within current thread when
> current_buffer (already thread-local) is altered.

It is only safe if no other thread will access the same buffer.  For
example, redisplay will be unable to show that buffer if it is visible
in some window, because its notion of the buffer-local values might be
inaccurate.

> >> > Oh, yes, they will: see fetch_buffer_markers, called by
> >> > set_buffer_internal_2.
> >> 
> >> Do you mean that in the existing cooperative Elisp threads, if one
> >> thread moves the point and yields to other thread, the other thread will
> >> be left with point in the same position (arbitrary, from the point of
> >> view of this other thread)?
> >
> > That's one problem, yes.  There are others.  Emacs Lisp uses point,
> > both explicitly and implicitly, all over the board.  It is unthinkable
> > that a thread will find point not in a place where it last moved it.
> 
> It is exactly what happens with current cooperative threads, AFAIK.

With the existing threads, this will never happen, because a thread
will never yield between moving point to some position and accessing
buffer text at that position.

> Will it make sense to convert PT, ZV, and BEGV into thread-local variables?

What do you expect redisplay to do when some thread moves point in a
way that it is no longer in the window?

> >> Is it buffer's marker list? I thought that you are referring to
> >> BUF_MARKERS, not to PT, BEGV, and ZV.
> >
> > Buffer's marker list are referenced in subroutines of
> > record_buffer_markers.
> 
> Do you mean record_buffer_markers->set_marker_both->attach_marker->
>   if (m->buffer != b)
>     {
>       unchain_marker (m);
>       m->buffer = b;
>       m->next = BUF_MARKERS (b);
>       BUF_MARKERS (b) = m;
>     }
> 
> But will this `if' ever trigger for PT, BEGV, and ZV?

I don't know!  You cannot possibly have code where you need to reason
about every single line whether something can or cannot happen there.
You need a relatively small set of basic assumptions that _always_
hold.  Anything more complex makes the task of developing and
maintaining this code an impossible job.

> Also, it looks reasonable to block BUF_MARKERS when we need to change
> BUF_MARKERS.

Sure.  Like I said: we'd need to lock everything.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 12:01                                                     ` Ihor Radchenko
@ 2023-07-08 14:45                                                       ` Eli Zaretskii
  0 siblings, 0 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-08 14:45 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Po Lu <luangruo@yahoo.com>, emacs-devel@gnu.org
> Date: Sat, 08 Jul 2023 12:01:00 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> > That is already a problem, as long as we are talking about leaving
> >> > most of Emacs application code intact.  How do you ensure only the
> >> > main thread can process input and display?  A non-main thread can
> >> > easily call some function which prompts the user, e.g., with
> >> > yes-or-no-p, or force redisplay with sit-for, and what do you do when
> >> > that happens?
> >> 
> >> To signal an error.
> >
> > Great! that means in practice no existing Lisp program could ever run
> > in a non-main thread.  It isn't a very practical solution.
> >
> > Besides, non-main threads do sometimes legitimately need to prompt
> > the user.  It is not a programmer's error when they do.
> 
> As an alternative async threads can be temporarily changed to
> cooperative in such scenario.

That doesn't help, because, as I said, we don't have a good solution
for this dilemma even for the current Lisp threads.

> They will already have to wait synchronously when consing new objects
> anyway.

This issue about display and prompting the user is not the same as
interlocking.  The latter can be solved by waiting, but the former
cannot.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 10:02                                                       ` Ihor Radchenko
@ 2023-07-08 19:39                                                         ` tomas
  0 siblings, 0 replies; 192+ messages in thread
From: tomas @ 2023-07-08 19:39 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Po Lu, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1480 bytes --]

On Sat, Jul 08, 2023 at 10:02:18AM +0000, Ihor Radchenko wrote:
> tomas@tuxteam.de writes:
> 
> > Reducing blocking waits for external stuff (processes, network) would
> > already be a lofty goal for Emacs, and far more attainable, I think.
> 
> Would you mind elaborating?
> AFAIU, the bottlenecks in these scenarios are still on Lisp side and are
> not specific to process/network handling.

I think we agree: the basic building blocks are there, but not every
Lisp code uses them. Writing explicit parallelism is a bit strange
(have a look at the javascript code used in browsers to see what
I mean: whenever they write a function to fetch something from the
server, they provide a callback as a parameter for the function to
know what to do when the network request succeeds. Some times there
are several callbacks (one for the error path).

Of course the function doesn't get called right away, but something
is put somewhere into the structure managing the select/poll/epoll
machinery.

I've done that in C: it's doable, but definitely more error prone
than the "normal" way of doing things. Of course in Javascript and
Emacs Lisp, with closures (thanks, Stefan!) it is a tad easier.

The elephant in the room is, as Eli says, all this existing code.

I thing that (if going this way at all) the interesting challenge
would be to find a strategy which can bring gradual improvements
without breaking "the rest of the world".

Cheers
-- 
t

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 14:12                                                       ` Eli Zaretskii
@ 2023-07-09  0:37                                                         ` Po Lu
  2023-07-09  7:01                                                           ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-09  0:37 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: yantar92, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> Are they allowed to, say, write some data to a file?  If so, they
> might need to ask the user whether its okay to overwrite an existing
> file.

They might chose to use `write-region' instead, or save the file from
the main thread.

> IOW, I think you have a very narrow idea of "number and text crunching
> tasks" that could benefit from threads.  For example, one of the
> frequent requests is to run the part of Gnus that fetches email and
> articles in a separate thread -- if this is okay for "number and text
> crunching tasks", then it is likely to prompt users.

Prompting for options should take place before the thread is started, or
after the data is retrieved and about to be displayed.

> We are mis-communicating.  My point is that it is almost impossible to
> take an existing non-trivial Lisp program and let it run from a
> non-main thread without bumping into this issue.  Your responses
> indicate that you agree with me: such Lisp programs need to be written
> from scratch under the assumption that they will run from a non-main
> thread.

I agree with this completely.  From my POV, such requirements are
reasonable and not very different from the requirements for doing so in
other GUI toolkits and programs.

> How is this compatible with the goal of having threads in Emacs, which
> are to allow running Lisp code with less hair than the existing timers
> or emacs-async?

I thought the concern was one of efficiency, and not ease of use:
process IO is slow compared to sharing the same VM space, and
subprocesses also utilize more memory.

>> > Fixed how?
>> 
>> By replacing `sit-for' with `sleep-for' (and in general avoiding
>> functions that call redisplay or GUI functions.)
>
> So programs running in non-main threads will be unable to do stuff
> like show progress etc.?  That's not very encouraging, to say the
> least.

They should be able to run code from the main thread's command loop, via
mechanisms such as `unread-command-events'.

> This basically means rewriting most of Emacs.  Because most APIs and
> subroutines we use in every Lisp program were not "specifically
> written for running outside the main thread".  So we'll need special
> variants of all of those to do those simple jobs.

I wasn't thinking about Lisp functions used for text editing tasks, but
rather raw data crunching functions.  Most of these are already
side-effect free, and some are even truly reentrant.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  0:37                                                         ` Po Lu
@ 2023-07-09  7:01                                                           ` Eli Zaretskii
  2023-07-09  7:14                                                             ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09  7:01 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: yantar92@posteo.net,  emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 08:37:47 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > IOW, I think you have a very narrow idea of "number and text crunching
> > tasks" that could benefit from threads.  For example, one of the
> > frequent requests is to run the part of Gnus that fetches email and
> > articles in a separate thread -- if this is okay for "number and text
> > crunching tasks", then it is likely to prompt users.
> 
> Prompting for options should take place before the thread is started, or
> after the data is retrieved and about to be displayed.

That is of course not always possible, or even desirable.  For
example, some prompts are caused by specific aspects of the
processing, which may or may not happen, depending on the data.
Prompting for options unrelated to the actual processing would mean
annoying users unnecessarily.  Etc. etc.

> > We are mis-communicating.  My point is that it is almost impossible to
> > take an existing non-trivial Lisp program and let it run from a
> > non-main thread without bumping into this issue.  Your responses
> > indicate that you agree with me: such Lisp programs need to be written
> > from scratch under the assumption that they will run from a non-main
> > thread.
> 
> I agree with this completely.  From my POV, such requirements are
> reasonable and not very different from the requirements for doing so in
> other GUI toolkits and programs.

So basically, what you have in mind is a way of accruing a body of
special-purpose Lisp programs written specifically for running from
non-main threads.  Which means reusing existing code or packages not
written to these specifications will be impossible, and we will in
effect have two completely separate flavors of Emacs Lisp programs.
It would mean, in particular, that many functions in simple.el,
subr.el, and other similar infrastructure packages will need to have
specialized variants suitable for running in non-main threads.

And all this will be possible if -- and it's a large "if" -- the
necessary support on the C level will be written and prove reliable.

If this is the plan, it might be possible, at least in principle, but
is it really what is on people's mind when they dream about "more
concurrent Emacs"?  I doubt that.

> > How is this compatible with the goal of having threads in Emacs, which
> > are to allow running Lisp code with less hair than the existing timers
> > or emacs-async?
> 
> I thought the concern was one of efficiency, and not ease of use:
> process IO is slow compared to sharing the same VM space, and
> subprocesses also utilize more memory.

Memory is cheap these days, and "slow IO" is still a gain when it
allows us to use more than a single CPU execution unit at the same
time.  So yes, efficiency is desirable, but ease of use is also
important.  What's more, I don't think anyone really wants to have to
write Lisp programs in a completely different way when we want them to
run from threads.

> >> > Fixed how?
> >> 
> >> By replacing `sit-for' with `sleep-for' (and in general avoiding
> >> functions that call redisplay or GUI functions.)
> >
> > So programs running in non-main threads will be unable to do stuff
> > like show progress etc.?  That's not very encouraging, to say the
> > least.
> 
> They should be able to run code from the main thread's command loop, via
> mechanisms such as `unread-command-events'.

Those mechanisms only work when Emacs is idle, which is bad for
features like progress reporting.  Doing this right requires to
redesign how redisplay kicks in, and probably have several different
kinds of redisplay, not one.

> > This basically means rewriting most of Emacs.  Because most APIs and
> > subroutines we use in every Lisp program were not "specifically
> > written for running outside the main thread".  So we'll need special
> > variants of all of those to do those simple jobs.
> 
> I wasn't thinking about Lisp functions used for text editing tasks, but
> rather raw data crunching functions.  Most of these are already
> side-effect free, and some are even truly reentrant.

I think none of them are side-effect free, if you look closely.  They
access buffer text, they move point, they temporarily change the
selected window and the current buffer, the access and many times
modify the obarray, etc. etc.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  7:01                                                           ` Eli Zaretskii
@ 2023-07-09  7:14                                                             ` Po Lu
  2023-07-09  7:35                                                               ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-09  7:14 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: yantar92, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> That is of course not always possible, or even desirable.  For
> example, some prompts are caused by specific aspects of the
> processing, which may or may not happen, depending on the data.
> Prompting for options unrelated to the actual processing would mean
> annoying users unnecessarily.  Etc. etc.

In that case, the thread will have to suspend itself until the main
thread can read a response from the user.

> So basically, what you have in mind is a way of accruing a body of
> special-purpose Lisp programs written specifically for running from
> non-main threads.  Which means reusing existing code or packages not
> written to these specifications will be impossible, and we will in
> effect have two completely separate flavors of Emacs Lisp programs.
> It would mean, in particular, that many functions in simple.el,
> subr.el, and other similar infrastructure packages will need to have
> specialized variants suitable for running in non-main threads.

Yes.

> And all this will be possible if -- and it's a large "if" -- the
> necessary support on the C level will be written and prove reliable.
>
> If this is the plan, it might be possible, at least in principle, but
> is it really what is on people's mind when they dream about "more
> concurrent Emacs"?  I doubt that.

I don't know what other people think, but it's what I would find
desirable, as my gripe with tools like Semantic is that they cannot run
expensive text crunching tasks in the background.  Having shared-memory
multiprocessing would allow these tasks to be implemented efficiently.

> Memory is cheap these days, and "slow IO" is still a gain when it
> allows us to use more than a single CPU execution unit at the same
> time.  So yes, efficiency is desirable, but ease of use is also
> important.  What's more, I don't think anyone really wants to have to
> write Lisp programs in a completely different way when we want them to
> run from threads.

When programmers write such code for other interactive programs, they
are comfortable with the limitations of running code outside of the UI
thread.  Why should writing new, thread-safe Lisp for Emacs be any more
difficult?

> Those mechanisms only work when Emacs is idle, which is bad for
> features like progress reporting.  Doing this right requires to
> redesign how redisplay kicks in, and probably have several different
> kinds of redisplay, not one.

Maybe.  I haven't worked out the details of that yet, but in most other
GUI programs and toolkits, messages from other threads can only be
processed by the UI thread while it is idle.

> I think none of them are side-effect free, if you look closely.  They
> access buffer text, they move point, they temporarily change the
> selected window and the current buffer, the access and many times
> modify the obarray, etc. etc.

`intern' should be interlocked so that only one thread is accessing the
obarray at any given time.  Point and buffer text shouldn't prove a
problem, provided that the buffer being used is not accessed from any
other thread.

But yes, many of these functions will have to be audited and possibly
rewritten for multi-threaded correctness.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  7:14                                                             ` Po Lu
@ 2023-07-09  7:35                                                               ` Eli Zaretskii
  2023-07-09  7:57                                                                 ` Ihor Radchenko
  2023-07-09  9:25                                                                 ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09  7:35 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: yantar92@posteo.net,  emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 15:14:36 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > What's more, I don't think anyone really wants to have to
> > write Lisp programs in a completely different way when we want them to
> > run from threads.
> 
> When programmers write such code for other interactive programs, they
> are comfortable with the limitations of running code outside of the UI
> thread.  Why should writing new, thread-safe Lisp for Emacs be any more
> difficult?

Because we'd need to throw away 40 years of Lisp programming, and
rewrite almost every bit of what was written since then.  It's a huge
setback for writing Emacs applications.

> > Those mechanisms only work when Emacs is idle, which is bad for
> > features like progress reporting.  Doing this right requires to
> > redesign how redisplay kicks in, and probably have several different
> > kinds of redisplay, not one.
> 
> Maybe.  I haven't worked out the details of that yet, but in most other
> GUI programs and toolkits, messages from other threads can only be
> processed by the UI thread while it is idle.

Emacs doesn't have a UI thread, as you know.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  7:35                                                               ` Eli Zaretskii
@ 2023-07-09  7:57                                                                 ` Ihor Radchenko
  2023-07-09  8:41                                                                   ` Eli Zaretskii
  2023-07-09  9:25                                                                 ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-09  7:57 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Po Lu, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> When programmers write such code for other interactive programs, they
>> are comfortable with the limitations of running code outside of the UI
>> thread.  Why should writing new, thread-safe Lisp for Emacs be any more
>> difficult?
>
> Because we'd need to throw away 40 years of Lisp programming, and
> rewrite almost every bit of what was written since then.  It's a huge
> setback for writing Emacs applications.

May you please elaborate why exactly do we need to rewrite everything?
If we agree that async threads will have limitations, we may as well
start small, allowing a limited number of functions to be used. The
functions that do not support true async will then switch to the
cooperative mode, possibly throwing a warning.

Later, if it is truly necessary to rewrite things for async, it can be
done gradually.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  7:57                                                                 ` Ihor Radchenko
@ 2023-07-09  8:41                                                                   ` Eli Zaretskii
  2023-07-10 14:53                                                                     ` Dmitry Gutov
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09  8:41 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Po Lu <luangruo@yahoo.com>, emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 07:57:47 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> When programmers write such code for other interactive programs, they
> >> are comfortable with the limitations of running code outside of the UI
> >> thread.  Why should writing new, thread-safe Lisp for Emacs be any more
> >> difficult?
> >
> > Because we'd need to throw away 40 years of Lisp programming, and
> > rewrite almost every bit of what was written since then.  It's a huge
> > setback for writing Emacs applications.
> 
> May you please elaborate why exactly do we need to rewrite everything?

We already did, please read the previous messages.  In a nutshell:
because most of the Lisp code we have cannot be run from an async
thread.

> If we agree that async threads will have limitations, we may as well
> start small, allowing a limited number of functions to be used. The
> functions that do not support true async will then switch to the
> cooperative mode, possibly throwing a warning.
> 
> Later, if it is truly necessary to rewrite things for async, it can be
> done gradually.

The above exactly means a massive rewrite of a very large portion of
Lisp code we have today and use it every day.  As long as such a
rewrite is not done, "switching to cooperative mode" means that the
Lisp programs runs the way it does today, so there will be almost no
gain until enough code will be rewritten.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  7:35                                                               ` Eli Zaretskii
  2023-07-09  7:57                                                                 ` Ihor Radchenko
@ 2023-07-09  9:25                                                                 ` Po Lu
  2023-07-09 11:14                                                                   ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-09  9:25 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: yantar92, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> Because we'd need to throw away 40 years of Lisp programming, and
> rewrite almost every bit of what was written since then.  It's a huge
> setback for writing Emacs applications.

Programmers who are not comfortable with that can continue to utilize
asynch subprocesses or to run their code in the main thread.

> Emacs doesn't have a UI thread, as you know.

Emacs's equivalent is the main thread, which is the only thread that can
safely call redisplay.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 14:26                                                                         ` Eli Zaretskii
@ 2023-07-09  9:36                                                                           ` Ihor Radchenko
  2023-07-09  9:56                                                                             ` Po Lu
                                                                                               ` (2 more replies)
  0 siblings, 3 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-09  9:36 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> > Which variables can safely and usefully be made thread-local?
>> 
>> PT, ZV, BEGV
>
> Even that is not enough: you forgot the gap.

Am I missing something, does the gap remain intact when the just move
point? AFAIU, the gap only needs to move when we do edits.

I was thinking about thread-local variables just to move around and read
buffer. Asynchronous writing is probably a poor idea anyway - the very
idea of a gap does not work well when we need to write in multiple
far-away places in buffer.

>> and the buffer-local variables that are represented by the
>> global C variables.
>
> That's a lot!

Do you mean that "a lot" is bad?

>> > I don't see how this could be practically useful.
>> 
>> For example, `org-element-interpret-data' converts Org mode AST to
>> string. Just now, I tried it using AST of one of my large Org buffers.
>> It took 150seconds to complete, while blocking Emacs.
>
> It isn't side-effect-free, though.

It is, just not declared so.

> I don't believe any useful Lisp program in Emacs can be
> side-effect-free, for the purposes of this discussion.  Every single
> one of them accesses the global state and changes the global state.

As I said, I hope that we can convert the important parts of the global
state into thread-local state.

>> Yes. I mean... look at Haskell. There is no shortage of pure functional
>> libraries there.
>
> I cannot follow you there: I don't know Haskell.

In short, pure functions in Haskell can utilize multiple CPUs
automatically, without programmers explicitly writing code for
multi-threading support.
https://wiki.haskell.org/Parallelism

     In Haskell we provide two ways to achieve parallelism:

     - Pure parallelism, which can be used to speed up non-IO parts of the program.
     - Concurrency, which can be used for parallelising IO.
     
     Pure Parallelism (Control.Parallel): Speeding up a pure computation
     using multiple processors. Pure parallelism has these advantages:

     - Guaranteed deterministic (same result every time)
     - no race conditions or deadlocks
     
     Concurrency (Control.Concurrent): Multiple threads of control that execute "at the same time".

     - Threads are in the IO monad
     - IO operations from multiple threads are interleaved non-deterministically
     - communication between threads must be explicitly programmed
     - Threads may execute on multiple processors simultaneously
     - Dangers: race conditions and deadlocks
     
     *Rule of thumb: use Pure Parallelism if you can, Concurrency otherwise.*

>> (declare (pure t))
>
> How many of these do we have, and can useful programs be written using
> only those?

I think I did provide several examples. Po Lu also did.
One additional example: Org mode export (the most CPU-heavy part of it).

> ... More importantly, when you call some function from
> simple.el, how do you know whether all of its subroutines and
> primitives are 'pure'?

We do not, but it may be possible to add assertions that will ensure
purity in whatever sense we need.

Having pure functions is not enough by itself - async support is still
needed and not trivial. However, supporting asynchronous pure function
is easier compared to more general async support. See the above quote
from Haskell wiki.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  9:36                                                                           ` Ihor Radchenko
@ 2023-07-09  9:56                                                                             ` Po Lu
  2023-07-09 10:04                                                                               ` Ihor Radchenko
  2023-07-09 11:59                                                                             ` Eli Zaretskii
  2023-07-09 17:13                                                                             ` Gregory Heytings
  2 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-09  9:56 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Am I missing something, does the gap remain intact when the just move
> point? AFAIU, the gap only needs to move when we do edits.
>
> I was thinking about thread-local variables just to move around and read
> buffer. Asynchronous writing is probably a poor idea anyway - the very
> idea of a gap does not work well when we need to write in multiple
> far-away places in buffer.

Thread-local state is NOT cheap!  We should not jump at every
opportunity to add new thread-local state.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-08 14:43                                                     ` Eli Zaretskii
@ 2023-07-09  9:57                                                       ` Ihor Radchenko
  2023-07-09 12:08                                                         ` Eli Zaretskii
  2023-07-09 12:22                                                         ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-09  9:57 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> I was able to identify a single place in C code where buffer's base
>> buffer is being set: in make-indirect-buffer, when the buffer is just
>> created. So, it is safe to assume that buffer->base_buffer remain
>> constant for any given live buffer. Unless I miss something.
>
> C code can change.  It is not carved in stone.  Are we going to treat
> the current state of the code as if it can never change?  That's
> unwise.

Drastic changes as big as breaking the concept that indirect buffer has
a single fixed parent are not likely.

Of course, if there are breaking future changes on C level will need to
account for concurrency.

>> > Undo records changes in text properties and markers, and those are
>> > different in the indirect buffers from the base buffers.  Does this
>> > explain why we cannot simply point to the base buffer?
>> 
>> Are you sure? Text properties are certainly shared between indirect buffers.
>
> That's not what the documentation says.

May you please point me to this documentation?

>> I now looked a bit further, and what you are talking about are the
>> variables defined via DEFVAR_PER_BUFFER.
>
> Non necessarily.  Example: show-trailing-whitespace.

It has XSYMBOL (sym)->u.s.redirect = SYMBOL_FORWARDED;

and the loop in set_buffer_internal_2 has
if (sym->u.s.redirect == SYMBOL_LOCALIZED

>> If my understanding is correct, it should be safe to convert them into
>> thread-local variables and update them within current thread when
>> current_buffer (already thread-local) is altered.
>
> It is only safe if no other thread will access the same buffer.  For
> example, redisplay will be unable to show that buffer if it is visible
> in some window, because its notion of the buffer-local values might be
> inaccurate.

Another thread will have its own local set of Vfoo. When that thread
switches to a buffer, it will update its local Vfoo values. So,
redisplay will have access to correct local values.

>> Will it make sense to convert PT, ZV, and BEGV into thread-local variables?
>
> What do you expect redisplay to do when some thread moves point in a
> way that it is no longer in the window?

Async threads will not trigger redisplay. And they will have their own
PT, BEGV, and ZV.

Basically, I propose async threads not to set buffer->pt in the buffer
object. They will operate using their own local excursion and
restriction.

>> > Buffer's marker list are referenced in subroutines of
>> > record_buffer_markers.
>> 
>> Do you mean record_buffer_markers->set_marker_both->attach_marker->
>>   if (m->buffer != b)
>>     {
>>       unchain_marker (m);
>>       m->buffer = b;
>>       m->next = BUF_MARKERS (b);
>>       BUF_MARKERS (b) = m;
>>     }
>> 
>> But will this `if' ever trigger for PT, BEGV, and ZV?
>
> I don't know!  You cannot possibly have code where you need to reason
> about every single line whether something can or cannot happen there.
> You need a relatively small set of basic assumptions that _always_
> hold.  Anything more complex makes the task of developing and
> maintaining this code an impossible job.

Fair.
Then, we can block if we need to store thread markers.

>> Also, it looks reasonable to block BUF_MARKERS when we need to change
>> BUF_MARKERS.
>
> Sure.  Like I said: we'd need to lock everything.

I kindly do not agree. It is not like a global lock. Yes, there will be
a lot of blocking of individual Elisp objects. But it does not mean that
everything will be locked.

I think there is a good way to tentatively check if everything will be
locked or not - just check what will happen with consing. Consing
appears to be one of the biggest bottlenecks that will basically cause
global lock. If it can be demonstrated that consing is manageable, other
things will pose less of an issue.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  9:56                                                                             ` Po Lu
@ 2023-07-09 10:04                                                                               ` Ihor Radchenko
  0 siblings, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-09 10:04 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> Am I missing something, does the gap remain intact when the just move
>> point? AFAIU, the gap only needs to move when we do edits.
>>
>> I was thinking about thread-local variables just to move around and read
>> buffer. Asynchronous writing is probably a poor idea anyway - the very
>> idea of a gap does not work well when we need to write in multiple
>> far-away places in buffer.
>
> Thread-local state is NOT cheap!  We should not jump at every
> opportunity to add new thread-local state.

I see.
I still think that adding PT, BEGV, and ZV makes sense (even for current
cooperative threads; I have been trying to write something using these
some time ago, and having to constantly switch buffer and set point was
a big annoyance).

As for global C variable bindings, may we get rid of them?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  9:25                                                                 ` Po Lu
@ 2023-07-09 11:14                                                                   ` Eli Zaretskii
  2023-07-09 11:23                                                                     ` Ihor Radchenko
  2023-07-09 12:10                                                                     ` Po Lu
  0 siblings, 2 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09 11:14 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: yantar92@posteo.net,  emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 17:25:21 +0800
> 
> > Emacs doesn't have a UI thread, as you know.
> 
> Emacs's equivalent is the main thread, which is the only thread that can
> safely call redisplay.

Which makes it impossible to display indications that are unrelated to
buffer text displayed in some window.  We use the "normal"
buffer/window/frame/redisplay machinery for showing such indications
(a good example is progress report), but the downsides of this are:

  . we cannot show the indications asynchronously, although they have
    absolutely no relation to any other buffer or window, and
  . they look badly when compared to modern GUI facilities



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 11:14                                                                   ` Eli Zaretskii
@ 2023-07-09 11:23                                                                     ` Ihor Radchenko
  2023-07-09 12:10                                                                     ` Po Lu
  1 sibling, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-09 11:23 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Po Lu, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Emacs's equivalent is the main thread, which is the only thread that can
>> safely call redisplay.
>
> Which makes it impossible to display indications that are unrelated to
> buffer text displayed in some window.  We use the "normal"
> buffer/window/frame/redisplay machinery for showing such indications
> (a good example is progress report), but the downsides of this are:

Progress report and other indications that do not request user output do
make more sense if they are converted to async calls. For example,
`make-progress-reporter' might create a cooperative thread (that will be
able to trigger redisplay) and `progress-reporter-update' can signal to
that thread to display the updated the progress.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  9:36                                                                           ` Ihor Radchenko
  2023-07-09  9:56                                                                             ` Po Lu
@ 2023-07-09 11:59                                                                             ` Eli Zaretskii
  2023-07-09 13:58                                                                               ` Ihor Radchenko
  2023-07-09 17:13                                                                             ` Gregory Heytings
  2 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09 11:59 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 09:36:15 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> > Which variables can safely and usefully be made thread-local?
> >> 
> >> PT, ZV, BEGV
> >
> > Even that is not enough: you forgot the gap.
> 
> Am I missing something, does the gap remain intact when the just move
> point? AFAIU, the gap only needs to move when we do edits.

That's not the issue.  The issue is that without the gap being
accurate Emacs cannot convert buffer positions to pointers to buffer
text.  So not saving the gap is asking for trouble.

Once again: please do NOT try designing Emacs features based on what
you happen to see in the current code.  First, it is easy to miss
important stuff that invalidates your design; and second, code does
change over time, and if you introduce a feature that assumes some
changes will never happen, you are introducing ticking time bombs into
Emacs.  So instead, you need to understand the assumptions and the
invariants on which the code relies, and either keep them or go over
everything and adapt the code to assumptions that are no longer true.
In this case, the assumption is that the gap is always accurate except
for short periods of time, during which buffer text cannot be accessed
via buffer positions.

> I was thinking about thread-local variables just to move around and read
> buffer.

You will see in xml.c that we move the gap even though we do not
"edit" (i.e. do not modify the buffer).  We do this so that a pointer
to a portion of buffer text could be passed to an external library as
a simple C char array -- a legitimate technique that must be
available.  So even reading the buffer sometimes might require moving
the gap.

> Asynchronous writing is probably a poor idea anyway - the very
> idea of a gap does not work well when we need to write in multiple
> far-away places in buffer.

What if the main thread modifies buffer text, while one of the other
threads wants to read from it?

> >> and the buffer-local variables that are represented by the
> >> global C variables.
> >
> > That's a lot!
> 
> Do you mean that "a lot" is bad?

Yes, because it will require a huge thread-local storage.

> >> > I don't see how this could be practically useful.
> >> 
> >> For example, `org-element-interpret-data' converts Org mode AST to
> >> string. Just now, I tried it using AST of one of my large Org buffers.
> >> It took 150seconds to complete, while blocking Emacs.
> >
> > It isn't side-effect-free, though.
> 
> It is, just not declared so.

No, it isn't.  For starters, it changes obarray.

> >> Yes. I mean... look at Haskell. There is no shortage of pure functional
> >> libraries there.
> >
> > I cannot follow you there: I don't know Haskell.
> 
> In short, pure functions in Haskell can utilize multiple CPUs
> automatically, without programmers explicitly writing code for
> multi-threading support.
> https://wiki.haskell.org/Parallelism

Thanks, but I'm afraid this all is a bit academic.  Haskell is a
language, whereas Emacs is a text-processing program.  So Emacs
doesn't only define a programming language, it also implements gobs of
APIs and low-level subroutines whose purpose is to facilitate a
specific class of applications.  The huge global state that we have is
due to this latter aspect of Emacs, not to the design of the language.

> > ... More importantly, when you call some function from
> > simple.el, how do you know whether all of its subroutines and
> > primitives are 'pure'?
> 
> We do not, but it may be possible to add assertions that will ensure
> purity in whatever sense we need.

Those assertions will fire in any useful program with 100% certainty.
Imagine the plight of an Emacs Lisp programmer who has to write and
debug such programs.

We have in Emacs gazillion lines of Lisp code, written, debugged, and
tested during 4 decades.  We use those, almost without thinking, every
day for writing Lisp programs.  What you suggest means throwing away
most of that and starting from scratch.

I mean, take the simplest thing, like save-buffer-excursion or
with-selected-window, something that we use all the time, and look how
much of the global state they access and change.  Then imagine that
you don't have these and need to write programs that switch buffers
and windows temporarily in thread-safe way.  Then reflect on what this
means for all the other useful APIs and subroutines we have.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  9:57                                                       ` Ihor Radchenko
@ 2023-07-09 12:08                                                         ` Eli Zaretskii
  2023-07-09 14:16                                                           ` Ihor Radchenko
  2023-07-09 12:22                                                         ` Po Lu
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09 12:08 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 09:57:20 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> I was able to identify a single place in C code where buffer's base
> >> buffer is being set: in make-indirect-buffer, when the buffer is just
> >> created. So, it is safe to assume that buffer->base_buffer remain
> >> constant for any given live buffer. Unless I miss something.
> >
> > C code can change.  It is not carved in stone.  Are we going to treat
> > the current state of the code as if it can never change?  That's
> > unwise.
> 
> Drastic changes as big as breaking the concept that indirect buffer has
> a single fixed parent are not likely.

Famous last words ;-)

> Of course, if there are breaking future changes on C level will need to
> account for concurrency.

Who will remember that we at some point "assumed" buffer->base_buffer
can change _only_ in make-indirect-buffer?

> >> > Undo records changes in text properties and markers, and those are
> >> > different in the indirect buffers from the base buffers.  Does this
> >> > explain why we cannot simply point to the base buffer?
> >> 
> >> Are you sure? Text properties are certainly shared between indirect buffers.
> >
> > That's not what the documentation says.
> 
> May you please point me to this documentation?

     In all other respects, the indirect buffer and its base buffer are
  completely separate.  They have different names, independent values of
  point, independent narrowing, independent markers and overlays (though
  inserting or deleting text in either buffer relocates the markers and
  overlays for both), independent major modes, and independent
  buffer-local variable bindings.

Or did you exclude overlays and their properties from the above?

> >> If my understanding is correct, it should be safe to convert them into
> >> thread-local variables and update them within current thread when
> >> current_buffer (already thread-local) is altered.
> >
> > It is only safe if no other thread will access the same buffer.  For
> > example, redisplay will be unable to show that buffer if it is visible
> > in some window, because its notion of the buffer-local values might be
> > inaccurate.
> 
> Another thread will have its own local set of Vfoo. When that thread
> switches to a buffer, it will update its local Vfoo values.

And what happens if that thread also changes Vfoo?

> So, redisplay will have access to correct local values.

No, it won't, because redisplay runs only in the main thread,
remember?  So it will not see changes to Vfoo done by other threads.
This could sometimes be good (e.g., if the changes are temporary), but
sometimes bad (if the changes are permanent).

> >> Will it make sense to convert PT, ZV, and BEGV into thread-local variables?
> >
> > What do you expect redisplay to do when some thread moves point in a
> > way that it is no longer in the window?
> 
> Async threads will not trigger redisplay. And they will have their own
> PT, BEGV, and ZV.

This goes back to the other sub-thread, where we discussed how to show
and prompt the user from non-main threads.  The conclusion was that
there is no good solution to that.  The best proposal, wait for the
main thread, would mean that stuff like stealth fontifications, which
currently run from timers, cannot be run from a thread.

> >> Also, it looks reasonable to block BUF_MARKERS when we need to change
> >> BUF_MARKERS.
> >
> > Sure.  Like I said: we'd need to lock everything.
> 
> I kindly do not agree. It is not like a global lock. Yes, there will be
> a lot of blocking of individual Elisp objects. But it does not mean that
> everything will be locked.

I see no difference between locking everything and locking just 95%.

> I think there is a good way to tentatively check if everything will be
> locked or not - just check what will happen with consing. Consing
> appears to be one of the biggest bottlenecks that will basically cause
> global lock. If it can be demonstrated that consing is manageable, other
> things will pose less of an issue.

Consing is not even the tip of the iceberg.  The real bad problems are
elsewhere: in the global objects we access and modify.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 11:14                                                                   ` Eli Zaretskii
  2023-07-09 11:23                                                                     ` Ihor Radchenko
@ 2023-07-09 12:10                                                                     ` Po Lu
  2023-07-09 13:03                                                                       ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-09 12:10 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: yantar92, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>   . they look badly when compared to modern GUI facilities

Other GUI toolkits also cannot display progress indications outside the
main thread, so I'm confused as to how this comparison was made.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  9:57                                                       ` Ihor Radchenko
  2023-07-09 12:08                                                         ` Eli Zaretskii
@ 2023-07-09 12:22                                                         ` Po Lu
  2023-07-09 13:12                                                           ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-09 12:22 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Another thread will have its own local set of Vfoo. When that thread
> switches to a buffer, it will update its local Vfoo values. So,
> redisplay will have access to correct local values.

How do you propose to associate different bindings for global variables
with each thread?  Remember that access to even a C variable in TLS is
significantly more expensive than an access to a normal variable; for
example, in MIPS systems, such an access results in an instruction
emulation trap to the Unix kernel, and on many other systems requires
multiple system and function calls.

Multiple value cells will have to be maintained for each symbol that is
locally bound.  This alone will already be very expensive; treating all
symbols this way is definitely unacceptable.

> Async threads will not trigger redisplay. And they will have their own
> PT, BEGV, and ZV.
>
> Basically, I propose async threads not to set buffer->pt in the buffer
> object. They will operate using their own local excursion and
> restriction.

We already have window points that are distinct from PT and PT_BYTE.
Adding thread-local points and narrowing would only contribute more to
the confusion.

The straightforward solution is rather for Lisp to avoid editing buffers
that are currently being displayed from outside the main thread.

>> Sure.  Like I said: we'd need to lock everything.

But the interlocking will be specific to the object being locked.  Two
threads will be able to modify the marker lists of two distinct buffers
simultaneously.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 12:10                                                                     ` Po Lu
@ 2023-07-09 13:03                                                                       ` Eli Zaretskii
  0 siblings, 0 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09 13:03 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: yantar92@posteo.net,  emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 20:10:06 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >   . they look badly when compared to modern GUI facilities
> 
> Other GUI toolkits also cannot display progress indications outside the
> main thread, so I'm confused as to how this comparison was made.

In this particular point, I meant visually, not thread-wise.  We use
the "normal" Emacs display facilities: windows and frames, and those
don't look like what modern users expect to see for such widgets.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 12:22                                                         ` Po Lu
@ 2023-07-09 13:12                                                           ` Eli Zaretskii
  2023-07-10  0:18                                                             ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09 13:12 UTC (permalink / raw)
  To: Po Lu; +Cc: yantar92, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 20:22:30 +0800
> 
> >> Sure.  Like I said: we'd need to lock everything.
> 
> But the interlocking will be specific to the object being locked.  Two
> threads will be able to modify the marker lists of two distinct buffers
> simultaneously.

For this particular example, yes.  But we also have a lot of global
objects that are not specific to a buffer or a window.  Example:
buffer-list.  Another example: Vwindow_list (not exposed to Lisp).
Etc. etc.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 11:59                                                                             ` Eli Zaretskii
@ 2023-07-09 13:58                                                                               ` Ihor Radchenko
  2023-07-09 14:52                                                                                 ` Eli Zaretskii
                                                                                                   ` (2 more replies)
  0 siblings, 3 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-09 13:58 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> >> > Which variables can safely and usefully be made thread-local?
> ...
> In this case, the assumption is that the gap is always accurate except
> for short periods of time, during which buffer text cannot be accessed
> via buffer positions.

Thanks for the clarification.
But note that I was suggesting which global state variables may be
converted to thread-local.

I now understand that the gap can be moved by the code that is not
actually writing text in buffer. However, I do not see how this is a
problem we need to care about more than about generic problem with
simultaneous write.

If a variable or object value is being written, we need to block it.
If a buffer object is being written (like when moving the gap or writing
text), we need to block it. And this blocking will generally pose a
problem only when multiple threads try to access the same object, which
is generally unlikely.

The global state is another story. Redisplay, consing data, current
buffer, point, buffer narrowing, and C variables corresponding to
buffer-local Elisp variables are shared across all the threads, and
are often set by all the threads. And because point, narrowing, and some
buffer-locals (like `case-fold-search') are so ubiquitous, blocking them
will block everything. (Also, unwinding may contribute here, judging
from how thread.c juggles it)

So, if we want to have async support in Emacs, we need to find out how
to deal with each component of the global state without global locking:

1. There is consing, that is principally not asynchronous.

   It is not guaranteed, but I _hope_ that lock during consing
   can be manageable.

   We need to ensure that simultaneous consing will never happen. AFAIU,
   it should be ok if something that does not involve consing is running
   at the same time with cons (correct me if I am wrong here).

2. Redisplay cannot be asynchronous in a sense that it does not make
   sense that multiple threads, possibly working with different buffers
   and different points in those buffers, request redisplay
   simultaneously. Of course, it is impossible to display several places
   in a buffer at once.

   Only a single `main-thread' should be allowed to modify frames,
   window configurations, and generally trigger redisplay. And thread
   that attempts to do such modifications must wait to become
   `main-thread' first.

   This means that any code that is using things like
   `save-window-excursion', `display-buffer', and other display-related
   staff cannot run asynchronously.

   But I still believe that useful Elisp code can be written without a
   need to trigger redisplay. I have seen plenty of examples in Org and
   I have refactored a number of functions to avoid staff like
   `switch-to-buffer' in favour of `with-current-buffer'.

3. Current buffer, point position, and narrowing.

   By current design, Emacs always have a single global current buffer,
   current point position, and narrowing state in that buffer.
   Even when we switch cooperative threads, a thread must update its
   thread->current_buffer to previous_thread->current_buffer; and update
   point and narrowing by calling set_buffer_internal_2.

   Current design is incompatible with async threads - they must be able
   to have different buffers, points, and narrowing states current
   within each thread.

   That's why I suggested to convert PT, BEGV, and ZV into
   thread-locals.

   Note that PT, BEGV, and ZV are currently stored in buffer object
   before leaving a buffer and recovered when setting a new buffer.
   Async threads will make an assumption that
   (set-buffer "1") (goto-char 100) (set-buffer "2") (set-buffer "1")
   (= (point) 100) invalid.

4. Buffer-local variables, defined in C have C variable equivalents that
   are updated as Emacs changes current_buffer.
   
   AFAIU, their purpose is to make buffer-local variables and normal
   Elisp variables uniformly accessible from C code - C code does not
   need to worry about Vfoo being buffer-local or not, and just set it.

   This is not compatible with async threads that work with several buffers.

   I currently do not fully understand how defining C variables works in
   DEFVAR_LISP.

>> Asynchronous writing is probably a poor idea anyway - the very
>> idea of a gap does not work well when we need to write in multiple
>> far-away places in buffer.
>
> 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.

>> >> For example, `org-element-interpret-data' converts Org mode AST to
>> >> string. Just now, I tried it using AST of one of my large Org buffers.
>> >> It took 150seconds to complete, while blocking Emacs.
>> >
>> > It isn't side-effect-free, though.
>> 
>> It is, just not declared so.
>
> No, it isn't.  For starters, it changes obarray.

Do you mean `intern'? `intern-soft' would be equivalent there.

>> We do not, but it may be possible to add assertions that will ensure
>> purity in whatever sense we need.
>
> Those assertions will fire in any useful program with 100% certainty.
> Imagine the plight of an Emacs Lisp programmer who has to write and
> debug such programs.
>
> We have in Emacs gazillion lines of Lisp code, written, debugged, and
> tested during 4 decades.  We use those, almost without thinking, every
> day for writing Lisp programs.  What you suggest means throwing away
> most of that and starting from scratch.

There will indeed be a lot of work to make the range of Lisp functions
available for async code large enough. But it does not have to be done
all at once.

Of course, we first need to make sure that there are no hard blockers,
like global state. I do not think that Elisp code will be the blocker if
we find out how to deal with Emacs global state on C level.

> I mean, take the simplest thing, like save-buffer-excursion or
> with-selected-window, something that we use all the time, and look how
> much of the global state they access and change.  Then imagine that
> you don't have these and need to write programs that switch buffers
> and windows temporarily in thread-safe way.  Then reflect on what this
> means for all the other useful APIs and subroutines we have.

These examples are touching very basics aspects that we need to take
care of for async: (1) point/buffer; (2) unwind; (3) redisplay.
I think that (3) is not something that should be allowed as async. (1)
and (2) are to be discussed.

P.S. I am struggling to understand swap_in_symval_forwarding:

      /* Unload the previously loaded binding.  */
      tem1 = blv->valcell;

Is the above assignment redundant?

      if (blv->fwd.fwdptr)
	set_blv_value (blv, do_symval_forwarding (blv->fwd));

      /* Choose the new binding.  */
      {
	Lisp_Object var;
	XSETSYMBOL (var, symbol);
	tem1 = assq_no_quit (var, BVAR (current_buffer, local_var_alist));

This assignment always triggers after the first one, overriding it.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 12:08                                                         ` Eli Zaretskii
@ 2023-07-09 14:16                                                           ` Ihor Radchenko
  2023-07-09 15:00                                                             ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-09 14:16 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Of course, if there are breaking future changes on C level will need to
>> account for concurrency.
>
> Who will remember that we at some point "assumed" buffer->base_buffer
> can change _only_ in make-indirect-buffer?

Ok. So, in other words, we need to make sure that any changes in a
buffer and also in indirect buffers will lock.
Trying to be more granular is not reliable.

>> So, redisplay will have access to correct local values.
>
> No, it won't, because redisplay runs only in the main thread,
> remember?  So it will not see changes to Vfoo done by other threads.
> This could sometimes be good (e.g., if the changes are temporary), but
> sometimes bad (if the changes are permanent).

If redisplay is about to display buffer that is being modified
(including its buffer-local values), it will have to lock until the
modification is done. Same for global Lisp variables.

>> > What do you expect redisplay to do when some thread moves point in a
>> > way that it is no longer in the window?
>> 
>> Async threads will not trigger redisplay. And they will have their own
>> PT, BEGV, and ZV.
>
> This goes back to the other sub-thread, where we discussed how to show
> and prompt the user from non-main threads.  The conclusion was that
> there is no good solution to that.  The best proposal, wait for the
> main thread, would mean that stuff like stealth fontifications, which
> currently run from timers, cannot be run from a thread.

May you provide a link?
I am not sure how independent PT+buffer in different threads affects
prompts.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 13:58                                                                               ` Ihor Radchenko
@ 2023-07-09 14:52                                                                                 ` Eli Zaretskii
  2023-07-09 15:49                                                                                   ` Ihor Radchenko
  2023-07-16 14:58                                                                                 ` Ihor Radchenko
  2023-07-24  8:42                                                                                 ` Ihor Radchenko
  2 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09 14:52 UTC (permalink / raw)
  To: Ihor Radchenko, Stefan Monnier; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 13:58:51 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> >> > Which variables can safely and usefully be made thread-local?
> > ...
> > In this case, the assumption is that the gap is always accurate except
> > for short periods of time, during which buffer text cannot be accessed
> > via buffer positions.
> 
> Thanks for the clarification.
> But note that I was suggesting which global state variables may be
> converted to thread-local.
> 
> I now understand that the gap can be moved by the code that is not
> actually writing text in buffer. However, I do not see how this is a
> problem we need to care about more than about generic problem with
> simultaneous write.

Imagine a situation where we need to process XML or HTML, and since
that's quite expensive, we want to do that in a thread.  What you are
saying is that this will either be impossible/impractical to do from a
thread, or will require to lock the entire buffer from access, because
the above processing moves the gap.  If that is not a problem, I don't
know what is, because there could be a lot of such scenarios, and they
all will be either forbidden or very hard to implement.

> If a variable or object value is being written, we need to block it.
> If a buffer object is being written (like when moving the gap or writing
> text), we need to block it. And this blocking will generally pose a
> problem only when multiple threads try to access the same object, which
> is generally unlikely.

My impression is that this is very likely, because of the many global
objects in Emacs.  Moreover, if you intend to allow several threads
using the same buffer (and I'm not yet sure whether you want that or
not), then the buffer-local variables of that buffer present the same
problem as global variables.  Take the case-table or display-table,
for example: those are buffer-local in many cases, but their changes
will affect all the threads that work on the buffer.

> 1. There is consing, that is principally not asynchronous.
> 
>    It is not guaranteed, but I _hope_ that lock during consing
>    can be manageable.
> 
>    We need to ensure that simultaneous consing will never happen. AFAIU,
>    it should be ok if something that does not involve consing is running
>    at the same time with cons (correct me if I am wrong here).

What do you do if some thread hits the memory-full condition?  The
current handling includes GC.

> 2. Redisplay cannot be asynchronous in a sense that it does not make
>    sense that multiple threads, possibly working with different buffers
>    and different points in those buffers, request redisplay
>    simultaneously. Of course, it is impossible to display several places
>    in a buffer at once.

But what about different threads redisplaying different windows? is
that allowed?  If not, here goes one more benefit of concurrent
threads.

Also, that issue with prompting the user also needs some solution,
otherwise the class of jobs that non-main threads can do will be even
smaller.

>    Only a single `main-thread' should be allowed to modify frames,
>    window configurations, and generally trigger redisplay. And thread
>    that attempts to do such modifications must wait to become
>    `main-thread' first.

What about changes to frame-parameters?  Those don't necessarily
affect display.

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

>    Async threads will make an assumption that
>    (set-buffer "1") (goto-char 100) (set-buffer "2") (set-buffer "1")
>    (= (point) 100) invalid.

If this is invalid, I don't see how one can write useful Lisp
programs, except of we request Lisp to explicitly define critical
sections.

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

> >> >> For example, `org-element-interpret-data' converts Org mode AST to
> >> >> string. Just now, I tried it using AST of one of my large Org buffers.
> >> >> It took 150seconds to complete, while blocking Emacs.
> >> >
> >> > It isn't side-effect-free, though.
> >> 
> >> It is, just not declared so.
> >
> > No, it isn't.  For starters, it changes obarray.
> 
> Do you mean `intern'? `intern-soft' would be equivalent there.

"Equivalent" in what way?  AFAIU, the function does want to create a
symbol when it doesn't already exist.

> > Those assertions will fire in any useful program with 100% certainty.
> > Imagine the plight of an Emacs Lisp programmer who has to write and
> > debug such programs.
> >
> > We have in Emacs gazillion lines of Lisp code, written, debugged, and
> > tested during 4 decades.  We use those, almost without thinking, every
> > day for writing Lisp programs.  What you suggest means throwing away
> > most of that and starting from scratch.
> 
> There will indeed be a lot of work to make the range of Lisp functions
> available for async code large enough. But it does not have to be done
> all at once.

No, it doesn't.  But until we have enough of those functions
available, one will be unable to write applications without
implementing and debugging a lot of those new functions as part of the
job.  It will make simple programming jobs much larger and more
complicated, especially since it will require the programmers to
understand very well the limitations and requirements of concurrent
code programming, something Lisp programmers don't know very well, and
rightfully so.

> 
> Of course, we first need to make sure that there are no hard blockers,
> like global state. I do not think that Elisp code will be the blocker if
> we find out how to deal with Emacs global state on C level.
> 
> > I mean, take the simplest thing, like save-buffer-excursion or
> > with-selected-window, something that we use all the time, and look how
> > much of the global state they access and change.  Then imagine that
> > you don't have these and need to write programs that switch buffers
> > and windows temporarily in thread-safe way.  Then reflect on what this
> > means for all the other useful APIs and subroutines we have.
> 
> These examples are touching very basics aspects that we need to take
> care of for async: (1) point/buffer; (2) unwind; (3) redisplay.
> I think that (3) is not something that should be allowed as async. (1)
> and (2) are to be discussed.
> 
> P.S. I am struggling to understand swap_in_symval_forwarding:
> 
>       /* Unload the previously loaded binding.  */
>       tem1 = blv->valcell;
> 
> Is the above assignment redundant?

I'll let Stefan answer that, since he made the change in commit
ce5b453a449 that resulted in this code.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 14:16                                                           ` Ihor Radchenko
@ 2023-07-09 15:00                                                             ` Eli Zaretskii
  0 siblings, 0 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09 15:00 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 14:16:31 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> So, redisplay will have access to correct local values.
> >
> > No, it won't, because redisplay runs only in the main thread,
> > remember?  So it will not see changes to Vfoo done by other threads.
> > This could sometimes be good (e.g., if the changes are temporary), but
> > sometimes bad (if the changes are permanent).
> 
> If redisplay is about to display buffer that is being modified
> (including its buffer-local values), it will have to lock until the
> modification is done. Same for global Lisp variables.

Here goes one more advantage of concurrency, then: while redisplay
runs, all the threads that access buffers shown on display will have
to block.

> >> > What do you expect redisplay to do when some thread moves point in a
> >> > way that it is no longer in the window?
> >> 
> >> Async threads will not trigger redisplay. And they will have their own
> >> PT, BEGV, and ZV.
> >
> > This goes back to the other sub-thread, where we discussed how to show
> > and prompt the user from non-main threads.  The conclusion was that
> > there is no good solution to that.  The best proposal, wait for the
> > main thread, would mean that stuff like stealth fontifications, which
> > currently run from timers, cannot be run from a thread.
> 
> May you provide a link?
> I am not sure how independent PT+buffer in different threads affects
> prompts.

No, I meant the "Async threads will not trigger redisplay" part.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 14:52                                                                                 ` Eli Zaretskii
@ 2023-07-09 15:49                                                                                   ` Ihor Radchenko
  2023-07-09 16:35                                                                                     ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-09 15:49 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Stefan Monnier, luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> I now understand that the gap can be moved by the code that is not
>> actually writing text in buffer. However, I do not see how this is a
>> problem we need to care about more than about generic problem with
>> simultaneous write.
>
> Imagine a situation where we need to process XML or HTML, and since
> that's quite expensive, we want to do that in a thread.  What you are
> saying is that this will either be impossible/impractical to do from a
> thread, or will require to lock the entire buffer from access, because
> the above processing moves the gap.  If that is not a problem, I don't
> know what is, because there could be a lot of such scenarios, and they
> all will be either forbidden or very hard to implement.

In this particular example, the need to move the gap is there because
htmlReadMemory requires memory segment as input. Obviously, it requires
a block in the current implementation.

Can it be done async-safe? We would need to memcpy the parsed buffer
block. Or up to 3 memcpy if we do not want to move the gap.

>> If a variable or object value is being written, we need to block it.
>> If a buffer object is being written (like when moving the gap or writing
>> text), we need to block it. And this blocking will generally pose a
>> problem only when multiple threads try to access the same object, which
>> is generally unlikely.
>
> My impression is that this is very likely, because of the many global
> objects in Emacs.

There are many objects, but each individual thread will use a subset of
these objects. What are the odds that intersection of these subsets are
frequent? Not high, except certain frequently used objects. And we need
to focus on identifying and figuring out what to do with these
likely-to-clash objects.

> ... Moreover, if you intend to allow several threads
> using the same buffer (and I'm not yet sure whether you want that or
> not),

It would be nice if multiple threads can work with the same buffer in
read-only mode, maybe with a single main thread editing the buffer (and
pausing the async read-only threads while doing so).
Writing simultaneously is a much bigger ask.

> ... then the buffer-local variables of that buffer present the same
> problem as global variables.  Take the case-table or display-table,
> for example: those are buffer-local in many cases, but their changes
> will affect all the threads that work on the buffer.

And how frequently are case-table and display-table changed? AFAIK, not
frequently at all.

>>    We need to ensure that simultaneous consing will never happen. AFAIU,
>>    it should be ok if something that does not involve consing is running
>>    at the same time with cons (correct me if I am wrong here).
>
> What do you do if some thread hits the memory-full condition?  The
> current handling includes GC.

May you please explain a bit more about the situation you are referring
to? My above statement was about consing, not GC.

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.

>> 2. Redisplay cannot be asynchronous in a sense that it does not make
>>    sense that multiple threads, possibly working with different buffers
>>    and different points in those buffers, request redisplay
>>    simultaneously. Of course, it is impossible to display several places
>>    in a buffer at once.
>
> But what about different threads redisplaying different windows? is
> that allowed?  If not, here goes one more benefit of concurrent
> threads.

I think I need to elaborate what I mean by "redisplay cannot be
asynchronous".

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.

Is Emacs display code even capable of redisplaying two different windows
at the same time?

> Also, that issue with prompting the user also needs some solution,
> otherwise the class of jobs that non-main threads can do will be even
> smaller.

We can make reading input using similar idea to the above, but it will
always block until the response.

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.

>>    Only a single `main-thread' should be allowed to modify frames,
>>    window configurations, and generally trigger redisplay. And thread
>>    that attempts to do such modifications must wait to become
>>    `main-thread' first.
>
> What about changes to frame-parameters?  Those don't necessarily
> affect display.

But doesn't it depend on graphic toolkit? I got an impression (from Po
Lu's replies) that graphic toolkits generally do not handle async
requests well.

>>    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. A given frame must always have a single window active, which is not
compatible with async threads.
In addition, `with-selected-window' triggers redisplay. In particular,
it triggers redisplaying mode-lines.

It is a problem similar to async redisplay.

>>    Async threads will make an assumption that
>>    (set-buffer "1") (goto-char 100) (set-buffer "2") (set-buffer "1")
>>    (= (point) 100) invalid.
>
> If this is invalid, I don't see how one can write useful Lisp
> programs, except of we request Lisp to explicitly define critical
> sections.

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 I realize that something like

(while (re-search-forward "foo") nil t)
  (with-current-buffer "bar" (insert (match-string 0))))

may be broken if point is moved when switching between "bar" and "foo".

Maybe, the last PV, ZV, and BEGV should not be stored in the buffer
object upon switching away and instead recorded in a thread-local
((buffer PV ZV BEGV) ...) alist. Then, thread will set PV, ZV, and BEGV
from its local alist rather than by reading buffer->... values.

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

>> >> >> For example, `org-element-interpret-data' converts Org mode AST to
>> >> >> string. Just now, I tried it using AST of one of my large Org buffers.
>> >> >> It took 150seconds to complete, while blocking Emacs.
>> >> >
>> >> > It isn't side-effect-free, though.
>> >> 
>> >> It is, just not declared so.
>> >
>> > No, it isn't.  For starters, it changes obarray.
>> 
>> Do you mean `intern'? `intern-soft' would be equivalent there.
>
> "Equivalent" in what way?  AFAIU, the function does want to create a
> symbol when it doesn't already exist.

No.
(intern (format "org-element-%s-interpreter" type)) is just to retrieve
existing function symbol used for a given AST element type.

(interpret
		      (let ((fun (intern-soft
				  (format "org-element-%s-interpreter" type))))
			(if (and fun (fboundp fun)) fun (lambda (_ contents) contents))))

would also work.

To be clear, I do know how this function is designed to work.
It may not be de-facto pure, but that's just because nobody tried to
ensure it - the usefulness of pure declarations is questionable in Emacs
now.

>> There will indeed be a lot of work to make the range of Lisp functions
>> available for async code large enough. But it does not have to be done
>> all at once.
>
> No, it doesn't.  But until we have enough of those functions
> available, one will be unable to write applications without
> implementing and debugging a lot of those new functions as part of the
> job.  It will make simple programming jobs much larger and more
> complicated, especially since it will require the programmers to
> understand very well the limitations and requirements of concurrent
> code programming, something Lisp programmers don't know very well, and
> rightfully so.

I disagree.
If Emacs supports async threads, it does not mean that every single
peace of Elisp should be async-compatible.
But if a programmer is explicitly writing async code, it is natural to
expect limitations.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 15:49                                                                                   ` Ihor Radchenko
@ 2023-07-09 16:35                                                                                     ` Eli Zaretskii
  2023-07-10 11:30                                                                                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-09 16:35 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: monnier, luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Stefan Monnier <monnier@iro.umontreal.ca>, luangruo@yahoo.com,
>  emacs-devel@gnu.org
> Date: Sun, 09 Jul 2023 15:49:41 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > ... then the buffer-local variables of that buffer present the same
> > problem as global variables.  Take the case-table or display-table,
> > for example: those are buffer-local in many cases, but their changes
> > will affect all the threads that work on the buffer.
> 
> 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?

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.

> >>    We need to ensure that simultaneous consing will never happen. AFAIU,
> >>    it should be ok if something that does not involve consing is running
> >>    at the same time with cons (correct me if I am wrong here).
> >
> > What do you do if some thread hits the memory-full condition?  The
> > current handling includes GC.
> 
> 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.

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

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

> Is Emacs display code even capable of redisplaying two different windows
> at the same time?

Maybe.  At least the GUI redisplay proceeds one window at a time, so
we already deal with each window separately.  There are some caveats,
naturally: redisplay runs hooks, which could access other windows,
redisplaying a window also updates the frame title, etc. -- those will
need to be carefully examined.

> > Also, that issue with prompting the user also needs some solution,
> > otherwise the class of jobs that non-main threads can do will be even
> > smaller.
> 
> 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

> >>    Only a single `main-thread' should be allowed to modify frames,
> >>    window configurations, and generally trigger redisplay. And thread
> >>    that attempts to do such modifications must wait to become
> >>    `main-thread' first.
> >
> > 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.

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

> In addition, `with-selected-window' triggers redisplay. In particular,
> it triggers redisplaying mode-lines.

No, with-selected-window doesn't do any redisplay, it only marks the
window for redisplay, i.e. it suppresses the redisplay optimization
which could decide that this window doesn't need to be redrawn.
Redisplay itself will happen normally, usually when Emacs is idle,
i.e. when the command which called with-selected-window finishes.

> >>    Async threads will make an assumption that
> >>    (set-buffer "1") (goto-char 100) (set-buffer "2") (set-buffer "1")
> >>    (= (point) 100) invalid.
> >
> > If this is invalid, I don't see how one can write useful Lisp
> > programs, except of we request Lisp to explicitly define critical
> > sections.
> 
> 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.

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

> To be clear, I do know how this function is designed to work.
> It may not be de-facto pure, but that's just because nobody tried to
> ensure it - the usefulness of pure declarations is questionable in Emacs
> now.

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.

> >> There will indeed be a lot of work to make the range of Lisp functions
> >> available for async code large enough. But it does not have to be done
> >> all at once.
> >
> > No, it doesn't.  But until we have enough of those functions
> > available, one will be unable to write applications without
> > implementing and debugging a lot of those new functions as part of the
> > job.  It will make simple programming jobs much larger and more
> > complicated, especially since it will require the programmers to
> > understand very well the limitations and requirements of concurrent
> > code programming, something Lisp programmers don't know very well, and
> > rightfully so.
> 
> I disagree.

So let's agree to disagree.  Because I don't see how it will benefit
anything to continue this dispute.  I've said everything I have to
say, sometimes more than once.  At least Po Lu seems to agree that
concurrent Emacs means we will have to write a lot of new routines,
and that code which is meant to run in non-main threads will have to
be written very specially.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  9:36                                                                           ` Ihor Radchenko
  2023-07-09  9:56                                                                             ` Po Lu
  2023-07-09 11:59                                                                             ` Eli Zaretskii
@ 2023-07-09 17:13                                                                             ` Gregory Heytings
  2023-07-10 11:37                                                                               ` Ihor Radchenko
  2 siblings, 1 reply; 192+ messages in thread
From: Gregory Heytings @ 2023-07-09 17:13 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, luangruo, emacs-devel


>> I don't believe any useful Lisp program in Emacs can be 
>> side-effect-free, for the purposes of this discussion.  Every single 
>> one of them accesses the global state and changes the global state.
>
> As I said, I hope that we can convert the important parts of the global 
> state into thread-local state.
>

That's not possible alas.  Basically Emacs' global state is the memory it 
has allocated, which is an enormous graph in which objects are the nodes 
and pointers are the edges.  Any object may be linked to any number of 
other objects (which means that you cannot isolate a subgraph, or even a 
single object, of the graph), and any non-trivial Elisp function (as well 
as garbage collection and redisplay) can change any of the objects and the 
structure of the graph at any time (which means that two threads cannot 
use the same graph concurrently without both of them taking the risk of 
finding the graph suddenly different from what it was during the execution 
of the previous opcode).

Given that, the only thing can be done in practice (which is what 
emacs-async does) is to create another Emacs instance, with another global 
state, in which some processing is done, and report the result back to the 
main Emacs instance.

The only alternative is to create another Emacs from scratch, with 
concurrency as one of its design principles.

>>> Yes. I mean... look at Haskell. There is no shortage of pure 
>>> functional libraries there.
>>
>> I cannot follow you there: I don't know Haskell.
>
> In short, pure functions in Haskell can utilize multiple CPUs 
> automatically, without programmers explicitly writing code for 
> multi-threading support.
>

The problem is that Elisp is not Haskell, and cannot be converted into 
something that resembles Haskell.  Haskell functions are pure by default 
(impurity is the rare exception), Elisp functions are impure by default 
(purity is the rare exception).




^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 13:12                                                           ` Eli Zaretskii
@ 2023-07-10  0:18                                                             ` Po Lu
  0 siblings, 0 replies; 192+ messages in thread
From: Po Lu @ 2023-07-10  0:18 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: yantar92, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> For this particular example, yes.  But we also have a lot of global
> objects that are not specific to a buffer or a window.  Example:
> buffer-list.  Another example: Vwindow_list (not exposed to Lisp).
> Etc. etc.

They should have individual interlocks of their own, instead of sharing
a large global lock.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 16:35                                                                                     ` Eli Zaretskii
@ 2023-07-10 11:30                                                                                       ` Ihor Radchenko
  2023-07-10 12:13                                                                                         ` Po Lu
  2023-07-10 13:09                                                                                         ` Eli Zaretskii
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-10 11:30 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: monnier, luangruo, emacs-devel

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>



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 17:13                                                                             ` Gregory Heytings
@ 2023-07-10 11:37                                                                               ` Ihor Radchenko
  2023-07-13 13:54                                                                                 ` Gregory Heytings
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-10 11:37 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: Eli Zaretskii, luangruo, emacs-devel

Gregory Heytings <gregory@heytings.org> writes:

>> As I said, I hope that we can convert the important parts of the global 
>> state into thread-local state.
>>
>
> That's not possible alas.  Basically Emacs' global state is the memory it 
> has allocated, which is an enormous graph in which objects are the nodes 
> and pointers are the edges.  Any object may be linked to any number of 
> other objects (which means that you cannot isolate a subgraph, or even a 
> single object, of the graph), and any non-trivial Elisp function (as well 
> as garbage collection and redisplay) can change any of the objects and the 
> structure of the graph at any time (which means that two threads cannot 
> use the same graph concurrently without both of them taking the risk of 
> finding the graph suddenly different from what it was during the execution 
> of the previous opcode).

That's already similar for cooperative threads. The difference is that
sudden changes are limited to `thread-yield', while async threads will
have to be written keeping in mind that global variables may be changed
during execution unless explicitly locked.

And yes, we will need to implement locking on Elisp object level and
also on Elisp variable level.

But the very need to access global Elisp variables/objects from async
threads should not be considered a good practice (except near start/end
of thread execution). Most of processing should be done using threads'
local lexical scope.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 11:30                                                                                       ` Ihor Radchenko
@ 2023-07-10 12:13                                                                                         ` Po Lu
  2023-07-10 12:28                                                                                           ` Ihor Radchenko
  2023-07-10 13:09                                                                                         ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-10 12:13 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, monnier, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

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

What if even one of those other threads never calls maybe_gc?  This can
easily happen if they are blocked by a long running operation (domain
name resolution comes to mind) and will result in all threads waiting
for it to complete, defeating the purpose of having threads in the first
place.

TRT on GNU and other Mach based systems (OSF/1, OS X, etc) is to suspend
all other threads using `thread_suspend' and then run GC from whichever
thread is the first to discover that the consing threshold has been
exceeded.  Unix systems typically provide other platform specific
functions to suspend threads or LWPs.

As a consequence of this approach, GC can take place even if those other
threads hold pointers to relocatable string data and buffer text.  I've
experimentally verified that this is rare, and that not compacting
string blocks which are referenced from the stack or in registers
doesn't significantly affect string data fragmentation.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 12:13                                                                                         ` Po Lu
@ 2023-07-10 12:28                                                                                           ` Ihor Radchenko
  2023-07-10 12:48                                                                                             ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-10 12:28 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, monnier, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> 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.
>
> What if even one of those other threads never calls maybe_gc?  This can
> easily happen if they are blocked by a long running operation (domain
> name resolution comes to mind) and will result in all threads waiting
> for it to complete, defeating the purpose of having threads in the first
> place.

Good point.
Maybe something similar to how thread-yield is triggered by
`accept-process-output' - add extra maybe_gc() to process code as
necessary.

> TRT on GNU and other Mach based systems (OSF/1, OS X, etc) is to suspend
> all other threads using `thread_suspend' and then run GC from whichever
> thread is the first to discover that the consing threshold has been
> exceeded.  Unix systems typically provide other platform specific
> functions to suspend threads or LWPs.
>
> As a consequence of this approach, GC can take place even if those other
> threads hold pointers to relocatable string data and buffer text.  I've
> experimentally verified that this is rare, and that not compacting
> string blocks which are referenced from the stack or in registers
> doesn't significantly affect string data fragmentation.

Sounds not 100% reliable.
May it be possible to restart blocking operation if GC is triggered in
the middle of it?

What I have in mind is something similar to

(setq sucess nil)
(while (not success)
  (while-no-input
    (do-staff-that-knows-it-can-be-restarted-sometimes)
    (setq sucess t)))

but for low-level code that requires object locks.    

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 12:28                                                                                           ` Ihor Radchenko
@ 2023-07-10 12:48                                                                                             ` Po Lu
  2023-07-10 12:53                                                                                               ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-10 12:48 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, monnier, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Sounds not 100% reliable.

It is 100% reliable, at least when implemented with system specific
code.  POSIX is iffy in this regard.

> May it be possible to restart blocking operation if GC is triggered in
> the middle of it?

You cannot make an LWP that is performing name resolution call
`maybe_gc', but it can be suspended outright.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 12:48                                                                                             ` Po Lu
@ 2023-07-10 12:53                                                                                               ` Ihor Radchenko
  2023-07-10 13:18                                                                                                 ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-10 12:53 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, monnier, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> Sounds not 100% reliable.
>
> It is 100% reliable, at least when implemented with system specific
> code.  POSIX is iffy in this regard.

Then, may GC simply suspend all the threads every time, not just when we
have memory_full condition?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 11:30                                                                                       ` Ihor Radchenko
  2023-07-10 12:13                                                                                         ` Po Lu
@ 2023-07-10 13:09                                                                                         ` Eli Zaretskii
  2023-07-10 13:58                                                                                           ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-10 13:09 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: monnier, luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: monnier@iro.umontreal.ca, luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Mon, 10 Jul 2023 11:30:10 +0000
> 
> AFAIU, it is currently not possible to redisplay asynchronously.

The main reason for that is that redisplay accesses the global state
in many places, and so it needs that global state to stay put.  Wed
already have trouble with keeping this so because we allow to run Lisp
from various hooks called by redisplay and via :eval in the mode line.
Quite a few bugs were caused by these, and had to be fixed by "fixing
up" the state, like making sure the selected frame/window were not
deleted under your feet etc.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 12:53                                                                                               ` Ihor Radchenko
@ 2023-07-10 13:18                                                                                                 ` Po Lu
  0 siblings, 0 replies; 192+ messages in thread
From: Po Lu @ 2023-07-10 13:18 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, monnier, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Then, may GC simply suspend all the threads every time, not just when we
> have memory_full condition?

The easiest way to make Emacs's GC thread-safe is to make it suspend
every thread other than the one performing garbage collection.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 13:09                                                                                         ` Eli Zaretskii
@ 2023-07-10 13:58                                                                                           ` Ihor Radchenko
  2023-07-10 14:37                                                                                             ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-10 13:58 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: monnier, luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> AFAIU, it is currently not possible to redisplay asynchronously.
>
> The main reason for that is that redisplay accesses the global state
> in many places, and so it needs that global state to stay put.  Wed
> already have trouble with keeping this so because we allow to run Lisp
> from various hooks called by redisplay and via :eval in the mode line.
> Quite a few bugs were caused by these, and had to be fixed by "fixing
> up" the state, like making sure the selected frame/window were not
> deleted under your feet etc.

Do you know which particular parts of the global state are necessary for
redisplay? You mentioned current_frame and current_window. What else?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 13:58                                                                                           ` Ihor Radchenko
@ 2023-07-10 14:37                                                                                             ` Eli Zaretskii
  2023-07-10 14:55                                                                                               ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-10 14:37 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: monnier, luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: monnier@iro.umontreal.ca, luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Mon, 10 Jul 2023 13:58:48 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> AFAIU, it is currently not possible to redisplay asynchronously.
> >
> > The main reason for that is that redisplay accesses the global state
> > in many places, and so it needs that global state to stay put.  Wed
> > already have trouble with keeping this so because we allow to run Lisp
> > from various hooks called by redisplay and via :eval in the mode line.
> > Quite a few bugs were caused by these, and had to be fixed by "fixing
> > up" the state, like making sure the selected frame/window were not
> > deleted under your feet etc.
> 
> Do you know which particular parts of the global state are necessary for
> redisplay? You mentioned current_frame and current_window. What else?

You are asking questions that would require me to do a lot of code
scanning and investigating to produce a full answer.  So please
forgive me if I can afford only some random examples that pop up in my
mind, not even close to the exhaustive list:

  . the window tree (redisplay traverses it depth-first)
  . point position of displayed buffers (needed to decide whether to
    scroll the window)
  . narrowing of each displayed buffer
  . text properties and overlays of each displayed buffer
  . which window is selected on each frame
  . window-start position of each window
  . variables affecting display: display-line-numbers,
    show-trailing-whitespace, line-spacing, auto-compression-mode,
    auto-hscroll-mode, mode-line-format, etc.

And that is just a few seconds of thinking, I'm sure I forget a lot of
others.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09  8:41                                                                   ` Eli Zaretskii
@ 2023-07-10 14:53                                                                     ` Dmitry Gutov
  0 siblings, 0 replies; 192+ messages in thread
From: Dmitry Gutov @ 2023-07-10 14:53 UTC (permalink / raw)
  To: Eli Zaretskii, Ihor Radchenko; +Cc: luangruo, emacs-devel

On 09/07/2023 11:41, Eli Zaretskii wrote:
>> From: Ihor Radchenko<yantar92@posteo.net>
>> Cc: Po Lu<luangruo@yahoo.com>,emacs-devel@gnu.org
>> Date: Sun, 09 Jul 2023 07:57:47 +0000
>>
>> Eli Zaretskii<eliz@gnu.org>  writes:
>>
>>>> When programmers write such code for other interactive programs, they
>>>> are comfortable with the limitations of running code outside of the UI
>>>> thread.  Why should writing new, thread-safe Lisp for Emacs be any more
>>>> difficult?
>>> Because we'd need to throw away 40 years of Lisp programming, and
>>> rewrite almost every bit of what was written since then.  It's a huge
>>> setback for writing Emacs applications.
>> May you please elaborate why exactly do we need to rewrite everything?
> We already did, please read the previous messages.  In a nutshell:
> because most of the Lisp code we have cannot be run from an async
> thread.

IME most of the code we have works decently already, and what we've been 
missing is a way to speed up certain crunchy bits without spawning an 
additional Emacs process (with all the coding pain and overhead that 
that entails). Workloads, for example, like creating a buffer (pinned to 
the thread), calling a process (asynchronously or not), getting JSON 
from it, parsing said JSON, processing the result, and returning it in 
some shape to the parent (probably main) thread. Most of the work that 
Gnus does, I think, also fits in that rough category.

Reporting on progress can be done by sending messages to the main 
thread, either via a dedicated mechanism ("messages" like in JavaScript 
Workers), or by changing some global variable as decided by the code 
author. The variable access would have to be synchronized, of course.

The worker thread will need an efficient way to return the computation 
result too (which still might be large, memory-wise), though. Maybe 
it'll use the same communication mechanism as progress report, maybe 
not. But it's something to keep in mind.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 14:37                                                                                             ` Eli Zaretskii
@ 2023-07-10 14:55                                                                                               ` Ihor Radchenko
  2023-07-10 16:03                                                                                                 ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-10 14:55 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: monnier, luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Do you know which particular parts of the global state are necessary for
>> redisplay? You mentioned current_frame and current_window. What else?
>
> You are asking questions that would require me to do a lot of code
> scanning and investigating to produce a full answer.  So please
> forgive me if I can afford only some random examples that pop up in my
> mind, not even close to the exhaustive list:

Thanks, that is good enough.
I mostly wanted to get pointers to interesting places in that 1.2Mb
xdisp.c file to save myself from reading it from top to bottom.

>   . the window tree (redisplay traverses it depth-first)
>   . point position of displayed buffers (needed to decide whether to
>     scroll the window)

Maybe point position in window?

>   . narrowing of each displayed buffer
>   . text properties and overlays of each displayed buffer
>   . which window is selected on each frame
>   . window-start position of each window
>   . variables affecting display: display-line-numbers,
>     show-trailing-whitespace, line-spacing, auto-compression-mode,
>     auto-hscroll-mode, mode-line-format, etc.

These lay in 4 categories:
1. Data attached to frame object (window tree)
2. Data attached to window object (point position, window-start, etc)
3. Data attached to buffer object (buffer-local variables, narrowing)
4. Global Lisp variables

I have a suspicion that at least windows in a frame might be redisplayed
in parallel, unless some strange Elisp code is allowed to modify things
affecting redisplay.

May I know what happens if redisplay code sets variables like
line-spacing or display-line-numbers? Is it allowed?

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 14:55                                                                                               ` Ihor Radchenko
@ 2023-07-10 16:03                                                                                                 ` Eli Zaretskii
  0 siblings, 0 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-10 16:03 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: monnier, luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: monnier@iro.umontreal.ca, luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Mon, 10 Jul 2023 14:55:36 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > You are asking questions that would require me to do a lot of code
> > scanning and investigating to produce a full answer.  So please
> > forgive me if I can afford only some random examples that pop up in my
> > mind, not even close to the exhaustive list:
> 
> Thanks, that is good enough.
> I mostly wanted to get pointers to interesting places in that 1.2Mb
> xdisp.c file to save myself from reading it from top to bottom.

You'd still need to do that, because the list I posted is nowhere near
complete.

> >   . the window tree (redisplay traverses it depth-first)
> >   . point position of displayed buffers (needed to decide whether to
> >     scroll the window)
> 
> Maybe point position in window?

No, point of the buffer.  We copy the window-point to the buffer point
once, but assume it stays put thereafter, until we are done with the
window.

> >   . narrowing of each displayed buffer
> >   . text properties and overlays of each displayed buffer
> >   . which window is selected on each frame
> >   . window-start position of each window
> >   . variables affecting display: display-line-numbers,
> >     show-trailing-whitespace, line-spacing, auto-compression-mode,
> >     auto-hscroll-mode, mode-line-format, etc.
> 
> These lay in 4 categories:
> 1. Data attached to frame object (window tree)
> 2. Data attached to window object (point position, window-start, etc)
> 3. Data attached to buffer object (buffer-local variables, narrowing)
> 4. Global Lisp variables

Beware: you categorize and are making conclusions based on incomplete
information.

> I have a suspicion that at least windows in a frame might be redisplayed
> in parallel, unless some strange Elisp code is allowed to modify things
> affecting redisplay.

Mostly, yes.  But there are things like resize-mini-windows that can
affect other windows.  Also, this is limited to GUI redisplay; TTY
frames have additional subtleties.

> May I know what happens if redisplay code sets variables like
> line-spacing or display-line-numbers? Is it allowed?

In a nutshell, we need to abort the current redisplay cycle and start
anew.  So doing that is a very bad idea.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-10 11:37                                                                               ` Ihor Radchenko
@ 2023-07-13 13:54                                                                                 ` Gregory Heytings
  2023-07-13 14:23                                                                                   ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Gregory Heytings @ 2023-07-13 13:54 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, luangruo, emacs-devel


>
> while async threads will have to be written keeping in mind that global 
> variables may be changed during execution unless explicitly locked.
>
> And yes, we will need to implement locking on Elisp object level and 
> also on Elisp variable level.
>

It's not completely clear (to me) what you mean by "locking".  But I 
assume you mean two operations lock/unlock with which a thread can request 
and waive exclusive access to an object, and sleeps until that exclusive 
access is granted.  IOW, mutexes.  If my guess is correct, that is not 
possible.  You cannot use mutexes in a program whose data structures have 
not been organized in a way that makes the use of such synchronization 
primitives possible, without having deadlocks.  Given that in Emacs 
objects are part of an enormous unstructured graph, with pointers leading 
from anywhere to anywhere, that's clearly not the case, and all you can 
use is a single global lock.

But...

>
> But the very need to access global Elisp variables/objects from async 
> threads should not be considered a good practice (except near start/end 
> of thread execution). Most of processing should be done using threads' 
> local lexical scope.
>

... if what you have in mind are async threads that should in fact not 
access objects of the main thread, why is it necessary to lock objects? 
You can prepare the arguments to the async thread in the main thread, 
start the async thread, and when its execution completes process the 
return values of the async thread in the main thread, which is what 
emacs-async already does.

May I ask you if you have a concrete example of a task that you would like 
to perform with such threads, and that cannot already be done with 
emacs-async?  In other words, what are the limitations of emacs-async you 
try to overcome?




^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-13 13:54                                                                                 ` Gregory Heytings
@ 2023-07-13 14:23                                                                                   ` Ihor Radchenko
  0 siblings, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-13 14:23 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: Eli Zaretskii, luangruo, emacs-devel

Gregory Heytings <gregory@heytings.org> writes:

> It's not completely clear (to me) what you mean by "locking".  But I 
> assume you mean two operations lock/unlock with which a thread can request 
> and waive exclusive access to an object, and sleeps until that exclusive 
> access is granted.  IOW, mutexes.

Yes. Although mutexes are not the only way to achieve this.

> If my guess is correct, that is not 
> possible.  You cannot use mutexes in a program whose data structures have 
> not been organized in a way that makes the use of such synchronization 
> primitives possible, without having deadlocks.  Given that in Emacs 
> objects are part of an enormous unstructured graph, with pointers leading 
> from anywhere to anywhere, that's clearly not the case, and all you can 
> use is a single global lock.

I do not think so.
There is usually no need to ensure that a given Elisp object is locked
recursively, with all the linked objects (like all elements in a list).
We just need to ensure that we protect individual Lisp_Object's from
data races.

>> But the very need to access global Elisp variables/objects from async 
>> threads should not be considered a good practice (except near start/end 
>> of thread execution). Most of processing should be done using threads' 
>> local lexical scope.
>
> ... if what you have in mind are async threads that should in fact not 
> access objects of the main thread, why is it necessary to lock
> objects?
> You can prepare the arguments to the async thread in the main thread, 
> start the async thread, and when its execution completes process the 
> return values of the async thread in the main thread, which is what 
> emacs-async already does.

> May I ask you if you have a concrete example of a task that you would like 
> to perform with such threads, and that cannot already be done with 
> emacs-async?  In other words, what are the limitations of emacs-async you 
> try to overcome?

You cannot pass non-printable objects like (like markers of buffer). You
cannot pass Emacs local state, like user customization, except a small
subset. You have to go through print/read loop to pass the objects
around. You have to deal with startup overheads and large memory
overheads associated with creating a new Emacs process.

Examples:

1. Background parsing of large buffers, where you cannot afford to pass
   the whole buffer and parser state back-and-forth every time the user
   makes changes.
   
2. Searching across multiple buffers, while keeping all the buffer-local
   customizations (including runtime, this-session-only, customizations)
   honored.

3. Stealth fontification of buffers. Not just on idle, but
   asynchronously.

4. Doing something as basic as loop parallelism on multiple CPUs for
   CPU-heavy processing. (passing the data between Emacs process would
   defer the whole purpose here - that's inter-process communication
   overheads + sexp parsing !!!)

5. The very async process communication where process sentinels are not
   trivial and have to utilize significant CPU processing.

6. Basically, anything more complex than what can be done using a bunch
   of foo& + wait() in bash.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 13:58                                                                               ` Ihor Radchenko
  2023-07-09 14:52                                                                                 ` Eli Zaretskii
@ 2023-07-16 14:58                                                                                 ` Ihor Radchenko
  2023-07-17  7:55                                                                                   ` Ihor Radchenko
  2023-07-24  8:42                                                                                 ` Ihor Radchenko
  2 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-16 14:58 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> 4. Buffer-local variables, defined in C have C variable equivalents that
>    are updated as Emacs changes current_buffer.
>    
>    AFAIU, their purpose is to make buffer-local variables and normal
>    Elisp variables uniformly accessible from C code - C code does not
>    need to worry about Vfoo being buffer-local or not, and just set it.
>
>    This is not compatible with async threads that work with several buffers.
>
>    I currently do not fully understand how defining C variables works in
>    DEFVAR_LISP.

Currently, Emacs has a huge `globals' struct, where each record is a
variable defined in Lisp.

On C side, we have macros like
#define Vdebug_on_error globals.f_Vdebug_on_error
So, Vdebug_on_error can be used pretending that it is an ordinary
variable.
The location of stored Elisp value is thus fixed in memory.

On Elisp side, the value cells of symbols point to special internal
type, called "object forwarder". It arranges the actual symbol value
to be a pointer to where the value is actually stored.

For global variables the above scheme is easy.
Tricky things start to happen when we make variable that has C
reference buffer-local - the Vfoo C variable will always point to
the same address in ~globals~, but that address can only represent a
single value.

So, Emacs has to keep updating ~globals~ every time we switch buffer:

1. An old value stored in ~globals~ should be recorded in
to-be-switched-away buffer object.
2. ~globals~ is reassigned to load the value from new buffer.

(the situation is a bit more complex, because updating is done
lazily, only when the new buffer actually has a separate
buffer-local binding for a given variable; but the basic scheme is
as described).

The same update is happening when Emacs enters/exits let-binding or
switches between current cooperative threads.

---

The easiest way to not break the current Emacs logic would be making
~globals~ thread-local. Then, the current code may remain intact, if
we are content with global variables synchronized (or not) between
threads manually (say, when a thread exits or when it calls something
like `thread-synchronize-globals').

~globals~ currently has 457 variables, which is \approx{} =sizeof (int) * 457=
memory per thread.

If memory is an issue, we may want to store only a list of actually
changed variables in thread and change the current approach with
Vfoo macro and have something like VSETfoo + VGETfoo. VSETfoo will
always assign thread-local binding, adding it as necessary. VGETfoo
will first check thread-local binding and reach out to global struct
as fallback.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-16 14:58                                                                                 ` Ihor Radchenko
@ 2023-07-17  7:55                                                                                   ` Ihor Radchenko
  2023-07-17  8:36                                                                                     ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-17  7:55 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> The same update is happening when Emacs enters/exits let-binding or
> switches between current cooperative threads.

Which is actually quite a big issue.

When Emacs descents into let binding and dynamic scope is active, it
stores the existing variable value into a stack and sets the new value
globally, into symbol's value slot.

When ascending out of let, the existing value is discarded and the old
value is set globally, recovered from the stack.

And the cooperative threads simply manipulate the stack to store the
thread-local values before thread yields and load the values from the
next active stack. (though I did not look too close here)

--

The conclusion so far: it will be difficult to support dynamic scope
if we want things to run in parallel.

We would need to implement some way to store symbol values in every
thread (not necessarily all values, but at least the symbols changed by
thread function).

I can see several ways to approach this:

1. We can maintain a separate, sparse (just for changed variables)
   obarray inside threads, so that `intern'
   will point to thread-local symbol objects.

2. Force lexical scope inside threads, so that the values do not have to
   be stored in object value slots, but within per-thread lexical scope
   structure.
   This however, will not work when using things like `cl-letf' where
   Elisp code may want to temporarily set symbol function slots.

3. Store multiple value and function slots in symbol objects.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-17  7:55                                                                                   ` Ihor Radchenko
@ 2023-07-17  8:36                                                                                     ` Po Lu
  2023-07-17  8:52                                                                                       ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-17  8:36 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I can see several ways to approach this:
>
> 1. We can maintain a separate, sparse (just for changed variables)
>    obarray inside threads, so that `intern'
>    will point to thread-local symbol objects.

Interning happens at read (and thus load) time.  Byte-code vectors and
lambdas given to make-thread contain symbols, and not their names.  A
thread-local obarray will not be helpful.

> 2. Force lexical scope inside threads, so that the values do not have to
>    be stored in object value slots, but within per-thread lexical scope
>    structure.
>    This however, will not work when using things like `cl-letf' where
>    Elisp code may want to temporarily set symbol function slots.

This is not realistic.

> 3. Store multiple value and function slots in symbol objects.

Why would this be difficult?  It would slow down accesses to value and
function slots (especially those which aren't bound dynamically), but
that's an unavoidable drawback of multiprocessing.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-17  8:36                                                                                     ` Po Lu
@ 2023-07-17  8:52                                                                                       ` Ihor Radchenko
  2023-07-17  9:39                                                                                         ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-17  8:52 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> 1. We can maintain a separate, sparse (just for changed variables)
>>    obarray inside threads, so that `intern'
>>    will point to thread-local symbol objects.
>
> Interning happens at read (and thus load) time.  Byte-code vectors and
> lambdas given to make-thread contain symbols, and not their names.  A
> thread-local obarray will not be helpful.

Fair point. Alas.

>> 3. Store multiple value and function slots in symbol objects.
>
> Why would this be difficult?  It would slow down accesses to value and
> function slots (especially those which aren't bound dynamically), but
> that's an unavoidable drawback of multiprocessing.

Mostly because it will involve more changes than I was hoping for. And I
am not sure how things will affect memory alignment of symbol objects (I
do not fully understand the relevant comments in lisp.h) - we should be
careful about concurrent read access to struct slots in shared objects.
If there is some memory re-alignment happening concurrently, we may
either have to use READ_ONCE (and degrade performance) or be extra
careful to ensure that memory does not get shifted around, and we do not
end up trying to read wrong memory address because some other thread
wrote staff into the same symbol during the read.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-17  8:52                                                                                       ` Ihor Radchenko
@ 2023-07-17  9:39                                                                                         ` Po Lu
  2023-07-17  9:54                                                                                           ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-17  9:39 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Mostly because it will involve more changes than I was hoping for. 

Fundamental changes to the Emacs Lisp runtime will unavoidably involve
an immense number of changes, of course.

> And I am not sure how things will affect memory alignment of symbol
> objects (I do not fully understand the relevant comments in lisp.h)

How is any of this relevant to symbol alignment?  Please tell us which
comments you're referring to.

> we should be careful about concurrent read access to struct slots in
> shared objects.

Fortunately, there are very few direct references to fields within
Lisp_Symbol.

> If there is some memory re-alignment happening concurrently, we may
> either have to use READ_ONCE (and degrade performance) or be extra
> careful to ensure that memory does not get shifted around, and we do
> not end up trying to read wrong memory address because some other
> thread wrote staff into the same symbol during the read.

Memory re-alignment?



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-17  9:39                                                                                         ` Po Lu
@ 2023-07-17  9:54                                                                                           ` Ihor Radchenko
  2023-07-17 10:08                                                                                             ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-17  9:54 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> And I am not sure how things will affect memory alignment of symbol
>> objects (I do not fully understand the relevant comments in lisp.h)
>
> How is any of this relevant to symbol alignment?  Please tell us which
> comments you're referring to.

GCALIGNED_UNION_MEMBER

>> we should be careful about concurrent read access to struct slots in
>> shared objects.
>
> Fortunately, there are very few direct references to fields within
> Lisp_Symbol.

What I mean is a situation when we try to read sym->u.s.val.value, but
the value becomes Lisp_Object value[].

Then, realloc calls in other thread may create a race condition when
accessing array element may point to obsolete memory address that was
only valid prior to realloc.

Of course, it is just a trivial example. I am worried about less obvious
scenarios (those, I can't predict in advance).

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-17  9:54                                                                                           ` Ihor Radchenko
@ 2023-07-17 10:08                                                                                             ` Po Lu
  0 siblings, 0 replies; 192+ messages in thread
From: Po Lu @ 2023-07-17 10:08 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> GCALIGNED_UNION_MEMBER

Its purpose is to ensure that Lisp structures are aligned sufficiently
for mark_memory to locate its members when they are placed on the stack,
through the optional AUTO_CONS or AUTO_STRING mechanism.

> What I mean is a situation when we try to read sym->u.s.val.value, but
> the value becomes Lisp_Object value[].
>
> Then, realloc calls in other thread may create a race condition when
> accessing array element may point to obsolete memory address that was
> only valid prior to realloc.

My implementation used a lock around a linked list of value and function
cells, only taken if the thread sees that the list is not empty.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-05  2:31   ` Eli Zaretskii
@ 2023-07-17 20:43     ` Hugo Thunnissen
  2023-07-18  4:51       ` tomas
  2023-07-18  5:25       ` Ihor Radchenko
  0 siblings, 2 replies; 192+ messages in thread
From: Hugo Thunnissen @ 2023-07-17 20:43 UTC (permalink / raw)
  To: Eli Zaretskii, emacs-devel, yantar92


On 7/5/23 04:31, Eli Zaretskii wrote:
> No, because we already handle sub-process output in a way that doesn't
> require true concurrency.
>
Not to hijack this thread and this is only tangentially related to the 
the message I'm responding to, but asynchronous IO like Emacs has for 
sub processes, network connections and the like are, in my opinion, more 
important features than true concurrency. Asynchronous IO is also what 
can make current lisp threads useful. I think that most of the time when 
people want to reach for concurrency in an Emacs lisp program it is to 
retrieve, process, store or emit data in the background. Correct me if 
you think I'm wrong, but I suspect that execution time, parallelism or 
performance isn't usually much of an issue: I think what people usually 
want is a way of processing things or synchronizing things without 
having the user wait on an unresponsive Emacs. Combining lisp threads, 
asynchronous IO (not async execution) facilities and a little bit of 
polling of `input-pending-p' can in my opinion already get this job done 
most of the time.

Ihor Radchenko wrote:

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

I am skeptical that there are all that many use cases for the 
asynchronous analysis of text in buffers that are currently being 
edited. Correct me if I'm wrong, AFAIU programs that analyze text as it 
is being edited will usually need to re-parse after every edit anyways, 
making a parse during the edit itself not all that useful: After all, 
the result is no longer accurate for the buffer contents by the time it 
becomes available. A synchronous parser can probably do just as good of 
a job in such a scenario, as long as it is interruptible by user input.

I'm not familiar with the C core at all, do you think it might be more 
realistic to add a little more facilities for asynchronous data streams 
than to rework the execution model of Emacs to add multi threading? I'm 
mainly thinking of something like "channels" or "queues" for lisp 
objects, with an easy to use scheduling mechanism that makes it 
straightforward for people to not stall the main thread for too long.

And another (very) nice to have feature: asynchronous filesystem IO. 
Consider the code below which I use in a package I'm working on. My 
package reads and parses a large amount of files in the background 
within a short time frame. Parsing/processing the files in a timely 
manner is never really an issue, but the blocking IO of 
`insert-file-contents' does often take so long that it is  impossible to 
not have the user notice, even if polling `input-pending-p' and yielding 
in between operations. The workaround below makes the thread virtually 
unnoticeable in the majority of cases, but it would have been nice to 
have a native elisp facility for this as opposed to using `cat` in an 
asynchronous process.

----

(defconst phpinspect--cat-executable (executable-find "cat")
   "The executable used to read files asynchronously from the filesystem.")

(defsubst phpinspect--insert-file-contents-asynchronously (file)
   "Inserts FILE contents into the current buffer asynchronously, while 
yielding the current thread.

Errors when executed in main thread, as it should be used to make
background operations less invasive. Usage in the main thread can
only be the result of a logic error."
   (let* ((thread (current-thread))
          (mx (make-mutex))
          (condition (make-condition-variable mx))
          (err)
          (sentinel
           (lambda (process event)
             (with-mutex mx
               (if (string-match-p 
"^\\(deleted\\|exited\\|failed\\|connection\\)" event)
                   (progn
                     (setq err (format "cat process %s failed with 
event: %s" process event))
                     (condition-notify condition))
                 (when (string-match-p "^finished" event)
                   (condition-notify condition)))))))
     (when (not phpinspect--cat-executable)
       (error
        "ERROR: phpinspect--insert-file-contents-asynchronously called 
when cat-executable is not set"))

     (when (eq thread main-thread)
       (error "ERROR: phpinspect--insert-file-contents-asynchronously 
called from main-thread"))

     (with-mutex mx
       (make-process :name "phpinspect--insert-file-contents-asynchronously"
                     :command `(,phpinspect--cat-executable ,file)
                     :buffer (current-buffer)
                     :sentinel sentinel)

       (condition-wait condition)
       (when err (error err)))))

(cl-defmethod phpinspect-fs-insert-file-contents ((fs phpinspect-fs) 
file &optional prefer-async)
   "Insert file contents from FILE. "
   (if (and prefer-async (not (eq (current-thread) main-thread))
            phpinspect--cat-executable)
       (phpinspect--insert-file-contents-asynchronously file)
     (insert-file-contents-literally file)))




^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-17 20:43     ` Hugo Thunnissen
@ 2023-07-18  4:51       ` tomas
  2023-07-18  5:25       ` Ihor Radchenko
  1 sibling, 0 replies; 192+ messages in thread
From: tomas @ 2023-07-18  4:51 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 2581 bytes --]

On Mon, Jul 17, 2023 at 10:43:26PM +0200, Hugo Thunnissen wrote:
> 
> On 7/5/23 04:31, Eli Zaretskii wrote:
> > No, because we already handle sub-process output in a way that doesn't
> > require true concurrency.
> > 
> Not to hijack this thread and this is only tangentially related to the the
> message I'm responding to, but asynchronous IO like Emacs has for sub
> processes, network connections and the like are, in my opinion, more
> important features than true concurrency. Asynchronous IO is also what can
> make current lisp threads useful. I think that most of the time when people
> want to reach for concurrency in an Emacs lisp program it is to retrieve,
> process, store or emit data in the background [...]

I couldn't agree more. I'd add: true parallelism is a whole order of
magnitude more complex than just avoiding blocking operations. You
want the former when you need to harness more than one CPU in your
process. As you do, I don't think we need that in Emacs just yet.

But I think that there's a lot of the latter on the table for Emacs
until we reach a point of diminishing returns (and, btw, tackling the
easier part prepares the code for the more difficult one).

I've been watching the Java story from the sidelines for a long time
time (there was a huge push towards true parallelism, because Sun
back then was envisioning massively parallel processors). They tried
with "classical" interlocking. It was a world of pain. Their attempt
to develop a parallel GUI toolkit (it's tempting: each widget lives
a life of its own and iteracts with each other and the user is a
compelling idea) failed miserably and they had to showe the whole
thing into one thread after all (Po Lu is right).

There are other approaches which feel more manageable (communicating
processes over channels, à la Erlang, Concurrent ML). There is a
very nice series of blog posts by Andy Wingo, Guile's maintainer, on
that topic (ex. [1], [2]). But Emacs's architecture is just too far
away from that for it to be a viable option, methinks.

The other extreme is to design lockless algorithms from the ground
up (cf. the ref to Paul McKenney I posted elsewhere) as it is
being done in the Linux kernel.

Still I think those approaches aren't (yet?) for Emacs. Of course,
it makes a lot of sense to "draw a future picture" to have an idea
of what direction one wants to take...

Cheers

[1] https://wingolog.org/archives/2016/09/20/concurrent-ml-versus-go
[2] https://wingolog.org/archives/2017/06/29/a-new-concurrent-ml

-- 
t

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-17 20:43     ` Hugo Thunnissen
  2023-07-18  4:51       ` tomas
@ 2023-07-18  5:25       ` Ihor Radchenko
  2023-07-18  5:39         ` Po Lu
  2023-07-18 12:14         ` Hugo Thunnissen
  1 sibling, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-18  5:25 UTC (permalink / raw)
  To: Hugo Thunnissen; +Cc: Eli Zaretskii, emacs-devel

Hugo Thunnissen <devel@hugot.nl> writes:

>> 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.
>
> I am skeptical that there are all that many use cases for the 
> asynchronous analysis of text in buffers that are currently being 
> edited.

Org does it via idle timers.
Also, consider searching across many buffers - that's a legitimate use
case. Think about AG/RG and other multi-threaded text searchers.
If the buffers are also structured and need to be parsed, we arrive at
the situation when parallelism can be quite useful.

> ... Correct me if I'm wrong, AFAIU programs that analyze text as it 
> is being edited will usually need to re-parse after every edit anyways, 
> making a parse during the edit itself not all that useful: After all, 
> the result is no longer accurate for the buffer contents by the time it 
> becomes available. A synchronous parser can probably do just as good of 
> a job in such a scenario, as long as it is interruptible by user input.

No. It still makes sense to re-parse incrementally before the point
where the edits are being made. For example, Org parser has to trash
everything that may theoretically be affected by current edits and
re-parse later, synchronously, on demand. This trashing sometimes
involve rather big chunks of text, and it would significantly speed
things up if we could do part of the incremental parsing in parallel.

> I'm not familiar with the C core at all, do you think it might be more 
> realistic to add a little more facilities for asynchronous data streams 
> than to rework the execution model of Emacs to add multi threading? I'm 
> mainly thinking of something like "channels" or "queues" for lisp 
> objects, with an easy to use scheduling mechanism that makes it 
> straightforward for people to not stall the main thread for too long.

AFAIK, sentinels do this already. Of course, they are quite basic.
As for queues, see https://emacsconf.org/2022/talks/async/

> And another (very) nice to have feature: asynchronous filesystem IO. 
> Consider the code below which I use in a package I'm working on. My 
> package reads and parses a large amount of files in the background 
> within a short time frame. Parsing/processing the files in a timely 
> manner is never really an issue, but the blocking IO of 
> `insert-file-contents' does often take so long that it is  impossible to 
> not have the user notice, even if polling `input-pending-p' and yielding 
> in between operations.

Well. I have an opposite problem when `read' takes second to read few Mb
of Elisp data, while inserting it into a temporary buffer is instant.

Also, did you actually profile your use case? I actually doubt that
reading file takes a long time on modern systems. In my previous tests,
I tried to open multiple tens of thousands files using

(let ((buffer (get-buffer-create " *Processing*")))
  (with-current-buffer buffer
    (let (buffer-undo-list t)
      (dolist (f files)
	(insert-file-contents f nil nil nil 'replace)
        (do-staff)))))

And it was very, very fast.
What was not fast (orders of magnitude) is opening files directly that
triggers all kinds of user hooks.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-18  5:25       ` Ihor Radchenko
@ 2023-07-18  5:39         ` Po Lu
  2023-07-18  5:49           ` Ihor Radchenko
  2023-07-18 12:14         ` Hugo Thunnissen
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-18  5:39 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Hugo Thunnissen, Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Also, did you actually profile your use case? I actually doubt that
> reading file takes a long time on modern systems. In my previous tests,
> I tried to open multiple tens of thousands files using
>
> (let ((buffer (get-buffer-create " *Processing*")))
>   (with-current-buffer buffer
>     (let (buffer-undo-list t)
>       (dolist (f files)
> 	(insert-file-contents f nil nil nil 'replace)
>         (do-staff)))))
>
> And it was very, very fast.

Now do this over NFS, and see how much slower it becomes.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-18  5:39         ` Po Lu
@ 2023-07-18  5:49           ` Ihor Radchenko
  0 siblings, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-18  5:49 UTC (permalink / raw)
  To: Po Lu; +Cc: Hugo Thunnissen, Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Now do this over NFS, and see how much slower it becomes.

Fair point.
That said, I myself is not very interested in async IO. Others may take
this work, if they wish to.

Though I still have a feeling that Hugo had different reasons for
slowdown - on Elisp side. We might probably identify them.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-18  5:25       ` Ihor Radchenko
  2023-07-18  5:39         ` Po Lu
@ 2023-07-18 12:14         ` Hugo Thunnissen
  2023-07-18 12:39           ` Async IO and queing process sentinels (was: Concurrency via isolated process/thread) Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Hugo Thunnissen @ 2023-07-18 12:14 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-devel


On 7/18/23 07:25, Ihor Radchenko wrote:
> Hugo Thunnissen <devel@hugot.nl> writes:
>
>>> 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.
>> I am skeptical that there are all that many use cases for the
>> asynchronous analysis of text in buffers that are currently being
>> edited.
> Org does it via idle timers.
> Also, consider searching across many buffers - that's a legitimate use
> case. Think about AG/RG and other multi-threaded text searchers.
> If the buffers are also structured and need to be parsed, we arrive at
> the situation when parallelism can be quite useful.

True, for that scenario parallelism could be useful.

>
>> ... Correct me if I'm wrong, AFAIU programs that analyze text as it
>> is being edited will usually need to re-parse after every edit anyways,
>> making a parse during the edit itself not all that useful: After all,
>> the result is no longer accurate for the buffer contents by the time it
>> becomes available. A synchronous parser can probably do just as good of
>> a job in such a scenario, as long as it is interruptible by user input.
> No. It still makes sense to re-parse incrementally before the point
> where the edits are being made. For example, Org parser has to trash
> everything that may theoretically be affected by current edits and
> re-parse later, synchronously, on demand. This trashing sometimes
> involve rather big chunks of text, and it would significantly speed
> things up if we could do part of the incremental parsing in parallel.

I see. I am not familiar with org-mode's code so I'll take your word for it.

 From my own limited experience, I think that most of the time the user 
isn't looking for "instant" feedback, but just wants feedback to appear 
shortly after they stop typing. I would expect a parser, even if not 
incremental, to be able to parse most buffers within 10 to maybe 200 
milliseconds. I think this is an acceptable wait time for feedback 
granted that the parse is interruptible by user input, so that it can 
never make the user wait for an unresponsive Emacs. Having less wait 
time through the use of multi threading might be slightly nicer, but I 
don't see it making that much of a difference for the user experience in 
most modes. Would you agree with that as a general statement?

>> I'm not familiar with the C core at all, do you think it might be more
>> realistic to add a little more facilities for asynchronous data streams
>> than to rework the execution model of Emacs to add multi threading? I'm
>> mainly thinking of something like "channels" or "queues" for lisp
>> objects, with an easy to use scheduling mechanism that makes it
>> straightforward for people to not stall the main thread for too long.
> AFAIK, sentinels do this already. Of course, they are quite basic.
> As for queues, see https://emacsconf.org/2022/talks/async/

Sentinels are for external processes. I was thinking more in the vain of 
a queue that is owned by two lisp threads, where one thread can write 
and prepare input for the other, and the other thread yields as long as 
there is no new input to process. When a thread receives an input 
message from the other, it will come alive at the next idling moment and 
process the queue while yielding in between messages. This would make it 
easier to process data in chunks without halting the main thread long 
enough to bother the user, as long as the main thread is prioritized 
over others.


>> And another (very) nice to have feature: asynchronous filesystem IO.
>> Consider the code below which I use in a package I'm working on. My
>> package reads and parses a large amount of files in the background
>> within a short time frame. Parsing/processing the files in a timely
>> manner is never really an issue, but the blocking IO of
>> `insert-file-contents' does often take so long that it is  impossible to
>> not have the user notice, even if polling `input-pending-p' and yielding
>> in between operations.
> Well. I have an opposite problem when `read' takes second to read few Mb
> of Elisp data, while inserting it into a temporary buffer is instant.
>
> Also, did you actually profile your use case?

I am ashamed to admit that I did not, I did not know about the profiler 
at the time so I just did guesswork until I seemed to be getting 
results. There seemed to be a noticeable difference at the time, but at 
the moment I'm having trouble reproducing it, so needless to say I did 
not find a large difference re-running both solutions with the profiler 
enabled. I did make some other large changes in the codebase since I 
made this change, so it could very well be that the thing that made 
Emacs hang at the time was not filesystem IO at all. Or there was 
something else going on with the specific files that were being parsed 
(I don't remember which folder I did my tests on). Sorry for being the 
typical guy who makes claims about performance without doing his due 
diligence ;)

I still suspect that async filesystem IO could make a considerable 
difference in some scenarios. My package is for PHP projects and it is 
not unheard of for PHP files to be edited over network mounts where IO 
could have a lot more latency. For example, some people have asked me 
whether my package works over tramp. I haven't profiled this scenario 
yet as I have to make some tweaks first to make it work at all, but I 
fear that blocking on `insert-file-contents' may not be ideal in that 
scenario.

And then there also is `directory-files-recursively':

          457  77%         - phpinspect-index-current-project
          457  77%          - let*
          371  63%           - if
          371  63%            - progn
          371  63%             - let
          371  63%              - while
          371  63%               - let
          371  63%                - if
          371  63%                 - progn
          371  63%                  - let
          371  63%                   - while
          371  63%                    - let
          371  63%                     - if
          361  61%                      - progn
          361  61%                       - let*
          312  53%                        - if
          312  53%                         - progn
          308  52%                          - maphash
          308  52%                           - #<lambda -0x2edd06728f18cc0>
          308  52%                            - let
          305  51%                             - if
          305  51%                              - progn
          305  51%                               - 
phpinspect-al-strategy-fill-typehash
          305  51%                                - apply
          292  49%                                 - #<lambda 
0x725f4ad90660>
          292  49%                                  - progn
          292  49%                                   - let
          292  49%                                    - let
          292  49%                                     - while
          292  49%                                      - let
          292  49%                                       - let
          172  29%                                        - while
          172  29%                                         + let
          120  20%                                        - 
phpinspect-fs-directory-files-recursively
          120  20%                                         - apply
          120  20%                                          - #<lambda 
0xf3386f2a7932164>
          120  20%                                           - progn
          120  20%                                           - progn
          120  20%                                            - 
directory-files-recursively
           79  13%                                             - 
directory-files-recursively
           21   3%                                              - 
directory-files-recursively
           11 1% directory-files-recursively
            4   0% sort
            3   0% file-remote-p






^ permalink raw reply	[flat|nested] 192+ messages in thread

* Async IO and queing process sentinels (was: Concurrency via isolated process/thread)
  2023-07-18 12:14         ` Hugo Thunnissen
@ 2023-07-18 12:39           ` Ihor Radchenko
  2023-07-18 12:49             ` Ihor Radchenko
  2023-07-18 14:12             ` Async IO and queing process sentinels Michael Albinus
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-18 12:39 UTC (permalink / raw)
  To: Hugo Thunnissen; +Cc: emacs-devel, Michael Albinus

Hugo Thunnissen <devel@hugot.nl> writes:

>>> I'm not familiar with the C core at all, do you think it might be more
>>> realistic to add a little more facilities for asynchronous data streams
>>> than to rework the execution model of Emacs to add multi threading? I'm
>>> mainly thinking of something like "channels" or "queues" for lisp
>>> objects, with an easy to use scheduling mechanism that makes it
>>> straightforward for people to not stall the main thread for too long.
>> AFAIK, sentinels do this already. Of course, they are quite basic.
>> As for queues, see https://emacsconf.org/2022/talks/async/
>
> Sentinels are for external processes. I was thinking more in the vain of 
> a queue that is owned by two lisp threads, where one thread can write 
> and prepare input for the other, and the other thread yields as long as 
> there is no new input to process. When a thread receives an input 
> message from the other, it will come alive at the next idling moment and 
> process the queue while yielding in between messages. This would make it 
> easier to process data in chunks without halting the main thread long 
> enough to bother the user, as long as the main thread is prioritized 
> over others.

What you describe sounds very similar to `condition-wait'/`condition-notify'.

That said, I have seen scenarios when I have several dozens of network
connections open, receive data from server, and fire all their sentinels
near the same time. This effectively blocks Emacs because all the
sentinels are suddenly scheduled to run together.
It would be nice to have some better balancing in such case.

> I still suspect that async filesystem IO could make a considerable 
> difference in some scenarios. My package is for PHP projects and it is 
> not unheard of for PHP files to be edited over network mounts where IO 
> could have a lot more latency. For example, some people have asked me 
> whether my package works over tramp. I haven't profiled this scenario 
> yet as I have to make some tweaks first to make it work at all, but I 
> fear that blocking on `insert-file-contents' may not be ideal in that 
> scenario.

AFAIR, Michael Albinus (the maintainer of TRAMP) is working on better
support for async processes in TRAMP. And he had troubles with managing
async process queues as well.

CCing him, as this discussion might be relevant.

> And then there also is `directory-files-recursively':
>
>           457  77% - phpinspect-index-current-project
>           120  20%    -directory-files-recursively
>            79  13%     -directory-files-recursively
>            21   3%        -directory-files-recursively
>            11 1%            directory-files-recursively

Note how functions involving actual IO do not show up in the backtrace:
`file-name-all-completions' and `file-symlink-p' are not there.

Most of the 120ms spend inside `directory-files-recursively' is Elisp
recursive calls.

I also did testing locally, on my $HOME dir, and got similar results
with recursive calls taking most of the CPU time:

         463  29%            - directory-files-recursively
         434  27%             - directory-files-recursively
         305  19%              - directory-files-recursively
         111   7%               - directory-files-recursively
          85   5%                - directory-files-recursively
          62   3%                 - directory-files-recursively
          24   1%                  - directory-files-recursively
          10   0%                   - directory-files-recursively
          10   0%                    - directory-files-recursively
           7   0%                     - directory-files-recursively
           7   0%                        directory-files-recursively
           3   0%                    sort
           3   0%                   sort
           3   0%                sort

If you want to improve performance here, you likely need to rewrite
`directory-files-recursively' without recursion. Doable, and nothing to
do with IO slowness. 

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Async IO and queing process sentinels (was: Concurrency via isolated process/thread)
  2023-07-18 12:39           ` Async IO and queing process sentinels (was: Concurrency via isolated process/thread) Ihor Radchenko
@ 2023-07-18 12:49             ` Ihor Radchenko
  2023-07-18 14:12             ` Async IO and queing process sentinels Michael Albinus
  1 sibling, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-18 12:49 UTC (permalink / raw)
  To: Hugo Thunnissen; +Cc: emacs-devel, Michael Albinus

Ihor Radchenko <yantar92@posteo.net> writes:

> I also did testing locally, on my $HOME dir, and got similar results
> with recursive calls taking most of the CPU time:
>
>          463  29%            - directory-files-recursively
>          434  27%             - directory-files-recursively
>          305  19%              - directory-files-recursively
> ...
>
> If you want to improve performance here, you likely need to rewrite
> `directory-files-recursively' without recursion. Doable, and nothing to
> do with IO slowness. 

Not recursion, actually. I did a bit more elaborate profiling with perf
and it looks like most of the time is spent matching regexp against file
names:

The actual tested command was (ignore (directory-files-recursively "/home/yantar92/.data" ".+"))

    33.82%  emacs         emacs                        [.] re_match_2_internal
    14.87%  emacs         emacs                        [.] process_mark_stack
     8.47%  emacs         emacs                        [.] Fnconc
     6.07%  emacs         emacs                        [.] re_search_2
     2.36%  emacs         emacs                        [.] unbind_to
     2.08%  emacs         emacs                        [.] sweep_strings
     1.98%  emacs         emacs                        [.] compile_pattern
     1.64%  emacs         emacs                        [.] execute_charset
     1.64%  emacs         emacs                        [.] assq_no_quit
     1.40%  emacs         emacs                        [.] sweep_conses
     1.01%  emacs         emacs                        [.] plist_get
     0.97%  emacs         emacs                        [.] set_buffer_internal_2
     0.86%  emacs         emacs                        [.] RE_SETUP_SYNTAX_TABLE_FOR_OBJECT
     0.84%  emacs         emacs                        [.] mark_interval_tree_1
     0.75%  emacs         emacs                        [.] internal_equal
     0.73%  emacs         emacs                        [.] Ffind_file_name_handler

There was quite a bit of GC (not included into Elisp profile), that
shows up as *mark* and *sweep* calls.

No IO at all shows up in the backtrace. Even Ffind_file_name_handler is
simply matching filename against `file-name-handler-alist', calling
`insert-directory-program' somewhere in the process (the call does not
show up anywhere high in the profile).

Most of the time is spent doing regexp matching in various places and
building the actual (long) list of files.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Async IO and queing process sentinels
  2023-07-18 12:39           ` Async IO and queing process sentinels (was: Concurrency via isolated process/thread) Ihor Radchenko
  2023-07-18 12:49             ` Ihor Radchenko
@ 2023-07-18 14:12             ` Michael Albinus
  1 sibling, 0 replies; 192+ messages in thread
From: Michael Albinus @ 2023-07-18 14:12 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Hugo Thunnissen, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> AFAIR, Michael Albinus (the maintainer of TRAMP) is working on better
> support for async processes in TRAMP. And he had troubles with managing
> async process queues as well.

Some years ago, I've tried to add threads to Tramp in order to unblock
it for remote file access. It worked somehow, but I couldn't fix the
problem to react on user input in a thread (for example, asking for
passwords and alike). So I have stopped this.

There were still problems that several operations could interfer each
other, when a Tramp file operation is interrupted by another Tramp file
operation due to timers, process sentinels, process filters and alike. I
have defended this in Tramp by suspending timers while accepting process
output. Sentinels and filters are less disturbing.

So I cannot offer something useful for the general case.

Best regards, Michael.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-09 13:58                                                                               ` Ihor Radchenko
  2023-07-09 14:52                                                                                 ` Eli Zaretskii
  2023-07-16 14:58                                                                                 ` Ihor Radchenko
@ 2023-07-24  8:42                                                                                 ` Ihor Radchenko
  2023-07-24  9:52                                                                                   ` Po Lu
  2023-07-24 12:44                                                                                   ` Eli Zaretskii
  2 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24  8:42 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> 3. Current buffer, point position, and narrowing.
>
>    By current design, Emacs always have a single global current buffer,
>    current point position, and narrowing state in that buffer.
>    Even when we switch cooperative threads, a thread must update its
>    thread->current_buffer to previous_thread->current_buffer; and update
>    point and narrowing by calling set_buffer_internal_2.
>
>    Current design is incompatible with async threads - they must be able
>    to have different buffers, points, and narrowing states current
>    within each thread.
>
>    That's why I suggested to convert PT, BEGV, and ZV into
>    thread-locals.

Would it be acceptable to convert buffer PT, BEGV, and ZV into
thread-local for current cooperative threads?

I am thinking about:

1. Removing pt, pt_byte, begv, begv_byte, zv, zv_byte, pt_marker_,
   begv_marker_, and zv_marker_ from buffer objects.
2. Adding pt/begv/zv to thread object.
3. Adding an alist linking buffers and past
   pt/begv/zv positions visited by a given thread.

This way, when a thread yields and later continues executing, its point
and restriction will not be changed.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24  8:42                                                                                 ` Ihor Radchenko
@ 2023-07-24  9:52                                                                                   ` Po Lu
  2023-07-24 10:09                                                                                     ` Ihor Radchenko
  2023-07-24 12:44                                                                                   ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-24  9:52 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> 3. Current buffer, point position, and narrowing.
>>
>>    By current design, Emacs always have a single global current buffer,
>>    current point position, and narrowing state in that buffer.
>>    Even when we switch cooperative threads, a thread must update its
>>    thread->current_buffer to previous_thread->current_buffer; and update
>>    point and narrowing by calling set_buffer_internal_2.
>>
>>    Current design is incompatible with async threads - they must be able
>>    to have different buffers, points, and narrowing states current
>>    within each thread.
>>
>>    That's why I suggested to convert PT, BEGV, and ZV into
>>    thread-locals.
>
> Would it be acceptable to convert buffer PT, BEGV, and ZV into
> thread-local for current cooperative threads?
>
> I am thinking about:
>
> 1. Removing pt, pt_byte, begv, begv_byte, zv, zv_byte, pt_marker_,
>    begv_marker_, and zv_marker_ from buffer objects.
> 2. Adding pt/begv/zv to thread object.
> 3. Adding an alist linking buffers and past
>    pt/begv/zv positions visited by a given thread.
>
> This way, when a thread yields and later continues executing, its point
> and restriction will not be changed.

It may be possible to implement this with our current yielding system.

But how much do you want to slow down accesses to PT in the future, when
multiple threads will run simultaneously?  Operations that use and
modify PT are essentially the bread and butter of Emacs, and as a result
accesses to PT must be very fast.

In addition to that, we already have separate window and buffer points.
It would confound users even more to add a third kind of object with its
own point to the mix.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24  9:52                                                                                   ` Po Lu
@ 2023-07-24 10:09                                                                                     ` Ihor Radchenko
  2023-07-24 12:15                                                                                       ` Po Lu
  2023-07-24 12:50                                                                                       ` Eli Zaretskii
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24 10:09 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> Would it be acceptable to convert buffer PT, BEGV, and ZV into
>> thread-local for current cooperative threads?
> ...
> But how much do you want to slow down accesses to PT in the future, when
> multiple threads will run simultaneously?  Operations that use and
> modify PT are essentially the bread and butter of Emacs, and as a result
> accesses to PT must be very fast.

I do not plan to slow down access to PT.
The basic idea is to have
#define PT (current_thread->m_pt + 0)
#define PT_BYTE (current_thread->m_pt_byte + 0)

Basically, use thread object slot instead of buffer object slot to store
point and restriction. This will not cause any performance degradation
and allow multiple points in the same buffer.

What may be slightly slower is setting/getting points in other (not
current) buffers.
We will need to store point and restriction history for each thread.
Searching this history will scale with the number of buffers that have
been current previously during thread execution (though we may use hash
table if necessary).

However, accessing and changing point in buffer that is not current is
not very common. AFAIU, it is done in (1) read operation when reading
from stream represented by a buffer; (2) when switching buffers when we
need to transfer current PT to history for current buffer and retrieve
PT from history for the buffer that will become current.

> In addition to that, we already have separate window and buffer
> points. It would confound users even more to add a third kind of
> object with its own point to the mix.

I propose to remove buffer points completely and use thread points
instead.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 10:09                                                                                     ` Ihor Radchenko
@ 2023-07-24 12:15                                                                                       ` Po Lu
  2023-07-24 12:25                                                                                         ` Ihor Radchenko
  2023-07-24 12:50                                                                                       ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-24 12:15 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I do not plan to slow down access to PT.
> The basic idea is to have
> #define PT (current_thread->m_pt + 0)
> #define PT_BYTE (current_thread->m_pt_byte + 0)
>
> Basically, use thread object slot instead of buffer object slot to store
> point and restriction. This will not cause any performance degradation
> and allow multiple points in the same buffer.

No matter where thread local points are stored, each of these points
will need to be a marker, because text editing operations will otherwise
cause PT in other threads to be desynchronized with PT_BYTE within
multibyte buffers.  Both unibyte buffers and multibyte buffers will also
experience complications if a thread deletes text within a buffer,
reducing its size below point within another thread.

Each text editing operations must then loop through and update each of
these markers.

> What may be slightly slower is setting/getting points in other (not
> current) buffers.
> We will need to store point and restriction history for each thread.
> Searching this history will scale with the number of buffers that have
> been current previously during thread execution (though we may use hash
> table if necessary).
>
> However, accessing and changing point in buffer that is not current is
> not very common. AFAIU, it is done in (1) read operation when reading
> from stream represented by a buffer; (2) when switching buffers when we
> need to transfer current PT to history for current buffer and retrieve
> PT from history for the buffer that will become current.

See above.  What you are proposing is a fundamental change to the
performance attributes of some of the most basic operations performed by
Emacs, and is not acceptable.

> I propose to remove buffer points completely and use thread points
> instead.

IMHO, any change that increases the number of objects that store points
will be a mistake.  There is only one buffer, but there can be many
threads.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 12:15                                                                                       ` Po Lu
@ 2023-07-24 12:25                                                                                         ` Ihor Radchenko
  2023-07-24 13:31                                                                                           ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24 12:25 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> No matter where thread local points are stored, each of these points
> will need to be a marker, because text editing operations will otherwise
> cause PT in other threads to be desynchronized with PT_BYTE within
> multibyte buffers.  Both unibyte buffers and multibyte buffers will also
> experience complications if a thread deletes text within a buffer,
> reducing its size below point within another thread.
>
> Each text editing operations must then loop through and update each of
> these markers.

> ...  What you are proposing is a fundamental change to the
> performance attributes of some of the most basic operations performed by
> Emacs, and is not acceptable.

But editing operations already loop over all the buffer markers. If a
just dozen of extra buffer markers (one for each thread, in the worse
case) are unacceptable, we should really do something about marker
performance.

>> I propose to remove buffer points completely and use thread points
>> instead.
>
> IMHO, any change that increases the number of objects that store points
> will be a mistake.  There is only one buffer, but there can be many
> threads.

Last time I wrote a thread code that had to work with buffer text, I had
to store markers manually anyway. Otherwise, point position were always
chaotic.

And threads that will not work with buffer text, will only need to store
a single marker.

So, I do not see how the proposed approach will make things worse
memory-wise.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24  8:42                                                                                 ` Ihor Radchenko
  2023-07-24  9:52                                                                                   ` Po Lu
@ 2023-07-24 12:44                                                                                   ` Eli Zaretskii
  2023-07-24 13:02                                                                                     ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-24 12:44 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Mon, 24 Jul 2023 08:42:52 +0000
> 
> Ihor Radchenko <yantar92@posteo.net> writes:
> 
> > 3. Current buffer, point position, and narrowing.
> >
> >    By current design, Emacs always have a single global current buffer,
> >    current point position, and narrowing state in that buffer.
> >    Even when we switch cooperative threads, a thread must update its
> >    thread->current_buffer to previous_thread->current_buffer; and update
> >    point and narrowing by calling set_buffer_internal_2.
> >
> >    Current design is incompatible with async threads - they must be able
> >    to have different buffers, points, and narrowing states current
> >    within each thread.
> >
> >    That's why I suggested to convert PT, BEGV, and ZV into
> >    thread-locals.
> 
> Would it be acceptable to convert buffer PT, BEGV, and ZV into
> thread-local for current cooperative threads?

General note: it is very hard to have a serious discussion of this
kind of subject when the goals are not clearly announced, and there
are gaps of week or two between messages.  Discussing several separate
aspects of this makes this even harder to follow and respond in a
useful manner.

> I am thinking about:
> 
> 1. Removing pt, pt_byte, begv, begv_byte, zv, zv_byte, pt_marker_,
>    begv_marker_, and zv_marker_ from buffer objects.
> 2. Adding pt/begv/zv to thread object.
> 3. Adding an alist linking buffers and past
>    pt/begv/zv positions visited by a given thread.
> 
> This way, when a thread yields and later continues executing, its point
> and restriction will not be changed.

Why is the last sentence a worthy goal? what do you think will be the
advantage of that?



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 10:09                                                                                     ` Ihor Radchenko
  2023-07-24 12:15                                                                                       ` Po Lu
@ 2023-07-24 12:50                                                                                       ` Eli Zaretskii
  2023-07-24 13:15                                                                                         ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-24 12:50 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
> Date: Mon, 24 Jul 2023 10:09:50 +0000
> 
> The basic idea is to have
> #define PT (current_thread->m_pt + 0)
> #define PT_BYTE (current_thread->m_pt_byte + 0)

Point is the attribute of a buffer.  The current definition of PT,
viz.:

  #define PT (current_buffer->pt + 0)

automagically makes PT refer to the current buffer, so the code only
needs to change current_buffer to have PT set correctly.

By contrast, you propose to have one value of point per thread, which
means a thread that switches buffers will have to manually change all
of these values, one by one.  Why is that a good idea?

And what about C code which copies/moves text between two buffers?
For example, some primitives in coding.c can decode text from one
buffer while writing the decoded text into another.

> Basically, use thread object slot instead of buffer object slot to store
> point and restriction. This will not cause any performance degradation
> and allow multiple points in the same buffer.
> 
> What may be slightly slower is setting/getting points in other (not
> current) buffers.
> We will need to store point and restriction history for each thread.
> Searching this history will scale with the number of buffers that have
> been current previously during thread execution (though we may use hash
> table if necessary).
> 
> However, accessing and changing point in buffer that is not current is
> not very common. AFAIU, it is done in (1) read operation when reading
> from stream represented by a buffer; (2) when switching buffers when we
> need to transfer current PT to history for current buffer and retrieve
> PT from history for the buffer that will become current.

Once again: please state the final goal, and please describe at least
in principle how these measures are steps toward that goal.

> I propose to remove buffer points completely and use thread points
> instead.

I don't think this could fly, because we must be able to copy text
from one buffer to another in a single thread, see above.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 12:44                                                                                   ` Eli Zaretskii
@ 2023-07-24 13:02                                                                                     ` Ihor Radchenko
  2023-07-24 13:54                                                                                       ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24 13:02 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Would it be acceptable to convert buffer PT, BEGV, and ZV into
>> thread-local for current cooperative threads?
>
> General note: it is very hard to have a serious discussion of this
> kind of subject when the goals are not clearly announced, and there
> are gaps of week or two between messages.  Discussing several separate
> aspects of this makes this even harder to follow and respond in a
> useful manner.

I was initially exploring what should be done allow async threads in
Emacs. The discussion established several important global states in
Emacs that must be changed. Then, I explored if those blockers are
possible to solve. Now, I believe that all the blockers can be solved,
if we accept to leave GC and redisplay synchronous.

However, reducing the global state will not be easy and should better be
done in steps. Ideally, steps that can be tested using the existing
synchronous thread paradigm.

The first step, that can also be potentially useful even if I fail to
proceed far enough to get async threads, is removing global point and
restriction state.

>> 1. Removing pt, pt_byte, begv, begv_byte, zv, zv_byte, pt_marker_,
>>    begv_marker_, and zv_marker_ from buffer objects.
>> 2. Adding pt/begv/zv to thread object.
>> 3. Adding an alist linking buffers and past
>>    pt/begv/zv positions visited by a given thread.
>> 
>> This way, when a thread yields and later continues executing, its point
>> and restriction will not be changed.
>
> Why is the last sentence a worthy goal? what do you think will be the
> advantage of that?

Consider a simple regexp search running in a thread:

(while (re-search-forward re nil t)
  (do-useful-staff)
  (thread-yield))

This search will fail if another thread ever moves point or changes
restriction in the same buffer.

The obvious

(while (re-search-forward re nil t)
  (do-useful-staff)
  (save-restriction (save-excursion (thread-yield))))

won't work.

So, one would have to go through awkward

(let ((marker (point-marker)))
 (while (re-search-forward re nil t)
  (do-useful-staff)
  (move-marker marker (point))
  (thread-yield)
  (goto-char marker)))

at least. But it will yet fail if we need to maintain buffer
restriction. So, the code becomes

(let ((marker (point-marker)) (begv (point-min-marker)) (zv (point-max-marker)))
 (while (re-search-forward re nil t)
  (do-useful-staff)
  (move-marker marker (point))
  (thread-yield)
  (widen)
  (narrow-to-region begv zv)
  (goto-char marker)))

And this may still fail because of unreproducible bugs.

Not to mention that we do not always know where the thread will yield.
Any function may be adviced by user to contain some interactive query,
which will also trigger thread yield.


The above example is just the trivial regexp search. When the code
becomes more complex, things gets even more chaotic and threads become
simply unusable for anything that is trying to scan buffer text.

That's why I think that having threads store their own point and
restriction is useful, even without considering that it will reduce the
global state.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 12:50                                                                                       ` Eli Zaretskii
@ 2023-07-24 13:15                                                                                         ` Ihor Radchenko
  2023-07-24 13:41                                                                                           ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24 13:15 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> The basic idea is to have
>> #define PT (current_thread->m_pt + 0)
>> #define PT_BYTE (current_thread->m_pt_byte + 0)
>
> Point is the attribute of a buffer.  The current definition of PT,
> viz.:
>
>   #define PT (current_buffer->pt + 0)
>
> automagically makes PT refer to the current buffer, so the code only
> needs to change current_buffer to have PT set correctly.
>
> By contrast, you propose to have one value of point per thread, which
> means a thread that switches buffers will have to manually change all
> of these values, one by one.  Why is that a good idea?

Switching buffer already involves juggling with pt_marker_ in
record_buffer_markers and fetch_buffer_markers.

So, I do not see much problem here.

> And what about C code which copies/moves text between two buffers?
> For example, some primitives in coding.c can decode text from one
> buffer while writing the decoded text into another.

They still use set_buffer_internal.

There are certain cases when the code needs to fetch buffer point from a
buffer that is not current, but I did not yet look into them closely
before asking if the whole idea is going to be acceptable. (Roughly, I plan
to use thread point history or fallback to default point position as in
buffer constructor).

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 12:25                                                                                         ` Ihor Radchenko
@ 2023-07-24 13:31                                                                                           ` Po Lu
  2023-07-24 13:53                                                                                             ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-24 13:31 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> But editing operations already loop over all the buffer markers. If a
> just dozen of extra buffer markers (one for each thread, in the worse
> case) are unacceptable, we should really do something about marker
> performance.

It is unacceptable because it is drastically more expensive than it is
today.  No degree of optimization to the marker code will eliminate this
problem.

> Last time I wrote a thread code that had to work with buffer text, I had
> to store markers manually anyway. Otherwise, point position were always
> chaotic.

What happens if another thread deletes the text that surrounds point in
the thread your code is running in?  Won't you have the same problem
then anyhow?

> And threads that will not work with buffer text, will only need to store
> a single marker.
>
> So, I do not see how the proposed approach will make things worse
> memory-wise.

I'm not concerned about the memory consumption.  I'm concerned about
both the usability aspects of having even more disparate objects hold
point positions, and the slowdown.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 13:15                                                                                         ` Ihor Radchenko
@ 2023-07-24 13:41                                                                                           ` Eli Zaretskii
  2023-07-24 14:13                                                                                             ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-24 13:41 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Mon, 24 Jul 2023 13:15:55 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >   #define PT (current_buffer->pt + 0)
> >
> > automagically makes PT refer to the current buffer, so the code only
> > needs to change current_buffer to have PT set correctly.
> >
> > By contrast, you propose to have one value of point per thread, which
> > means a thread that switches buffers will have to manually change all
> > of these values, one by one.  Why is that a good idea?
> 
> Switching buffer already involves juggling with pt_marker_ in
> record_buffer_markers and fetch_buffer_markers.

No, it doesn't, not if the code only sets current_buffer.

> > And what about C code which copies/moves text between two buffers?
> > For example, some primitives in coding.c can decode text from one
> > buffer while writing the decoded text into another.
> 
> They still use set_buffer_internal.

That's not necessary for some operations.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 13:31                                                                                           ` Po Lu
@ 2023-07-24 13:53                                                                                             ` Ihor Radchenko
  2023-07-25  0:12                                                                                               ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24 13:53 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> But editing operations already loop over all the buffer markers. If a
>> just dozen of extra buffer markers (one for each thread, in the worse
>> case) are unacceptable, we should really do something about marker
>> performance.
>
> It is unacceptable because it is drastically more expensive than it is
> today.  No degree of optimization to the marker code will eliminate this
> problem.

May you please elaborate?
I routinely deal with buffers having hundreds of markers.
How will adding a couple of markers from threads will make things worse?

>> Last time I wrote a thread code that had to work with buffer text, I had
>> to store markers manually anyway. Otherwise, point position were always
>> chaotic.
>
> What happens if another thread deletes the text that surrounds point in
> the thread your code is running in?  Won't you have the same problem
> then anyhow?

Usually not. The worst case could be some match being skipped, which is
often acceptable. I have seen plenty of examples because Org provides
`org-element-map' API where we allow the user function to change buffer.

Point and restriction changing unpredictably is much bigger problem in
practice, because it can be triggered even without editing the buffer.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 13:02                                                                                     ` Ihor Radchenko
@ 2023-07-24 13:54                                                                                       ` Eli Zaretskii
  2023-07-24 14:24                                                                                         ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-24 13:54 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Mon, 24 Jul 2023 13:02:26 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> Would it be acceptable to convert buffer PT, BEGV, and ZV into
> >> thread-local for current cooperative threads?
> >
> > General note: it is very hard to have a serious discussion of this
> > kind of subject when the goals are not clearly announced, and there
> > are gaps of week or two between messages.  Discussing several separate
> > aspects of this makes this even harder to follow and respond in a
> > useful manner.
> 
> I was initially exploring what should be done allow async threads in
> Emacs. The discussion established several important global states in
> Emacs that must be changed. Then, I explored if those blockers are
> possible to solve. Now, I believe that all the blockers can be solved,
> if we accept to leave GC and redisplay synchronous.

So the goal is to eventually support true concurrency in Emacs?

> However, reducing the global state will not be easy and should better be
> done in steps. Ideally, steps that can be tested using the existing
> synchronous thread paradigm.

But the goal is concurrency? so these steps only make sense if the
concurrency is attainable via these measures?

> >> This way, when a thread yields and later continues executing, its point
> >> and restriction will not be changed.
> >
> > Why is the last sentence a worthy goal? what do you think will be the
> > advantage of that?
> 
> Consider a simple regexp search running in a thread:
> 
> (while (re-search-forward re nil t)
>   (do-useful-staff)
>   (thread-yield))
> 
> This search will fail if another thread ever moves point or changes
> restriction in the same buffer.

You are considering just the simplest scenario.  Usually, Lisp
programs in Emacs not only read text, they also write and delete it.
What if one thread makes changes to buffer text and another thread
then wants to access it using its state variables?  E.g., what do you
plan doing about the gap? will thread switch move the gap or
something? that alone is a performance killer.

> That's why I think that having threads store their own point and
> restriction is useful, even without considering that it will reduce the
> global state.

To be a step in the general direction of achieving concurrency, we
need some plan that will at least in principle allow concurrent
editing.  Even if "concurrent" here means that only one thread can
write to a buffer at a time, we still need to see how this will work
when other threads are unblocked once the writer thread is done
writing.  Can you describe how this will work, assuming we keep the
current design of buffer with a gap?



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 13:41                                                                                           ` Eli Zaretskii
@ 2023-07-24 14:13                                                                                             ` Ihor Radchenko
  0 siblings, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24 14:13 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Switching buffer already involves juggling with pt_marker_ in
>> record_buffer_markers and fetch_buffer_markers.
>
> No, it doesn't, not if the code only sets current_buffer.
>> They still use set_buffer_internal.
>
> That's not necessary for some operations.

Then, we can store pt, zv, and begv as markers.

PT/ZV/BEGV macros will do something like

current_buffer == current_thread->m_pt->buffer?
  current_thread->m_pt->charpos:
  (<update current_thread->m_pt and return it>)

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 13:54                                                                                       ` Eli Zaretskii
@ 2023-07-24 14:24                                                                                         ` Ihor Radchenko
  2023-07-24 16:00                                                                                           ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24 14:24 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> So the goal is to eventually support true concurrency in Emacs?

Yes.

> ... so these steps only make sense if the
> concurrency is attainable via these measures?

Yes, except that the part about thread-local point and restriction may
be more generally useful.

>> Consider a simple regexp search running in a thread:
>> 
>> (while (re-search-forward re nil t)
>>   (do-useful-staff)
>>   (thread-yield))
>> 
>> This search will fail if another thread ever moves point or changes
>> restriction in the same buffer.
>
> You are considering just the simplest scenario.  Usually, Lisp
> programs in Emacs not only read text, they also write and delete it.
> What if one thread makes changes to buffer text and another thread
> then wants to access it using its state variables?  E.g., what do you
> plan doing about the gap? will thread switch move the gap or
> something? that alone is a performance killer.

AFAIK, reading buffer does not require moving the gap.

We only need to move the gap when buffer is changed or before copying
text region from buffer to another buffer. Both such operations should
be considered buffer modifications and must be blocking.

> To be a step in the general direction of achieving concurrency, we
> need some plan that will at least in principle allow concurrent
> editing.  Even if "concurrent" here means that only one thread can
> write to a buffer at a time, we still need to see how this will work
> when other threads are unblocked once the writer thread is done
> writing.  Can you describe how this will work, assuming we keep the
> current design of buffer with a gap?

The idea is the same as with what is already done for indirect buffers.
Indirect buffer modification will affect buffer_text object in the
base buffer (automatically - buffer_text object is shared). And they
will also affect point position in the base buffer.

The point adjustment in the base buffer is done simply by storing point
as a marker. We can do the same for thread-local point positions.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 14:24                                                                                         ` Ihor Radchenko
@ 2023-07-24 16:00                                                                                           ` Eli Zaretskii
  2023-07-24 16:38                                                                                             ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-24 16:00 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Mon, 24 Jul 2023 14:24:32 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > So the goal is to eventually support true concurrency in Emacs?
> 
> Yes.

OK, thanks.

> > ... so these steps only make sense if the
> > concurrency is attainable via these measures?
> 
> Yes, except that the part about thread-local point and restriction may
> be more generally useful.

I suggest to forget that latter part: we have a "generally useful"
code already, so changing it without a very good reason is not wise,
from where I stand.

> > You are considering just the simplest scenario.  Usually, Lisp
> > programs in Emacs not only read text, they also write and delete it.
> > What if one thread makes changes to buffer text and another thread
> > then wants to access it using its state variables?  E.g., what do you
> > plan doing about the gap? will thread switch move the gap or
> > something? that alone is a performance killer.
> 
> AFAIK, reading buffer does not require moving the gap.

We've been through that: at least xml.c moves the gap to allow
external libraries access to buffer text as a single contiguous C
string.  This is a capability I wouldn't want to lose, because it
might come in handy in future developments.

> We only need to move the gap when buffer is changed or before copying
> text region from buffer to another buffer. Both such operations should
> be considered buffer modifications and must be blocking.
> 
> > To be a step in the general direction of achieving concurrency, we
> > need some plan that will at least in principle allow concurrent
> > editing.  Even if "concurrent" here means that only one thread can
> > write to a buffer at a time, we still need to see how this will work
> > when other threads are unblocked once the writer thread is done
> > writing.  Can you describe how this will work, assuming we keep the
> > current design of buffer with a gap?
> 
> The idea is the same as with what is already done for indirect buffers.
> Indirect buffer modification will affect buffer_text object in the
> base buffer (automatically - buffer_text object is shared). And they
> will also affect point position in the base buffer.
> 
> The point adjustment in the base buffer is done simply by storing point
> as a marker. We can do the same for thread-local point positions.

I still don't quite see how this will work.  Indirect buffers don't
introduce parallelism, and programs that modify indirect buffers
_know_ that the text of the base buffer will also be modified.  By
contrast, a thread that has been preempted won't know and won't expect
that.  It could, for example, keep buffer positions in simple
variables, not in markers; almost all Lisp programs do that, and use
markers only in very special situations.

In addition, on the C level, some code computes pointers to buffer
text via BYTE_POS_ADDR, and then uses the pointer as any C program
would.  If such a thread is suspended, and some other thread modifies
buffer text in the meantime, all those pointers will be invalid, and
we have bugs.  So it looks like, if we want to allow concurrent access
to buffers from several threads, we will have a lot of code rewriting
on our hands, and the rewritten code will be less efficient, because
it will have to always access buffer text via buffer positions and
macros like FETCH_BYTE and fetch_char_advance; access through char *
pointers will be lost forever.

So maybe we should take a step back and consider a restriction that
only one thread can access a buffer at any given time?  WDYT?



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 16:00                                                                                           ` Eli Zaretskii
@ 2023-07-24 16:38                                                                                             ` Ihor Radchenko
  2023-07-25  0:20                                                                                               ` Po Lu
  2023-07-25 11:29                                                                                               ` Eli Zaretskii
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-24 16:38 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> AFAIK, reading buffer does not require moving the gap.
>
> We've been through that: at least xml.c moves the gap to allow
> external libraries access to buffer text as a single contiguous C
> string.  This is a capability I wouldn't want to lose, because it
> might come in handy in future developments.

move_gap_both should lock the buffer as it is buffer modification (of
the buffer_text object). htmlReadMemory should also lock it because it
expects constant string segment.

And if we really want xml.c:parse_region to be asynchronous (not
blocking other threads reading the same buffer), we can still do it at
the cost of extra memcpy into newly allocated contiguous memory segment.

>> The point adjustment in the base buffer is done simply by storing point
>> as a marker. We can do the same for thread-local point positions.
>
> I still don't quite see how this will work.  Indirect buffers don't
> introduce parallelism, and programs that modify indirect buffers
> _know_ that the text of the base buffer will also be modified.  By
> contrast, a thread that has been preempted won't know and won't expect
> that.  It could, for example, keep buffer positions in simple
> variables, not in markers; almost all Lisp programs do that, and use
> markers only in very special situations.

Any async thread should expect that current buffer might be modified. Or
lock the buffer for text modifications explicitly (the feature we should
probably provide - something more enforcing compared to read-only-mode
we have now).

> In addition, on the C level, some code computes pointers to buffer
> text via BYTE_POS_ADDR, and then uses the pointer as any C program
> would.  If such a thread is suspended, and some other thread modifies
> buffer text in the meantime, all those pointers will be invalid, and
> we have bugs.  So it looks like, if we want to allow concurrent access
> to buffers from several threads, we will have a lot of code rewriting
> on our hands, and the rewritten code will be less efficient, because
> it will have to always access buffer text via buffer positions and
> macros like FETCH_BYTE and fetch_char_advance; access through char *
> pointers will be lost forever.

Not necessarily lost. We should provide facilities to prevent buffer
from being modified ("write mutex").

This problem is not limited to buffers - any low-level function that
modifies C object struct must enforce the condition when other threads
cannot modify the same object. For example SETCAR will have to mark the
modified object non-writable first, set its car, and release the lock.

So, any time we need a guarantee that an object remains unchanged, we
should acquire object-specific write-preventing mutex.

Of course, such write locks should happen for short periods of time to
be efficient.

Some of the existing uses of BYTE_POS_ADDRS may be converted into
explicit dynamic calls to
 (n < GPT_BYTE ? 0 : GAP_SIZE) + n + BEG_ADDR - BEG_BYTE;
If necessary.

> So maybe we should take a step back and consider a restriction that
> only one thread can access a buffer at any given time?  WDYT?

Buffers are so central in Emacs that I do not want to give up before we
try our best.

Alternatively, I can try to look into other global states first and
leave async buffer access to later. If we can get rid of the truly
global states (which buffer point is not; given that each thread has an
exclusive lock on its buffer), we can later come back to per-thread
point and restriction.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 13:53                                                                                             ` Ihor Radchenko
@ 2023-07-25  0:12                                                                                               ` Po Lu
  2023-07-25  4:28                                                                                                 ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-25  0:12 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> May you please elaborate?
> I routinely deal with buffers having hundreds of markers.
> How will adding a couple of markers from threads will make things worse?

See bug#64391 for a performance regression in Gnus resulting from a
_single_ marker.

> Usually not. The worst case could be some match being skipped, which is
> often acceptable. I have seen plenty of examples because Org provides
> `org-element-map' API where we allow the user function to change buffer.

But Org doesn't run in another thread, does it?  Besides, text matching
is hardly the only tasks our users want to perform in a different
thread.

> Point and restriction changing unpredictably is much bigger problem in
> practice, because it can be triggered even without editing the buffer.

Code that believes this is a problem should devise and make use of
additional synchronization protocols independent from Emacs's internal
buffer synchronization.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 16:38                                                                                             ` Ihor Radchenko
@ 2023-07-25  0:20                                                                                               ` Po Lu
  2023-07-25  4:36                                                                                                 ` Ihor Radchenko
  2023-07-25 11:29                                                                                               ` Eli Zaretskii
  1 sibling, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-25  0:20 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> This problem is not limited to buffers - any low-level function that
> modifies C object struct must enforce the condition when other threads
> cannot modify the same object. For example SETCAR will have to mark the
> modified object non-writable first, set its car, and release the lock.

No, because reading the car of a Lisp_Cons does not depend on any other
field within the Lisp_Cons.

No interlocking is required to read from or write to machine word-sized
fields, as changes to such fields are always propagated coherently to
other CPUs.  At worst, you will need to flush the write cache on any CPU
that has written to one such field.  (Even these machines are rare -- I
don't think Emacs currently supports any.)

Interlocking is only required when correctly interpreting the value of
one field requires the state of other field(s) to be the same as when
the first field was set.  For example, PT and PT_BYTE must always be
interlocked: a thread must not see one value of PT and then read an
earlier or later PT_BYTE corresponding to a different value.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  0:12                                                                                               ` Po Lu
@ 2023-07-25  4:28                                                                                                 ` Ihor Radchenko
  0 siblings, 0 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-25  4:28 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> May you please elaborate?
>> I routinely deal with buffers having hundreds of markers.
>> How will adding a couple of markers from threads will make things worse?
>
> See bug#64391 for a performance regression in Gnus resulting from a
> _single_ marker.

That's not a single marker. Quite far from it.

/* Record the accessible range of the buffer when narrow-to-region
     is called, that is, before applying the narrowing.  That
     information is used only by internal--label-restriction.  */
  Fset (Qoutermost_restriction, list3 (Qoutermost_restriction,
				       Fpoint_min_marker (),
				       Fpoint_max_marker ()));

will create a pair of __new__ markers every time it is called.
And, AFAIU, did not clear them all the way until the next GC.
So, the reproducer mentioned in the report was likely dealing with a
growing number of markers.

There is no doubt that processing markers takes time - Emacs goes
through the whole marker list on every buffer change. But it is
acceptable when the number of markers is moderate, not in the
pathological cases with huge number of markers.

Note, however, that it can (and probably should) be improved.
As discussed in the past, we can utilize itree.c to store markers and
remove the need in O(N_markers) processing when updating marker
positions.

>> Usually not. The worst case could be some match being skipped, which is
>> often acceptable. I have seen plenty of examples because Org provides
>> `org-element-map' API where we allow the user function to change buffer.
>
> But Org doesn't run in another thread, does it?  Besides, text matching
> is hardly the only tasks our users want to perform in a different
> thread.

My point was to show that per-thread point can be quite useful. I did
not try to prove that all the possible tasks potentially done via
threads need it.

Text matching is one of the _common_ tasks when working with buffers,
don't you agree?

>> Point and restriction changing unpredictably is much bigger problem in
>> practice, because it can be triggered even without editing the buffer.
>
> Code that believes this is a problem should devise and make use of
> additional synchronization protocols independent from Emacs's internal
> buffer synchronization.

Please refer to my other message where I showed why synchronization is
extremely difficult with the available tools even for something as
simple as incremental regexp search.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  0:20                                                                                               ` Po Lu
@ 2023-07-25  4:36                                                                                                 ` Ihor Radchenko
  2023-07-25  7:27                                                                                                   ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-25  4:36 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> This problem is not limited to buffers - any low-level function that
>> modifies C object struct must enforce the condition when other threads
>> cannot modify the same object. For example SETCAR will have to mark the
>> modified object non-writable first, set its car, and release the lock.
>
> No, because reading the car of a Lisp_Cons does not depend on any other
> field within the Lisp_Cons.
>
> No interlocking is required to read from or write to machine word-sized
> fields, as changes to such fields are always propagated coherently to
> other CPUs.  At worst, you will need to flush the write cache on any CPU
> that has written to one such field.  (Even these machines are rare -- I
> don't think Emacs currently supports any.)

This is a dangerous assumption.
What if Emacs decides to support such architecture in future?

Simultaneous write and read generally has undefined outcome, unless we
use READ_ONCE and WRITE_ONCE. There are known architectures where
simultaneous read may result in mixing bits from the old and new values.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  4:36                                                                                                 ` Ihor Radchenko
@ 2023-07-25  7:27                                                                                                   ` Po Lu
  2023-07-25  7:59                                                                                                     ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-25  7:27 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> This is a dangerous assumption.
> What if Emacs decides to support such architecture in future?

No such architecture is likely to run Emacs in the future, because it
will fail to support every single piece of multiprocessing software
written up to now.

And if Emacs does need to support such an architecture in the future
(which is a very long shot), we can revisit our choices then.  Until
that time, I see no need to slow down reads and writes to conses or
vectors with interlocking on the basis of purely theoretical
considerations.

> Simultaneous write and read generally has undefined outcome

Only the order in which the reads and writes appear to take place is
undefined.  Provided that only valid Lisp_Objects are written, no
invalid Lisp_Object will ever be read.

>, unless we use READ_ONCE and WRITE_ONCE.

Emacs is not the Linux kernel and is not subject to its considerations.
Besides, READ_ONCE and WRITE_ONCE involve _NO_ interlocking!

> There are known architectures where simultaneous read may result in
> mixing bits from the old and new values.

Which architectures would that be?  Unless you're describing unaligned
reads and writes, or for data types larger or smaller (on the 21064)
than a machine word.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  7:27                                                                                                   ` Po Lu
@ 2023-07-25  7:59                                                                                                     ` Ihor Radchenko
  2023-07-25  8:27                                                                                                       ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-25  7:59 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> There are known architectures where simultaneous read may result in
>> mixing bits from the old and new values.
>
> Which architectures would that be?  Unless you're describing unaligned
> reads and writes, or for data types larger or smaller (on the 21064)
> than a machine word.
 
McKenney (2023) Is Parallel Programming Hard, And, If So, What Can You
Do About It? p. 483

    "For an example, consider
    embedded systems with 32-bit pointers and 16-bit busses.
    On such a system, a data race involving a store to and a
    load from a given pointer might well result in the load
    returning the low-order 16 bits of the old value of the
    pointer concatenated with the high-order 16 bits of the
    new value of the pointer."

>>, unless we use READ_ONCE and WRITE_ONCE.
>
> Emacs is not the Linux kernel and is not subject to its considerations.
> Besides, READ_ONCE and WRITE_ONCE involve _NO_ interlocking!

Sure. I just meant that we need to avoid data races. Using
READ/WRITE_ONCE or mutexes. READ/WRITE_ONCE should be actually avoided,
AFAIK. They will degrade performance significantly (one-two orders of
magnitude) for each individual operation (see Figure 5.1 in the
McKenney's book)

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  7:59                                                                                                     ` Ihor Radchenko
@ 2023-07-25  8:27                                                                                                       ` Po Lu
  2023-07-25  8:45                                                                                                         ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-25  8:27 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> McKenney (2023) Is Parallel Programming Hard, And, If So, What Can You
> Do About It? p. 483
>
>     "For an example, consider
>     embedded systems with 32-bit pointers and 16-bit busses.
>     On such a system, a data race involving a store to and a
>     load from a given pointer might well result in the load
>     returning the low-order 16 bits of the old value of the
>     pointer concatenated with the high-order 16 bits of the
>     new value of the pointer."

Emacs supports such systems?  And they are SMPs as well?

> Sure. I just meant that we need to avoid data races.

Again, there are NO data races on systems Emacs supports.

> Using READ/WRITE_ONCE or mutexes.

So nothing needs to be done.

> READ/WRITE_ONCE should be actually avoided

Because on the hypothetical systems that Emacs will need to support in
the future _and_ do not coherently propagate word sized writes between
CPUs, they must be implemented with interlocking, either in hardware or
in software.  Mutexes, of course, will then need to be implemented on
top of that.

> AFAIK. They will degrade
> performance significantly (one-two orders of magnitude) for each
> individual operation (see Figure 5.1 in the McKenney's book)

Figure 5.1 in that book illustrates the scalability of x86 interlocked
instructions by comparing an increment mechanism using plain loads and
stores to an atomic increment mechanism.  It is not relevant to the
subject at hand.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  8:27                                                                                                       ` Po Lu
@ 2023-07-25  8:45                                                                                                         ` Ihor Radchenko
  2023-07-25  8:53                                                                                                           ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-25  8:45 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> McKenney (2023) Is Parallel Programming Hard, And, If So, What Can You
>> Do About It? p. 483
>>
>>     "For an example, consider
>>     embedded systems with 32-bit pointers and 16-bit busses.
>>     On such a system, a data race involving a store to and a
>>     load from a given pointer might well result in the load
>>     returning the low-order 16 bits of the old value of the
>>     pointer concatenated with the high-order 16 bits of the
>>     new value of the pointer."
>
> Emacs supports such systems?  And they are SMPs as well?

My point is that it will be very difficult to support such systems in
future if we decide that we want to rely upon safety of concurrent read
and write.

>> AFAIK. They will degrade
>> performance significantly (one-two orders of magnitude) for each
>> individual operation (see Figure 5.1 in the McKenney's book)
>
> Figure 5.1 in that book illustrates the scalability of x86 interlocked
> instructions by comparing an increment mechanism using plain loads and
> stores to an atomic increment mechanism.  It is not relevant to the
> subject at hand.

I understood that figure and the associated section differently.
Do you have some kind of reference showing the performance of
READ_ONCE/WRITE_ONCE?

If we need less interlocks, it will certainly make things easier, but I
do not want to run into hard-to-debug bugs caused by data races.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  8:45                                                                                                         ` Ihor Radchenko
@ 2023-07-25  8:53                                                                                                           ` Po Lu
  2023-07-25  9:03                                                                                                             ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-25  8:53 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> My point is that it will be very difficult to support such systems in
> future if we decide that we want to rely upon safety of concurrent read
> and write.

It's very unlikely that these systems will exist in the future and that
Emacs will want to run on them.  These assumptions are currently being
made by Linux and the JVM, just to name a few multiprocessor programs
that do so.

If, by some miracle, these systems do appear, Emacs can either run on a
single CPU, or we can revisit our decisions at that time.

> I understood that figure and the associated section differently.
> Do you have some kind of reference showing the performance of
> READ_ONCE/WRITE_ONCE?

The figure is titled ``Figure 5.1: Atomic Increment Scalability on
x86''.  The surrounding text and source code listings make the
comparison taking place unambiguous.

> If we need less interlocks, it will certainly make things easier, but I
> do not want to run into hard-to-debug bugs caused by data races.

And you won't, if you run your program on a machine Emacs currently
supports.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  8:53                                                                                                           ` Po Lu
@ 2023-07-25  9:03                                                                                                             ` Ihor Radchenko
  2023-07-25  9:17                                                                                                               ` Po Lu
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-25  9:03 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> I understood that figure and the associated section differently.
>> Do you have some kind of reference showing the performance of
>> READ_ONCE/WRITE_ONCE?
>
> The figure is titled ``Figure 5.1: Atomic Increment Scalability on
> x86''.  The surrounding text and source code listings make the
> comparison taking place unambiguous.

Sure, but the whole chapter design shows that READ_ONCE/WRITE_ONCE is
not the best design. They go deep and complex just to avoid using it too
much.

That said, the chapter is dealing with increments specifically and
frequent writes.

In Elisp, we will deal with frequent writes into symbol objects, but
probably not into conses and other non-vectorlike objects.

>> If we need less interlocks, it will certainly make things easier, but I
>> do not want to run into hard-to-debug bugs caused by data races.
>
> And you won't, if you run your program on a machine Emacs currently
> supports.

Ok. I will take your word for granted.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  9:03                                                                                                             ` Ihor Radchenko
@ 2023-07-25  9:17                                                                                                               ` Po Lu
  2023-07-25  9:27                                                                                                                 ` Ihor Radchenko
  0 siblings, 1 reply; 192+ messages in thread
From: Po Lu @ 2023-07-25  9:17 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> Sure, but the whole chapter design shows that READ_ONCE/WRITE_ONCE is
> not the best design. They go deep and complex just to avoid using it too
> much.

That's because READ_ONCE and WRITE_ONCE both have additional semantics
beyond ensuring that loads and stores are coherently propagated and
received to and from other CPUs.  For example, the compiler is not
allowed to merge loads and stores, although it and the CPU are both
allowed to reorder them.

We don't have similar requirements in Emacs.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  9:17                                                                                                               ` Po Lu
@ 2023-07-25  9:27                                                                                                                 ` Ihor Radchenko
  2023-07-25  9:37                                                                                                                   ` Po Lu
  2023-07-25 12:40                                                                                                                   ` Eli Zaretskii
  0 siblings, 2 replies; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-25  9:27 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu <luangruo@yahoo.com> writes:

>> Sure, but the whole chapter design shows that READ_ONCE/WRITE_ONCE is
>> not the best design. They go deep and complex just to avoid using it too
>> much.
>
> That's because READ_ONCE and WRITE_ONCE both have additional semantics
> beyond ensuring that loads and stores are coherently propagated and
> received to and from other CPUs.  For example, the compiler is not
> allowed to merge loads and stores, although it and the CPU are both
> allowed to reorder them.
>
> We don't have similar requirements in Emacs.

I've seen a couple of volatile variables in the code.
So, there is at least some fighting with GCC optimizations going on.

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  9:27                                                                                                                 ` Ihor Radchenko
@ 2023-07-25  9:37                                                                                                                   ` Po Lu
  2023-07-25 12:40                                                                                                                   ` Eli Zaretskii
  1 sibling, 0 replies; 192+ messages in thread
From: Po Lu @ 2023-07-25  9:37 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel

Ihor Radchenko <yantar92@posteo.net> writes:

> I've seen a couple of volatile variables in the code.
> So, there is at least some fighting with GCC optimizations going on.

sig_atomic_t used within signal handlers must be volatile.  Others are
due to interactions between longjmp and automatic register allocation,
not due to store merging or CSE.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-24 16:38                                                                                             ` Ihor Radchenko
  2023-07-25  0:20                                                                                               ` Po Lu
@ 2023-07-25 11:29                                                                                               ` Eli Zaretskii
  2023-07-25 11:52                                                                                                 ` Ihor Radchenko
  1 sibling, 1 reply; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-25 11:29 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Mon, 24 Jul 2023 16:38:55 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> AFAIK, reading buffer does not require moving the gap.
> >
> > We've been through that: at least xml.c moves the gap to allow
> > external libraries access to buffer text as a single contiguous C
> > string.  This is a capability I wouldn't want to lose, because it
> > might come in handy in future developments.
> 
> move_gap_both should lock the buffer as it is buffer modification (of
> the buffer_text object). htmlReadMemory should also lock it because it
> expects constant string segment.

This basically means we will only allow access of a single thread to
the same buffer, see below.

> > I still don't quite see how this will work.  Indirect buffers don't
> > introduce parallelism, and programs that modify indirect buffers
> > _know_ that the text of the base buffer will also be modified.  By
> > contrast, a thread that has been preempted won't know and won't expect
> > that.  It could, for example, keep buffer positions in simple
> > variables, not in markers; almost all Lisp programs do that, and use
> > markers only in very special situations.
> 
> Any async thread should expect that current buffer might be modified.

That's impossible without rewriting a lot of code.  And even after
that, how is a thread supposed to "expect" such changes, when they can
happen at any point in the Lisp program execution?  What kind of
measures can a Lisp program take to 'expect" that?  The only one that
I could think of is to copy the entire buffer to another one, and work
on that.  (Which is also not fool-proof.)

> Or lock the buffer for text modifications explicitly (the feature we
> should probably provide - something more enforcing compared to
> read-only-mode we have now).

Locking while accessing a buffer would in practice mean only one
thread can access a given buffer at the same time.  Which is what I
suggested to begin with, but you said you didn't want to give up.

> > In addition, on the C level, some code computes pointers to buffer
> > text via BYTE_POS_ADDR, and then uses the pointer as any C program
> > would.  If such a thread is suspended, and some other thread modifies
> > buffer text in the meantime, all those pointers will be invalid, and
> > we have bugs.  So it looks like, if we want to allow concurrent access
> > to buffers from several threads, we will have a lot of code rewriting
> > on our hands, and the rewritten code will be less efficient, because
> > it will have to always access buffer text via buffer positions and
> > macros like FETCH_BYTE and fetch_char_advance; access through char *
> > pointers will be lost forever.
> 
> Not necessarily lost. We should provide facilities to prevent buffer
> from being modified ("write mutex").

That again means only one thread can access a given buffer, the rest
will be stuck waiting for the mutex.

> This problem is not limited to buffers - any low-level function that
> modifies C object struct must enforce the condition when other threads
> cannot modify the same object. For example SETCAR will have to mark the
> modified object non-writable first, set its car, and release the lock.
> 
> So, any time we need a guarantee that an object remains unchanged, we
> should acquire object-specific write-preventing mutex.

So we will allow access to many/all objects to only one thread at a
time.  How is this better than the current threads?

> Of course, such write locks should happen for short periods of time to
> be efficient.

How can this be done in practice?  Suppose a Lisp program needs to
access some object, so it locks it.  When will it be able to release
the lock, except after it is basically done?  because accessing an
object is not contiguous: you access it, then do something else, then
access it again, etc. -- and assume that the object will not change
between successive accesses.  If you release the lock after each
individual access, that assumption will be false, and all bets are off
again.

> > So maybe we should take a step back and consider a restriction that
> > only one thread can access a buffer at any given time?  WDYT?
> 
> Buffers are so central in Emacs that I do not want to give up before we
> try our best.

But in practice, what you suggest instead does mean we must give up on
that, see above.

> Alternatively, I can try to look into other global states first and
> leave async buffer access to later. If we can get rid of the truly
> global states (which buffer point is not; given that each thread has an
> exclusive lock on its buffer), we can later come back to per-thread
> point and restriction.

That's up to you, although I don't see how the other objects are
different, as explained above.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25 11:29                                                                                               ` Eli Zaretskii
@ 2023-07-25 11:52                                                                                                 ` Ihor Radchenko
  2023-07-25 14:27                                                                                                   ` Eli Zaretskii
  0 siblings, 1 reply; 192+ messages in thread
From: Ihor Radchenko @ 2023-07-25 11:52 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Any async thread should expect that current buffer might be modified.
>
> That's impossible without rewriting a lot of code.  And even after
> that, how is a thread supposed to "expect" such changes, when they can
> happen at any point in the Lisp program execution?  What kind of
> measures can a Lisp program take to 'expect" that?  The only one that
> I could think of is to copy the entire buffer to another one, and work
> on that.  (Which is also not fool-proof.)

>> Of course, such write locks should happen for short periods of time to
>> be efficient.
>
> How can this be done in practice?  Suppose a Lisp program needs to
> access some object, so it locks it.  When will it be able to release
> the lock, except after it is basically done?  because accessing an
> object is not contiguous: you access it, then do something else, then
> access it again, etc. -- and assume that the object will not change
> between successive accesses.  If you release the lock after each
> individual access, that assumption will be false, and all bets are off
> again.

This is just a basic problem with any kind of async code. It should
either (1) lock the shared object it works with to prevent async writing
(not async reading though); (2) copy the shared object value and work
with the copy; (3) Design the code logic taking into account the
possibility that any shared object might change any time.

All 3 approaches may be combined.

>> Or lock the buffer for text modifications explicitly (the feature we
>> should probably provide - something more enforcing compared to
>> read-only-mode we have now).
>
> Locking while accessing a buffer would in practice mean only one
> thread can access a given buffer at the same time.  Which is what I
> suggested to begin with, but you said you didn't want to give up.

Not necessary. Multiple threads may still read the buffer in parallel,
except special case when read also involves moving the gap (being
written at low level). Or are you saying that moving the gap by read
primitives is common?

>> So, any time we need a guarantee that an object remains unchanged, we
>> should acquire object-specific write-preventing mutex.
>
> So we will allow access to many/all objects to only one thread at a
> time.

Only when modifying global objects by side-effect. Like `setcar' or
`move-marker', or `puthash' (on shared objects). And only for the
duration of that modification.

I do not think that "many/all" objects will be locked in practice.

Symbol objects will be treated separately, with value slot storing
multiple values for different threads. (This is necessary because of how
specpdl and rewind works for symbols)

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



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25  9:27                                                                                                                 ` Ihor Radchenko
  2023-07-25  9:37                                                                                                                   ` Po Lu
@ 2023-07-25 12:40                                                                                                                   ` Eli Zaretskii
  1 sibling, 0 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-25 12:40 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
> Date: Tue, 25 Jul 2023 09:27:03 +0000
> 
> I've seen a couple of volatile variables in the code.
> So, there is at least some fighting with GCC optimizations going on.

That's usually related to setjmp/longjmp, fork/vfork, etc.



^ permalink raw reply	[flat|nested] 192+ messages in thread

* Re: Concurrency via isolated process/thread
  2023-07-25 11:52                                                                                                 ` Ihor Radchenko
@ 2023-07-25 14:27                                                                                                   ` Eli Zaretskii
  0 siblings, 0 replies; 192+ messages in thread
From: Eli Zaretskii @ 2023-07-25 14:27 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: luangruo, emacs-devel

> From: Ihor Radchenko <yantar92@posteo.net>
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> Date: Tue, 25 Jul 2023 11:52:32 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> Of course, such write locks should happen for short periods of time to
> >> be efficient.
> >
> > How can this be done in practice?  Suppose a Lisp program needs to
> > access some object, so it locks it.  When will it be able to release
> > the lock, except after it is basically done?  because accessing an
> > object is not contiguous: you access it, then do something else, then
> > access it again, etc. -- and assume that the object will not change
> > between successive accesses.  If you release the lock after each
> > individual access, that assumption will be false, and all bets are off
> > again.
> 
> This is just a basic problem with any kind of async code.

Async code avoids accessing the same object as much as possible, and
if that's not possible, uses synchronization features.  You also
suggest using synchronization feature, and there's nothing wrong with
that -- except that if those are used to lock buffers and global
objects, we no longer have true concurrency, because in many cases
only one thread will be able to do something useful.  That is all I'm
saying.  If you agree, then this whole development sounds
questionable, because it will require massive changes for very little
practical gain, and with many disadvantages, like the necessity to
call synchronization primitives from Lisp (which makes it impractical
to run the same Lisp program with and without concurrency).

Bottom line: I think your view of what will need to change vs what
will be the costs is very optimistic.  We have only scratched the
surface of the subject, and already there are quite serious issues and
obstacles to be negotiated.  Feel free to work on devising solutions,
but my gut feeling is that this is not worth it, and that Emacs cannot
be adapted to true concurrency without a very thorough redesign of the
core data structures and rethinking of the main architectural
assumptions and models.  E.g., do we even believe that MVC is a proper
architecture for a multithreaded program?

And one other remark: please always keep in mind that Emacs is unusual
in the depth and intensity of the control it lets Lisp programs have
on both input and display.  Any significant architectural change will
have to live up to the expectations of Lisp programmers who are used
to the level of control they have in Emacs.  Providing the same level
of control when input and display run in separate threads from the
Lisp machine, let alone allowing several Lisp threads run in parallel
and complete for such control, might prove much more difficult.  But
if not provided, this will no longer be Emacs, and will not be as
popular with the present community.  That's an additional uphill
battle any new design will have to fight.



^ permalink raw reply	[flat|nested] 192+ messages in thread

end of thread, other threads:[~2023-07-25 14:27 UTC | newest]

Thread overview: 192+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-07-04 16:58 Concurrency via isolated process/thread Ihor Radchenko
2023-07-04 17:12 ` Eli Zaretskii
2023-07-04 17:29   ` Ihor Radchenko
2023-07-04 17:35     ` Eli Zaretskii
2023-07-04 17:52       ` Ihor Radchenko
2023-07-04 18:24         ` Eli Zaretskii
2023-07-05 11:23           ` Ihor Radchenko
2023-07-05 11:49             ` Eli Zaretskii
2023-07-05 12:40               ` Ihor Radchenko
2023-07-05 13:02                 ` Lynn Winebarger
2023-07-05 13:10                   ` Ihor Radchenko
2023-07-06 18:35                     ` Lynn Winebarger
2023-07-07 11:48                       ` Ihor Radchenko
2023-07-05 13:33                 ` Eli Zaretskii
2023-07-05 13:35                   ` Ihor Radchenko
2023-07-05  0:34         ` Po Lu
2023-07-05 11:26           ` Ihor Radchenko
2023-07-05 12:11             ` Po Lu
2023-07-05 12:44               ` Ihor Radchenko
2023-07-05 13:21                 ` Po Lu
2023-07-05 13:26                   ` Ihor Radchenko
2023-07-05 13:51                     ` Eli Zaretskii
2023-07-05 14:00                       ` Ihor Radchenko
2023-07-06  0:32                         ` Po Lu
2023-07-06 10:46                           ` Ihor Radchenko
2023-07-06 12:24                             ` Po Lu
2023-07-06 12:31                               ` Ihor Radchenko
2023-07-06 12:41                                 ` Po Lu
2023-07-06 12:51                                   ` Ihor Radchenko
2023-07-06 12:58                                     ` Po Lu
2023-07-06 13:13                                       ` Ihor Radchenko
2023-07-06 14:13                                         ` Eli Zaretskii
2023-07-06 14:47                                           ` Ihor Radchenko
2023-07-06 15:10                                             ` Eli Zaretskii
2023-07-06 16:17                                               ` Ihor Radchenko
2023-07-06 18:19                                                 ` Eli Zaretskii
2023-07-07 12:04                                                   ` Ihor Radchenko
2023-07-07 13:16                                                     ` Eli Zaretskii
2023-07-07 14:29                                                       ` Ihor Radchenko
2023-07-07 14:47                                                         ` Eli Zaretskii
2023-07-07 15:21                                                           ` Ihor Radchenko
2023-07-07 18:04                                                             ` Eli Zaretskii
2023-07-07 18:24                                                               ` Ihor Radchenko
2023-07-07 19:36                                                                 ` Eli Zaretskii
2023-07-07 20:05                                                                   ` Ihor Radchenko
2023-07-08  7:05                                                                     ` Eli Zaretskii
2023-07-08 10:53                                                                       ` Ihor Radchenko
2023-07-08 14:26                                                                         ` Eli Zaretskii
2023-07-09  9:36                                                                           ` Ihor Radchenko
2023-07-09  9:56                                                                             ` Po Lu
2023-07-09 10:04                                                                               ` Ihor Radchenko
2023-07-09 11:59                                                                             ` Eli Zaretskii
2023-07-09 13:58                                                                               ` Ihor Radchenko
2023-07-09 14:52                                                                                 ` Eli Zaretskii
2023-07-09 15:49                                                                                   ` Ihor Radchenko
2023-07-09 16:35                                                                                     ` Eli Zaretskii
2023-07-10 11:30                                                                                       ` Ihor Radchenko
2023-07-10 12:13                                                                                         ` Po Lu
2023-07-10 12:28                                                                                           ` Ihor Radchenko
2023-07-10 12:48                                                                                             ` Po Lu
2023-07-10 12:53                                                                                               ` Ihor Radchenko
2023-07-10 13:18                                                                                                 ` Po Lu
2023-07-10 13:09                                                                                         ` Eli Zaretskii
2023-07-10 13:58                                                                                           ` Ihor Radchenko
2023-07-10 14:37                                                                                             ` Eli Zaretskii
2023-07-10 14:55                                                                                               ` Ihor Radchenko
2023-07-10 16:03                                                                                                 ` Eli Zaretskii
2023-07-16 14:58                                                                                 ` Ihor Radchenko
2023-07-17  7:55                                                                                   ` Ihor Radchenko
2023-07-17  8:36                                                                                     ` Po Lu
2023-07-17  8:52                                                                                       ` Ihor Radchenko
2023-07-17  9:39                                                                                         ` Po Lu
2023-07-17  9:54                                                                                           ` Ihor Radchenko
2023-07-17 10:08                                                                                             ` Po Lu
2023-07-24  8:42                                                                                 ` Ihor Radchenko
2023-07-24  9:52                                                                                   ` Po Lu
2023-07-24 10:09                                                                                     ` Ihor Radchenko
2023-07-24 12:15                                                                                       ` Po Lu
2023-07-24 12:25                                                                                         ` Ihor Radchenko
2023-07-24 13:31                                                                                           ` Po Lu
2023-07-24 13:53                                                                                             ` Ihor Radchenko
2023-07-25  0:12                                                                                               ` Po Lu
2023-07-25  4:28                                                                                                 ` Ihor Radchenko
2023-07-24 12:50                                                                                       ` Eli Zaretskii
2023-07-24 13:15                                                                                         ` Ihor Radchenko
2023-07-24 13:41                                                                                           ` Eli Zaretskii
2023-07-24 14:13                                                                                             ` Ihor Radchenko
2023-07-24 12:44                                                                                   ` Eli Zaretskii
2023-07-24 13:02                                                                                     ` Ihor Radchenko
2023-07-24 13:54                                                                                       ` Eli Zaretskii
2023-07-24 14:24                                                                                         ` Ihor Radchenko
2023-07-24 16:00                                                                                           ` Eli Zaretskii
2023-07-24 16:38                                                                                             ` Ihor Radchenko
2023-07-25  0:20                                                                                               ` Po Lu
2023-07-25  4:36                                                                                                 ` Ihor Radchenko
2023-07-25  7:27                                                                                                   ` Po Lu
2023-07-25  7:59                                                                                                     ` Ihor Radchenko
2023-07-25  8:27                                                                                                       ` Po Lu
2023-07-25  8:45                                                                                                         ` Ihor Radchenko
2023-07-25  8:53                                                                                                           ` Po Lu
2023-07-25  9:03                                                                                                             ` Ihor Radchenko
2023-07-25  9:17                                                                                                               ` Po Lu
2023-07-25  9:27                                                                                                                 ` Ihor Radchenko
2023-07-25  9:37                                                                                                                   ` Po Lu
2023-07-25 12:40                                                                                                                   ` Eli Zaretskii
2023-07-25 11:29                                                                                               ` Eli Zaretskii
2023-07-25 11:52                                                                                                 ` Ihor Radchenko
2023-07-25 14:27                                                                                                   ` Eli Zaretskii
2023-07-09 17:13                                                                             ` Gregory Heytings
2023-07-10 11:37                                                                               ` Ihor Radchenko
2023-07-13 13:54                                                                                 ` Gregory Heytings
2023-07-13 14:23                                                                                   ` Ihor Radchenko
2023-07-07  0:21                                         ` Po Lu
2023-07-06 14:08                             ` Eli Zaretskii
2023-07-06 15:01                               ` Ihor Radchenko
2023-07-06 15:16                                 ` Eli Zaretskii
2023-07-06 16:32                                   ` Ihor Radchenko
2023-07-06 17:50                                     ` Eli Zaretskii
2023-07-07 12:30                                       ` Ihor Radchenko
2023-07-07 13:34                                         ` Eli Zaretskii
2023-07-07 15:17                                           ` Ihor Radchenko
2023-07-07 19:31                                             ` Eli Zaretskii
2023-07-07 20:01                                               ` Ihor Radchenko
2023-07-08  6:50                                                 ` Eli Zaretskii
2023-07-08 11:55                                                   ` Ihor Radchenko
2023-07-08 14:43                                                     ` Eli Zaretskii
2023-07-09  9:57                                                       ` Ihor Radchenko
2023-07-09 12:08                                                         ` Eli Zaretskii
2023-07-09 14:16                                                           ` Ihor Radchenko
2023-07-09 15:00                                                             ` Eli Zaretskii
2023-07-09 12:22                                                         ` Po Lu
2023-07-09 13:12                                                           ` Eli Zaretskii
2023-07-10  0:18                                                             ` Po Lu
2023-07-08  0:51                                               ` Po Lu
2023-07-08  4:18                                                 ` tomas
2023-07-08  5:51                                                   ` Po Lu
2023-07-08  6:01                                                     ` tomas
2023-07-08 10:02                                                       ` Ihor Radchenko
2023-07-08 19:39                                                         ` tomas
2023-07-08  6:25                                                 ` Eli Zaretskii
2023-07-08  6:38                                                   ` Ihor Radchenko
2023-07-08  7:45                                                     ` Eli Zaretskii
2023-07-08  8:16                                                       ` Ihor Radchenko
2023-07-08 10:13                                                         ` Eli Zaretskii
2023-07-07 13:35                                         ` Po Lu
2023-07-07 15:31                                           ` Ihor Radchenko
2023-07-08  0:44                                             ` Po Lu
2023-07-08  4:29                                               ` tomas
2023-07-08  7:21                                               ` Eli Zaretskii
2023-07-08  7:48                                                 ` Po Lu
2023-07-08 10:02                                                   ` Eli Zaretskii
2023-07-08 11:54                                                     ` Po Lu
2023-07-08 14:12                                                       ` Eli Zaretskii
2023-07-09  0:37                                                         ` Po Lu
2023-07-09  7:01                                                           ` Eli Zaretskii
2023-07-09  7:14                                                             ` Po Lu
2023-07-09  7:35                                                               ` Eli Zaretskii
2023-07-09  7:57                                                                 ` Ihor Radchenko
2023-07-09  8:41                                                                   ` Eli Zaretskii
2023-07-10 14:53                                                                     ` Dmitry Gutov
2023-07-09  9:25                                                                 ` Po Lu
2023-07-09 11:14                                                                   ` Eli Zaretskii
2023-07-09 11:23                                                                     ` Ihor Radchenko
2023-07-09 12:10                                                                     ` Po Lu
2023-07-09 13:03                                                                       ` Eli Zaretskii
2023-07-08 12:01                                                     ` Ihor Radchenko
2023-07-08 14:45                                                       ` Eli Zaretskii
2023-07-07  0:41                                     ` Po Lu
2023-07-07 12:42                                       ` Ihor Radchenko
2023-07-07 13:31                                         ` Po Lu
2023-07-07  0:27                                 ` Po Lu
2023-07-07 12:45                                   ` Ihor Radchenko
2023-07-06  0:27                     ` Po Lu
2023-07-06 10:48                       ` Ihor Radchenko
2023-07-06 12:15                         ` Po Lu
2023-07-06 14:10                         ` Eli Zaretskii
2023-07-06 15:09                           ` Ihor Radchenko
2023-07-06 15:18                             ` Eli Zaretskii
2023-07-06 16:36                               ` Ihor Radchenko
2023-07-06 17:53                                 ` Eli Zaretskii
2023-07-07  0:22                             ` Po Lu
2023-07-05  0:33 ` Po Lu
2023-07-05  2:31   ` Eli Zaretskii
2023-07-17 20:43     ` Hugo Thunnissen
2023-07-18  4:51       ` tomas
2023-07-18  5:25       ` Ihor Radchenko
2023-07-18  5:39         ` Po Lu
2023-07-18  5:49           ` Ihor Radchenko
2023-07-18 12:14         ` Hugo Thunnissen
2023-07-18 12:39           ` Async IO and queing process sentinels (was: Concurrency via isolated process/thread) Ihor Radchenko
2023-07-18 12:49             ` Ihor Radchenko
2023-07-18 14:12             ` Async IO and queing process sentinels Michael Albinus

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).