concurrency icon indicating copy to clipboard operation
concurrency copied to clipboard

Thread-safe management of messages

Open nordlow opened this issue 2 years ago • 1 comments

Handling of messages should be thread-safe by default. This requires a message type T to either

  1. have solely immutable indirections if any (!hasAliasing!T)
  2. have indirections protected behind shared accessors, or
  3. be a unique/isolated data type with no implicit aliasing, for instance, as is(T == Unique!U, U)

Would it be motivated to expose concurrency operations that requires shallow or deep duplication of messages via calls to .dup ?

It might be useful/relevant to have concurrency operations be inferred @safe when the messages it operates on fulfills any of the properties above.

  • [ ] Add concurrency test case for Unique messages that copies some test case from just. Test assetion could do, for instance,
just(14.toUnique).syncWait.value.should == 14;

nordlow avatar Oct 19 '21 12:10 nordlow

The handling of messages is already thread-safe, but there is a problem in some places where users could build Senders out of unshared objects and then later have operations where these Senders are executed in a different execution context.

An interesting idea is to allow it until it crosses a thread boundary. That way it would be ok in a single-thread fiber application. (Although we probably want to allow moving fibers across threads, so it likely is marginal).

Ultimately there are a few cases with regards to shared data:

  1. if it is a value struct with no indirection we can just copy it
  2. if it is a unique reference we want to transfer ownership to the Sender
  3. if it is a fully shared object, it needs to be qualified shared

Anything else should be an error.

Case 2 is difficult. D can't really express a unique reference on a language level (it is the missing piece).

There is std.typecons.Unique but it requires the thing to be on the heap. The question is, how do you get it on the heap? You need to construct it there. That is problematic since how many things do you know that return a Unique!T? Not many. Yet there are plenty of things that return objects that are logically unique, that return a unique reference. But how do you lift those into a Unique!T?

Would it be motivated to expose concurrency operations that requires shallow or deep duplication of messages via calls to .dup ?

Absolutely not. No hidden .dup calls please. It is far better to require people to call .dup themselves.

It might be useful/relevant to have concurrency operations be inferred @safe when the messages it operates on fulfills any of the properties above.

I started out with inferring @safe. But since you often create long chains of operations it becomes a nightmare to debug when it isn't inferred where you think it should. Another issue is the fact that the library uses delegates instead of alias lambda template parameters. Instead of inferring the library requires delegates to be @safe to begin with and then guarantees everything to be @safe. The nice thing is that if the delegate isn't @safe (or @trusted as escape hatch), you will get a good compiler error.

skoppe avatar Oct 20 '21 08:10 skoppe