[Concurrency] async/await + actors

I think we are not allowed to implicitly switch back to the actor's queue after awaiting non-actor methods. These might have been auto converted from Continuation-Passing-Style (CPS) to async/await style. With the `mainActor` idea from the manifesto, all existing code will run in some actor, so changing the queue semantics could break existing code.

Actually, I don’t find it confusing: when calling non-actor methods, you have to take care by yourself - as today. Calling actor methods instead, you don’t have to bother about this any longer - this is actually a big incentive to ‚actorify‘ many APIs ;-)

Cheers
Marc

···

Am 24.08.2017 um 22:05 schrieb Thomas via swift-evolution <swift-evolution@swift.org>:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Thomas

I don’t think await should cause the actor’s queue (or any queue) to be suspended. Actor methods should not block waiting for asynchronous things. That’s how you get deadlocks. If an actor method needs to be async then it should work just like any async method on the main queue: it unblocks the queue and allows other messages to be processed until it gets an answer.

You do have to be aware of the fact that things can happen in between an await and the next line of code, but conveniently these places are all marked for you. They all say “await”. :)

···

On Aug 24, 2017, at 3:15 PM, Thomas <tclementdev@free.fr> wrote:

On 24 Aug 2017, at 23:47, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:

On Aug 24, 2017, at 1:05 PM, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 24 Aug 2017, at 21:48, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Yes, it would be confusing. await should either always return to the same queue or never do it. Otherwise it’s even more error-prone. I see the actor feature as being just another demonstration of why solving the queue-hopping problem is important for async/await to be useful.

So the way a non "fire and forget" actor method would work is:

- the actor's queue is in a suspended state until the method returns, this is required so that messages sent to other actor methods are not processed (they're added to the queue)
- if the method body awaits on some other code, it automatically jumps back on the actor's queue after awaiting, regardless of the queue's suspension and content
- when the method returns, the actor's queue is resumed and pending messages can be processed (if any)

Yes, that is important to note: when we `await` on an `async` method, the callers queue does not get blocked in any way. The control flow just continues - with the next instruction after the enclosing `beginAsync` I suppose - and when done with the current DispatchWorkItem just dequeues the next DispatchWorkItem and works on it. If there is no next item, the underlying thread might still not be suspend but be used to work on some another queue.

Despite that, we might still want to discuss if actor-methods should get serialized beyond that - think of an underlying GCD queue (as discussed above) and a separate (non-GDC) message-queue where actor messages will get queued up. In another thread I proposed to introduce a new modifier for actor methods which will not put them into the message-queue and which are thus allowed to run whenever the GCD queue picks them up:

serialized by message-queue:
`actor func foo() async`

non-serialized:
`interleaved actor func bar() async`

This way, when you reason about your code and look at places marked with `await`, only `interleaved` methods (or code using explicit `beginAsync` calls) might have changed your state.

Cheers
Marc

···

Am 25.08.2017 um 01:15 schrieb Adam Kemp via swift-evolution <swift-evolution@swift.org>:

I don’t think await should cause the actor’s queue (or any queue) to be suspended. Actor methods should not block waiting for asynchronous things. That’s how you get deadlocks. If an actor method needs to be async then it should work just like any async method on the main queue: it unblocks the queue and allows other messages to be processed until it gets an answer.

You do have to be aware of the fact that things can happen in between an await and the next line of code, but conveniently these places are all marked for you. They all say “await”. :)

It is correct that suspending the queue allows for deadlocks, but not doing it means you can receive messages while still in the middle of another message. For the same reason you may need FIFO ordering in a class to guarantee coherency, you will want this to work in an asynchronous world as well. Take for example some storage class:

1. store(object, key)
2. fetch(key)

If you're doing these operations in order, you want the fetch to return the object you just stored. If the 'store' needs to await something in its implementation and we were to not suspend the queue, the fetch would be processed before the object is actually stored and it would return something unexpected.

Thomas

···

On 25 Aug 2017, at 01:15, Adam Kemp <adam.kemp@apple.com> wrote:

On Aug 24, 2017, at 3:15 PM, Thomas <tclementdev@free.fr <mailto:tclementdev@free.fr>> wrote:

On 24 Aug 2017, at 23:47, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:

On Aug 24, 2017, at 1:05 PM, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 24 Aug 2017, at 21:48, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Yes, it would be confusing. await should either always return to the same queue or never do it. Otherwise it’s even more error-prone. I see the actor feature as being just another demonstration of why solving the queue-hopping problem is important for async/await to be useful.

So the way a non "fire and forget" actor method would work is:

- the actor's queue is in a suspended state until the method returns, this is required so that messages sent to other actor methods are not processed (they're added to the queue)
- if the method body awaits on some other code, it automatically jumps back on the actor's queue after awaiting, regardless of the queue's suspension and content
- when the method returns, the actor's queue is resumed and pending messages can be processed (if any)

I don’t think await should cause the actor’s queue (or any queue) to be suspended. Actor methods should not block waiting for asynchronous things. That’s how you get deadlocks. If an actor method needs to be async then it should work just like any async method on the main queue: it unblocks the queue and allows other messages to be processed until it gets an answer.

You do have to be aware of the fact that things can happen in between an await and the next line of code, but conveniently these places are all marked for you. They all say “await”. :)

This would only happen when the caller is an actor, which means new code, so I don't think we would be breaking any existing code.

Thomas

···

On 25 Aug 2017, at 09:04, Marc Schlichte <marc.schlichte@googlemail.com> wrote:

Am 24.08.2017 um 22:05 schrieb Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Thomas

I think we are not allowed to implicitly switch back to the actor's queue after awaiting non-actor methods. These might have been auto converted from Continuation-Passing-Style (CPS) to async/await style. With the `mainActor` idea from the manifesto, all existing code will run in some actor, so changing the queue semantics could break existing code.

Actors can use other means to serialize operations if they need to, for instance by using an internal queue of pending operations. It’s better for actors that need this kind of serialization to handle it explicitly than for every actor to suffer from potential deadlocks when doing seemingly straightforward things.

async/await in general is not meant to block anything. It’s explicitly meant to avoid blocking things. That’s what the feature is for. It would be confusing if await did something different for actor methods than it did for every other context.

···

On Aug 25, 2017, at 1:14 AM, Thomas <tclementdev@free.fr> wrote:

On 25 Aug 2017, at 01:15, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:
I don’t think await should cause the actor’s queue (or any queue) to be suspended. Actor methods should not block waiting for asynchronous things. That’s how you get deadlocks. If an actor method needs to be async then it should work just like any async method on the main queue: it unblocks the queue and allows other messages to be processed until it gets an answer.

You do have to be aware of the fact that things can happen in between an await and the next line of code, but conveniently these places are all marked for you. They all say “await”. :)

It is correct that suspending the queue allows for deadlocks, but not doing it means you can receive messages while still in the middle of another message. For the same reason you may need FIFO ordering in a class to guarantee coherency, you will want this to work in an asynchronous world as well. Take for example some storage class:

1. store(object, key)
2. fetch(key)

If you're doing these operations in order, you want the fetch to return the object you just stored. If the 'store' needs to await something in its implementation and we were to not suspend the queue, the fetch would be processed before the object is actually stored and it would return something unexpected.

Oh but yeah, the main actor will probably need migration/warnings from the compiler.

Thomas

···

On 25 Aug 2017, at 10:17, Thomas via swift-evolution <swift-evolution@swift.org> wrote:

On 25 Aug 2017, at 09:04, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Am 24.08.2017 um 22:05 schrieb Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Thomas

I think we are not allowed to implicitly switch back to the actor's queue after awaiting non-actor methods. These might have been auto converted from Continuation-Passing-Style (CPS) to async/await style. With the `mainActor` idea from the manifesto, all existing code will run in some actor, so changing the queue semantics could break existing code.

This would only happen when the caller is an actor, which means new code, so I don't think we would be breaking any existing code.

By the way, contrary to what I said earlier, that means we'd need to do this also for "fire and forget" methods, as is "store" in this example.

···

On 25 Aug 2017, at 10:14, Thomas via swift-evolution <swift-evolution@swift.org> wrote:

On 25 Aug 2017, at 01:15, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:

On Aug 24, 2017, at 3:15 PM, Thomas <tclementdev@free.fr <mailto:tclementdev@free.fr>> wrote:

On 24 Aug 2017, at 23:47, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:

On Aug 24, 2017, at 1:05 PM, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 24 Aug 2017, at 21:48, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Yes, it would be confusing. await should either always return to the same queue or never do it. Otherwise it’s even more error-prone. I see the actor feature as being just another demonstration of why solving the queue-hopping problem is important for async/await to be useful.

So the way a non "fire and forget" actor method would work is:

- the actor's queue is in a suspended state until the method returns, this is required so that messages sent to other actor methods are not processed (they're added to the queue)
- if the method body awaits on some other code, it automatically jumps back on the actor's queue after awaiting, regardless of the queue's suspension and content
- when the method returns, the actor's queue is resumed and pending messages can be processed (if any)

I don’t think await should cause the actor’s queue (or any queue) to be suspended. Actor methods should not block waiting for asynchronous things. That’s how you get deadlocks. If an actor method needs to be async then it should work just like any async method on the main queue: it unblocks the queue and allows other messages to be processed until it gets an answer.

You do have to be aware of the fact that things can happen in between an await and the next line of code, but conveniently these places are all marked for you. They all say “await”. :)

It is correct that suspending the queue allows for deadlocks, but not doing it means you can receive messages while still in the middle of another message. For the same reason you may need FIFO ordering in a class to guarantee coherency, you will want this to work in an asynchronous world as well. Take for example some storage class:

1. store(object, key)
2. fetch(key)

If you're doing these operations in order, you want the fetch to return the object you just stored. If the 'store' needs to await something in its implementation and we were to not suspend the queue, the fetch would be processed before the object is actually stored and it would return something unexpected.

Also think about this storage class being used concurrently. If the 'store' method is called concurrently and you don't suspend the queue, you'd end up with some of these 'store' requests processed while possibly in the middle of previous 'store' requests. That doesn't seem very safe. Soon enough, you'll end up wanting to wrap your class into an async FIFO pipeline.

Thomas

···

On 25 Aug 2017, at 10:14, Thomas via swift-evolution <swift-evolution@swift.org> wrote:

On 25 Aug 2017, at 01:15, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:

On Aug 24, 2017, at 3:15 PM, Thomas <tclementdev@free.fr <mailto:tclementdev@free.fr>> wrote:

On 24 Aug 2017, at 23:47, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:

On Aug 24, 2017, at 1:05 PM, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 24 Aug 2017, at 21:48, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Yes, it would be confusing. await should either always return to the same queue or never do it. Otherwise it’s even more error-prone. I see the actor feature as being just another demonstration of why solving the queue-hopping problem is important for async/await to be useful.

So the way a non "fire and forget" actor method would work is:

- the actor's queue is in a suspended state until the method returns, this is required so that messages sent to other actor methods are not processed (they're added to the queue)
- if the method body awaits on some other code, it automatically jumps back on the actor's queue after awaiting, regardless of the queue's suspension and content
- when the method returns, the actor's queue is resumed and pending messages can be processed (if any)

I don’t think await should cause the actor’s queue (or any queue) to be suspended. Actor methods should not block waiting for asynchronous things. That’s how you get deadlocks. If an actor method needs to be async then it should work just like any async method on the main queue: it unblocks the queue and allows other messages to be processed until it gets an answer.

You do have to be aware of the fact that things can happen in between an await and the next line of code, but conveniently these places are all marked for you. They all say “await”. :)

It is correct that suspending the queue allows for deadlocks, but not doing it means you can receive messages while still in the middle of another message. For the same reason you may need FIFO ordering in a class to guarantee coherency, you will want this to work in an asynchronous world as well. Take for example some storage class:

1. store(object, key)
2. fetch(key)

If you're doing these operations in order, you want the fetch to return the object you just stored. If the 'store' needs to await something in its implementation and we were to not suspend the queue, the fetch would be processed before the object is actually stored and it would return something unexpected.

I'd tend to think non-FIFO actor messaging will cause more trouble than potential deadlocks. I'm re-reading the proposal and it seems to go this way as well:

"An await on an actor method suspends the current task, and since you can get circular waits, you can end up with deadlock. This is because only one message is processed by the actor at a time. The trivial case like this can also be trivially diagnosed by the compiler. The complex case would ideally be diagnosed at runtime with a trap, depending on the runtime implementation model."

···

On 25 Aug 2017, at 18:30, Adam Kemp <adam.kemp@apple.com> wrote:

On Aug 25, 2017, at 1:14 AM, Thomas <tclementdev@free.fr <mailto:tclementdev@free.fr>> wrote:

On 25 Aug 2017, at 01:15, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:
I don’t think await should cause the actor’s queue (or any queue) to be suspended. Actor methods should not block waiting for asynchronous things. That’s how you get deadlocks. If an actor method needs to be async then it should work just like any async method on the main queue: it unblocks the queue and allows other messages to be processed until it gets an answer.

You do have to be aware of the fact that things can happen in between an await and the next line of code, but conveniently these places are all marked for you. They all say “await”. :)

It is correct that suspending the queue allows for deadlocks, but not doing it means you can receive messages while still in the middle of another message. For the same reason you may need FIFO ordering in a class to guarantee coherency, you will want this to work in an asynchronous world as well. Take for example some storage class:

1. store(object, key)
2. fetch(key)

If you're doing these operations in order, you want the fetch to return the object you just stored. If the 'store' needs to await something in its implementation and we were to not suspend the queue, the fetch would be processed before the object is actually stored and it would return something unexpected.

Actors can use other means to serialize operations if they need to, for instance by using an internal queue of pending operations. It’s better for actors that need this kind of serialization to handle it explicitly than for every actor to suffer from potential deadlocks when doing seemingly straightforward things.

async/await in general is not meant to block anything. It’s explicitly meant to avoid blocking things. That’s what the feature is for. It would be confusing if await did something different for actor methods than it did for every other context.

I would claim - without having a prove though - that as long as you don’t invoke async actor methods on weak or unowned actor references and the code is retain cycle free, no deadlocks will happen.

Cheers
Marc

···

Am 25.08.2017 um 19:08 schrieb Adam Kemp via swift-evolution <swift-evolution@swift.org>:

I understand what you’re saying, but I just think trying to make synchronous, blocking actor methods goes against the fundamental ideal of the actor model, and it’s a recipe for disaster. When actors communicate with each other that communication needs to be asynchronous or you will get deadlocks. It’s not just going to be a corner case. It’s going to be a very frequent occurrence.

One of the general rules of multithreaded programming is “don’t call unknown code while holding a lock”. Blocking a queue is effectively the same as holding a lock, and calling another actor is calling unknown code. So if the model works that way then the language itself will be encouraging people to call unknown code while holding locks. That is not going to go well.

I understand what you’re saying, but I just think trying to make synchronous, blocking actor methods goes against the fundamental ideal of the actor model, and it’s a recipe for disaster. When actors communicate with each other that communication needs to be asynchronous or you will get deadlocks. It’s not just going to be a corner case. It’s going to be a very frequent occurrence.

One of the general rules of multithreaded programming is “don’t call unknown code while holding a lock”. Blocking a queue is effectively the same as holding a lock, and calling another actor is calling unknown code. So if the model works that way then the language itself will be encouraging people to call unknown code while holding locks. That is not going to go well.

···

On Aug 25, 2017, at 9:54 AM, Thomas <tclementdev@free.fr> wrote:

I'd tend to think non-FIFO actor messaging will cause more trouble than potential deadlocks. I'm re-reading the proposal and it seems to go this way as well:

"An await on an actor method suspends the current task, and since you can get circular waits, you can end up with deadlock. This is because only one message is processed by the actor at a time. The trivial case like this can also be trivially diagnosed by the compiler. The complex case would ideally be diagnosed at runtime with a trap, depending on the runtime implementation model."

I’m not sure I understand. What is the connection between references and deadlocks?

···

On Aug 25, 2017, at 1:07 PM, Marc Schlichte <marc.schlichte@googlemail.com> wrote:

Am 25.08.2017 um 19:08 schrieb Adam Kemp via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

I understand what you’re saying, but I just think trying to make synchronous, blocking actor methods goes against the fundamental ideal of the actor model, and it’s a recipe for disaster. When actors communicate with each other that communication needs to be asynchronous or you will get deadlocks. It’s not just going to be a corner case. It’s going to be a very frequent occurrence.

One of the general rules of multithreaded programming is “don’t call unknown code while holding a lock”. Blocking a queue is effectively the same as holding a lock, and calling another actor is calling unknown code. So if the model works that way then the language itself will be encouraging people to call unknown code while holding locks. That is not going to go well.

I would claim - without having a prove though - that as long as you don’t invoke async actor methods on weak or unowned actor references and the code is retain cycle free, no deadlocks will happen.

Cheers
Marc

I’m not sure I understand. What is the connection between references and deadlocks?

This is what I had in mind:

To have a deadlock from async actor methods, you would need some mutual invocations of them - i.e a cycle in the call graph.

If your code is (strong) retain cycle free and you make invocations only on actors of which you have strong references, you will also have no cyclic call graph, hence no deadlocks.

Now, unfortunately - and contrary to my claim - deadlocks still can happen:

if you `await` in your async actor method on some state which can only be set via another actor method in your actor, a deadlock occurs:

Example:

actor class A {
  var continuation: (() -> Void)?
  actor func m1() async {
    await suspendAsync { cont in
      continuation = cont
    }
  }
  actor func m2() {
    continuation?()
  }
}

If someone calls `a.m1()`, and someone else `a.m2()`, `a.m1()` still does not complete as `a.m2()` is not allowed to run while `a.m1()` is not finished.

Marking `m2` as an `interleaved actor func` would remedy that situation as it could then run when the next work item is picked from the serial gdc queue - which can happen while we `await` on the `suspendAsync` in the example above.

Cheers
Marc

···

Am 26.08.2017 um 02:03 schrieb Adam Kemp via swift-evolution <swift-evolution@swift.org>:

On Aug 25, 2017, at 1:07 PM, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Am 25.08.2017 um 19:08 schrieb Adam Kemp via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

I understand what you’re saying, but I just think trying to make synchronous, blocking actor methods goes against the fundamental ideal of the actor model, and it’s a recipe for disaster. When actors communicate with each other that communication needs to be asynchronous or you will get deadlocks. It’s not just going to be a corner case. It’s going to be a very frequent occurrence.

One of the general rules of multithreaded programming is “don’t call unknown code while holding a lock”. Blocking a queue is effectively the same as holding a lock, and calling another actor is calling unknown code. So if the model works that way then the language itself will be encouraging people to call unknown code while holding locks. That is not going to go well.

I would claim - without having a prove though - that as long as you don’t invoke async actor methods on weak or unowned actor references and the code is retain cycle free, no deadlocks will happen.

Cheers
Marc

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

To avoid or at least detect deadlocks you need: timeout (which will at
least generate an error), cancel (which will prevent zombie processes), and
status information (for debugging). It doesn’t make any difference if the
reference is strong or weak. There is an advantage in strong references
since you can fire and forget if you want a deamon process that is totally
self managing.

···

On Sun, 27 Aug 2017 at 12:53 am, Marc Schlichte via swift-evolution < swift-evolution@swift.org> wrote:

Am 26.08.2017 um 02:03 schrieb Adam Kemp via swift-evolution < > swift-evolution@swift.org>:

I’m not sure I understand. What is the connection between references and
deadlocks?

This is what I had in mind:

To have a deadlock from async actor methods, you would need some mutual
invocations of them - i.e a cycle in the call graph.

If your code is (strong) retain cycle free and you make invocations only
on actors of which you have strong references, you will also have no cyclic
call graph, hence no deadlocks.

Now, unfortunately - and contrary to my claim - deadlocks still can happen:

if you `await` in your async actor method on some state which can only be
set via another actor method in your actor, a deadlock occurs:

Example:

actor class A {
  var continuation: (() -> Void)?
  actor func m1() async {
    await suspendAsync { cont in
      continuation = cont
    }
  }
  actor func m2() {
    continuation?()
  }
}

If someone calls `a.m1()`, and someone else `a.m2()`, `a.m1()` still does
not complete as `a.m2()` is not allowed to run while `a.m1()` is not
finished.

Marking `m2` as an `interleaved actor func` would remedy that situation as
it could then run when the next work item is picked from the serial gdc
queue - which can happen while we `await` on the `suspendAsync` in the
example above.

Cheers
Marc

On Aug 25, 2017, at 1:07 PM, Marc Schlichte <marc.schlichte@googlemail.com> > wrote:

Am 25.08.2017 um 19:08 schrieb Adam Kemp via swift-evolution < > swift-evolution@swift.org>:

I understand what you’re saying, but I just think trying to make
synchronous, blocking actor methods goes against the fundamental ideal of
the actor model, and it’s a recipe for disaster. When actors communicate
with each other that communication needs to be asynchronous or you will get
deadlocks. It’s not just going to be a corner case. It’s going to be a very
frequent occurrence.

One of the general rules of multithreaded programming is “don’t call
unknown code while holding a lock”. Blocking a queue is effectively the
same as holding a lock, and calling another actor is calling unknown code.
So if the model works that way then the language itself will be encouraging
people to call unknown code while holding locks. That is not going to go
well.

I would claim - without having a prove though - that as long as you don’t
invoke async actor methods on weak or unowned actor references and the code
is retain cycle free, no deadlocks will happen.

Cheers
Marc

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-- Howard.