Tasks, actors and garbage collection

Ola Fosheim Grøstad ola.fosheim.grostad at gmail.com
Tue Apr 20 21:03:53 UTC 2021


On Tuesday, 20 April 2021 at 16:21:39 UTC, Petar Kirov 
[ZombineDev] wrote:
> By passing the allocator as an object, you allow it to be used 
> safely from `pure` functions. (If `pure` functions were to 
> somehow be allowed to use those global allocator variables, you 
> could have some ugly consequences. For example, a pure function 
> can be preempted in the middle of its execution, only to have 
> the global allocator replaced under its feet, thereby leaving 
> all the memory allocated from the previous allocator dangling.)

Yes, but I think it is too tedious to pass around allocators. 
Having a global user-modified variable is also not good for 
static analysis. I think a Task ought to be a compiler construct, 
or at least a language-runtime-construct.

It might be desirable to have different types of Tasks, like one 
that is GC based and another type that is more like C++.

Then the compiler need to keep tabs of the call-tree and ensure 
that only a GC call-tree allows a regular pointer to own a new 
object.

And shared pointers could always be owning (possibly RC-based) 
unless some kind of borrowing scheme is implemented.

> Pure code (even in the relaxed D sense) is great for 
> parallelism, as a scheduler can essentially assume that it's 
> both lock-free and wait-free - it doesn't need to interact with 
> any other thread/fiber/task to make progress.

I guess that could be useful. How would it affect scheduling?

> There two main challenges:
> 1. Ensuring code doesn't brake the assumptions of the actor 
> model by e.g. sharing memory between threads in an uncontrolled 
> manner. This can be addressed in a variety of ways:
>     * The framework's build-system can prevent you from 
> importing code that doesn't fit its model

Hm, what implications are you thinking of that are different from 
what D has to deal with under the current scheme? Are you 
thinking about coexisting with the current regime of having a 
global GC as a transition, perhaps?

>     * The framework can run a non-optional linter as part of 
> the build process, which would ensure that you don't have:
>         * `@system` or `@trusted` code

But you should be able to call @trusted? Or maybe have a 
different mechanism like @unsafe.


>     * reference capabilities like [Pony][3]'s

Yes, I think being able to transition a shared object with a 
refcount of 1 into a GC-owned non-shared object could be 
desirable.

> 2. Making it ergonomic and easy to use, as is using the GC. 
> Essentially having all language and library features that 
> currently require the GC use `LocalGCAllocator` automagically.

Yes, I think you either have a thread local current_task pointer 
or have a dedicated hardware-register point to the current task. 
(implementation defined)

>     * Add `context` as the last parameter to each of druntime 
> function that may need to allocate memory set it's default 
> value to the global GC context. This is a pure refactoring, no 
> change in behavior.

I guess an alternative would be to transition to a new runtime. 
Then code that depends on the current runtime will have to be 
rewritten to work with the new regime, and compilation failure 
would protect mistakes from going unnoticed. Or, if a transition 
is needed, then I guess each runtime could assert that it isn't 
called from a task call-tree (check a thread local variable).


>     * Add Scala `implicit` parameters [⁴][4] [⁵][5] [⁶][6] 
> [⁷][7] [⁸][8] to the language and mark the `context` parameters 
> as `implicit`

The FAQ on implicits was long... maybe it is a language design 
mistake? :-)

Ola.


More information about the Digitalmars-d mailing list