Re: [swift-evolution] [Concurrency] async/await + actors: cancellation

2017-08-19 Thread Jan Tuitman via swift-evolution
Hi Joe,

Thanks for the answers so far! 

Abrupt cancellation is indeed not a good idea, but I wander if it is possible 
on every place where “await” is being used, to let the compiler generate  code 
which handles cancellation, assuming that can be cheap enough (and I am not 
qualified to judge if that is indeed the case)

Especially in the case where “await” implies “throws”, part of what you need 
for that is already in place. I imagine that it would work like this:
I imagine f(x) -> T is compiled as something that looks like f(x, callback: (T) 
-> Void). What if this was f(x,process, callback) where process is a simple 
pointer, that goes out of scope together with callback? This pointer the 
compiler can use to access a compiler generated mutable state, to see if the 
top level beginAsync { } in which context the call is being executed, has been 
canceled. The compiler could generate this check whenever it is going to do a 
new await call to a deeper level, and if the check said that the top level is 
canceled, the compiler can throw an exception. 

Would that introduce too much overhead? It does not seem to need references to 
the top level any longer than the callback needs to be kept alive.

I am asking this, because once Async/await is there, it will probably 
immediately become very popular, but the use case of having to abort a task 
from the same location where you start the task, is of course a very common 
one. Think of a view controller downloading some resources and then being moved 
of the screen by the user.

if everybody needs to wrap Async/await in classes which handle the 
cancellation, and share state with the tasks that can be cancelled, it might be 
cleaner to solve this problem in an invisible way, so that it is also 
standardized. This way there is more separation between the code of the task 
and the code that starts and cancels the task.

I assume actors in the future also are going to need a way to tell each other 
that pending messages can be cancelled, so, I think, in the end you need 
something for cancellation anyways. 

For the programmer it would look like this:

var result 
var process = beginAsync {
   result = await someSlowFunctionWithManyAwaitsInside(x)

}

// if it is no longer needed.
process.cancel()
// this will raise an exception inside someSlowFunction if this function enters 
an await.
// but not if it is waiting or actively doing something. So, it is also not 
guaranteed to cancel the function.



Regards,
Jan



> Op 18 aug. 2017 om 21:04 heeft Joe Groff  het volgende 
> geschreven:
> 
> 
>> On Aug 17, 2017, at 11:53 PM, Jan Tuitman via swift-evolution 
>>  wrote:
>> 
>> Hi,
>> 
>> 
>> After reading Chris Lattners proposal for async/await I wonder if the 
>> proposal has any way to cancel outstanding tasks.
>> 
>> I saw these two:
>> 
>> @IBAction func buttonDidClick(sender:AnyObject) {
>> // 1
>> beginAsync {
>>  // 2
>>  let image = await processImage()
>>  imageView.image = image
>> }
>> // 3
>> } 
>> 
>> 
>> And:
>> 
>> /// Shut down the current coroutine and give its memory back to the
>> /// shareholders.
>> func abandon() async -> Never {
>> await suspendAsync { _ = $0 }
>> }
>> 
>> 
>> Now, if I understand this correctly, the second thing is abandoning the task 
>> from the context of the task by basically preventing the implicit callback 
>> of abandon() to ever be called.
>> 
>> But I don't see any way how the beginAsync {} block can be canceled after a 
>> certain amount of time by the synchronous thread/context that is running at 
>> location //3
> 
> This is not something the proposal aims to support, and as you noted, abrupt 
> cancellation from outside a thread is not something you should generally do, 
> and which is not really possible to do robustly with cooperatively-scheduled 
> fibers like the coroutine proposal aims to provide. The section above is 
> making the factual observation that, in our model, a coroutine once suspended 
> can end up being dropped entirely by releasing all references to its 
> continuation, and discusses the impact that possibility has on the model. 
> This shouldn't be mistaken for proper cancellation support; as David noted, 
> that's something you should still code explicit support for if you need it.
> 
> -Joe
> 
___
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


Re: [swift-evolution] [Concurrency] async/await + actors: cancellation

2017-08-18 Thread Joe Groff via swift-evolution

> On Aug 17, 2017, at 11:53 PM, Jan Tuitman via swift-evolution 
>  wrote:
> 
> Hi,
> 
> 
> After reading Chris Lattners proposal for async/await I wonder if the 
> proposal has any way to cancel outstanding tasks.
> 
> I saw these two:
> 
> @IBAction func buttonDidClick(sender:AnyObject) {
>  // 1
>  beginAsync {
>// 2
>let image = await processImage()
>imageView.image = image
>  }
>  // 3
> } 
> 
> 
> And:
> 
> /// Shut down the current coroutine and give its memory back to the
> /// shareholders.
> func abandon() async -> Never {
>  await suspendAsync { _ = $0 }
> }
> 
> 
> Now, if I understand this correctly, the second thing is abandoning the task 
> from the context of the task by basically preventing the implicit callback of 
> abandon() to ever be called.
> 
> But I don't see any way how the beginAsync {} block can be canceled after a 
> certain amount of time by the synchronous thread/context that is running at 
> location //3

This is not something the proposal aims to support, and as you noted, abrupt 
cancellation from outside a thread is not something you should generally do, 
and which is not really possible to do robustly with cooperatively-scheduled 
fibers like the coroutine proposal aims to provide. The section above is making 
the factual observation that, in our model, a coroutine once suspended can end 
up being dropped entirely by releasing all references to its continuation, and 
discusses the impact that possibility has on the model. This shouldn't be 
mistaken for proper cancellation support; as David noted, that's something you 
should still code explicit support for if you need it.

-Joe

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


Re: [swift-evolution] [Concurrency] async/await + actors: cancellation

2017-08-18 Thread David P Grove via swift-evolution

> From: Jan Tuitman via swift-evolution 
> To: swift-evolution@swift.org
> Date: 08/18/2017 02:54 AM
> Subject: [swift-evolution] [Concurrency] async/await + actors:
cancellation
> Sent by: swift-evolution-boun...@swift.org
>
> Hi,
>
>
> After reading Chris Lattners proposal for async/await I wonder if
> the proposal has any way to cancel outstanding tasks.
>
> I saw these two:
>
> @IBAction func buttonDidClick(sender:AnyObject) {
>   // 1
>   beginAsync {
> // 2
> let image = await processImage()
> imageView.image = image
>   }
>   // 3
> }
>
>
> And:
>
> /// Shut down the current coroutine and give its memory back to the
> /// shareholders.
> func abandon() async -> Never {
>   await suspendAsync { _ = $0 }
> }
>
>
> Now, if I understand this correctly, the second thing is abandoning
> the task from the context of the task by basically preventing the
> implicit callback of abandon() to ever be called.
>
> But I don't see any way how the beginAsync {} block can be canceled
> after a certain amount of time by the synchronous thread/context
> that is running at location //3
>
> shouldn't beginAsync return something which can be checked if the
> block passed in to it is finished/ waiting/ ...? and which has a
> method to cancel it?
> I know Thread.cancel (which existed in some programming languages)
> is evil because you never know where it stops, but it seems strange
> to me, that there is no way to communicate with the running process
in //2.
>
> Then there is this example:
>
> func processImageData() async throws -> Image {
>   startProgressBar()
>   defer {
> // This will be called when error is thrown, when all operations
> // complete and a result is returned, or when the coroutine is
> // abandoned. We don't want to leave the progress bar animating if
> // work has stopped.
> stopProgressBar()
>   }
>
> let dataResource  = try await loadWebResource("dataprofile.txt")
>   let imageResource = try await loadWebResource("imagedata.dat")
>   do {
> let imageTmp= try await decodeImage(dataResource, imageResource)
>   } catch _ as CorruptedImage {
> // Give up hope now.
> await abandon()
>   }
>   return try await dewarpAndCleanupImage(imageTmp)
> }
>
>
> this seems to wrap other asynchronous functions in a new async
> function so it can add the defer logic and abandon logic, but this
> seems to me only adding more checking of the spawned process and
> thus not sufficient enough to deal with an external reason in
> location //3 (for example the process running //3 receives an event
> that the app is about to be quitted).
>
> so I am a bit confused here, am I missing something? How would //2
> be canceled from location //3, or how would //3 trigger an
> abandoment inside //2 ?
>

My initial reading is that cancellation is not part of the async/await
design.  I think this is the right decision.  (I hope I am right ;) ).

First the semantics of arbitrary cancellation are problematic (as you
noted);  it is really hard to write reliable code when a thread of control
could be spontaneously killed at any program point but the program as a
whole still needs to be able to continue to operate).  Second, support for
cancellation imposes a significant implementation burden.  It implies that
you actually need to make individual tasks into real program-visible
entities and that can get in the way of high-performance implementation
techniques (which Chris alludes to as perhaps being needed in Swift 6/7/8
for an actor system to really become viable at scale).

Much more reasonable to allow the programmer to plumb in cancellation where
it is needed via shared state that can be checked at safely cancelable
points in the task.

regards,

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


[swift-evolution] [Concurrency] async/await + actors: cancellation

2017-08-17 Thread Jan Tuitman via swift-evolution
Hi,


After reading Chris Lattners proposal for async/await I wonder if the proposal 
has any way to cancel outstanding tasks.

I saw these two:

@IBAction func buttonDidClick(sender:AnyObject) {
  // 1
  beginAsync {
// 2
let image = await processImage()
imageView.image = image
  }
  // 3
} 


And:

/// Shut down the current coroutine and give its memory back to the
/// shareholders.
func abandon() async -> Never {
  await suspendAsync { _ = $0 }
}


Now, if I understand this correctly, the second thing is abandoning the task 
from the context of the task by basically preventing the implicit callback of 
abandon() to ever be called.

But I don't see any way how the beginAsync {} block can be canceled after a 
certain amount of time by the synchronous thread/context that is running at 
location //3

shouldn't beginAsync return something which can be checked if the block passed 
in to it is finished/ waiting/ ...? and which has a method to cancel it?
I know Thread.cancel (which existed in some programming languages) is evil 
because you never know where it stops, but it seems strange to me, that there 
is no way to communicate with the running process in //2.

Then there is this example:

func processImageData() async throws -> Image {
  startProgressBar()
  defer {
// This will be called when error is thrown, when all operations
// complete and a result is returned, or when the coroutine is
// abandoned. We don't want to leave the progress bar animating if
// work has stopped.
stopProgressBar()
  }

let dataResource  = try await loadWebResource("dataprofile.txt")
  let imageResource = try await loadWebResource("imagedata.dat")
  do {
let imageTmp= try await decodeImage(dataResource, imageResource)
  } catch _ as CorruptedImage {
// Give up hope now.
await abandon()
  }
  return try await dewarpAndCleanupImage(imageTmp)
}


this seems to wrap other asynchronous functions in a new async function so it 
can add the defer logic and abandon logic, but this seems to me only adding 
more checking of the spawned process and thus not sufficient enough to deal 
with an external reason in location //3 (for example the process running //3 
receives an event that the app is about to be quitted). 

so I am a bit confused here, am I missing something? How would //2 be canceled 
from location //3, or how would //3 trigger an abandoment inside //2 ?

Jan Tuitman

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