Re:Async destructors, async genericity and completion futures
by sbf501 on Wednesday June 01, 2022 @09:04PM
(#31590121)
I started reading this hoping to get some insight into creating a way to abort an in flight promise, e.g. async/await pattern for a tower of async functons, and not a Promise.all[]. However, this is so exhaustively well-written then I realized this is a much harder problem. Yes, I know this is Rust, but the syntax and problems are similar for any deferred execution, ESPECIALLY when hardware is sitting at the bottom of the call.
While I applaud the author's completeness and attention to detail -- and how they are refreshingly open about certain solutions looking like "hacks" -- the paper would benefit from a Conclusions/Recommendations. (I know, that's lazy of me, but I think it would be a nice finish.)
Re:Async destructors, async genericity and completion futures
by bsder on Thursday June 02, 2022 @05:20PM
(#31600639)
> the paper would benefit from a Conclusions/Recommendations. (I know, that's lazy of me, but I think it would be a nice finish.)
Conclusion: Don't use async/await?
I'm being somewhat glib, but posts like these are really a gigantic warning sign that the abstraction may not be correct.
Async/await is supposed to make things easier, no? If the abstraction is leaking this much, you should start asking if it's actually making things easier. (Seee: Java's Thread.stop()/Thread.interrupt() issues for prior art)
> I started reading this hoping to get some insight into creating a way to abort an in flight promise
At the bottom, the issue you are asking is "How do I smack the event loop Executor that async/await is hiding so my Promise can run out its error/abort code to completion?"
And this is my primary beef with async/await. All of the attempts to hide the Executor are very leaky and demand lots of gymnastics to work around. This is doubly true in a non-GC language like Rust where you can't count on a global thing like the GC to clean everything up eventually.
I suspect that there is something to async/await, and I am grateful for people thrashing it out. However, I suspect that people need to shake off their Javascript-colored glasses before we get down to the "real" abstraction.
Re:Async destructors, async genericity and completion futures
by AgentME on Thursday June 02, 2022 @03:18AM
(#31592327)
A good way across languages to cancel an asynchronous task is by passing a CancellationToken / AbortSignal (Javascript) as an argument. This scales nicely as a task can pass the token onward to any number of other tasks, and the original creator of the token can abort it and all uses of it at any time.
Re:Async destructors, async genericity and completion futures
by zaphar on Friday June 03, 2022 @09:33AM
(#31608020)
This is the primary use-case for Contexts in go. It's standard practice to pass contexts through all your concurrent code and abort on the `Cancel` call.
Re:Async destructors, async genericity and completion futures
by sbf501 on Thursday June 02, 2022 @02:15PM
(#31598556)
I remember a github repo like this from ages ago. How is the token used? I assume the token has a function that triggers an abort, but does that mean the downstream code has to constantly check the token, or does it simply generate a fault/exception at the lowest level that bubbles upstream? I'm not sure of the token mechanics.
Re:Async destructors, async genericity and completion futures
by AgentME on Friday June 03, 2022 @01:03AM
(#31604489)
It works like your first guess, but it's not so bad. Downstream code has to either check the token occasionally, subscribe to an event from it, or just pass it forward to another function which uses the token. So if you were writing a function that just made a series of network requests, you would make it take a CancellationToken as a parameter, and then you would pass that parameter on to each of the network request function calls inside your function. Then if the token is in the aborted state while any of the network function calls are in-progress, then that network function call will throw an exception which will bubble up through your function, without your function needing to do anything special with the CancellationToken directly.
Re:Async destructors, async genericity and completion futures
by AgentME on Monday June 06, 2022 @05:24PM
(#31646495)
Kotlin has "structured concurrency" which I believe automatically handles some cases of this, but I don't fully understand it. I find explicit cancellation tokens without any magic easier to understand.
Re:Async destructors, async genericity and completion futures
by saghm on Wednesday June 01, 2022 @09:32PM
(#31590292)
My team had to deal with something like this a couple years ago. Because of some external constraints on the implementation of our connection pool, we needed to emit events to a listener whenever a connection was returned to the pool, but we wanted to have the connection automatically checked back into the pool when dropped, and that required doing some async things. The "solution" we came up with was that checking the connection back in a short bit later was acceptable, so we just spawned an async task in the `Drop` implementation that would emit the event and check the connection back into the pool. Spawning an async task is fairly cheap, so the `drop` method would finish quickly, and the work needed to check the connection back in the pool was done in the background. The only tricky part was implementing the test suite, as part of the external constraint was that we had to verify our behavior with some tests expressed in an agnostic, declarative fashion which expected us to be able to assert that events happened in a deterministic order. In practice, adding a short yield to the runtime in the test harness between operations caused the async runtime to almost always return the connection to the pool with the spare time it had, and we reasoned that we could always increase the length of time the test executor yielded if we ended up having too many spurious failures.
Re:Async destructors, async genericity and completion futures
by lilyball on Wednesday June 01, 2022 @08:20PM
(#31589829)
Yes, but you'd have to generate that dummy value upon creating the real value, and then pass it along with the real value so it's available to the spot that needs to consume it. This would ultimately end up being just a much more awkward way of having the real value just track a boolean internally for this and slapping Option around any non-trivial return value from the method.
Re:Async destructors, async genericity and completion futures
by Animats on Thursday June 02, 2022 @01:57AM
(#31591849)
> There is no way to statically guarantee that the method isn’t called twice, that’s up to the user.
That sounds like "re-animation", where a destructor leaks something to the outside that keeps the object from being destructed. That was in Microsoft Managed C++, and it was similarly awful.
If all you want to do is clean up something on deletion, how about passing the thing that needs cleanup to a channel, to be handled by another task?
Re:Async destructors, async genericity and completion futures
by cute_boi on Thursday June 02, 2022 @09:02AM
(#31594304)
Thanks, Sabrina for this wonderful article regarding async destructors. I think the Rust team committed a mistake by not considering Async destructors. I think they can use edition boundry to fix this issue. Let's see.
Re:Async destructors, async genericity and completion futures
by bruce343434 on Thursday June 02, 2022 @08:36AM
(#31594019)
Could you explain in non-academeze what an algebraic effect is and how it's the solution here? Genuine question since everything I read about algebraic effects is either impenetrable math notation or some article about how it's a resumable exception.
It's almost analogous to monads apparently being wrapper types (but somehow different/with implications?).
While I applaud the author's completeness and attention to detail -- and how they are refreshingly open about certain solutions looking like "hacks" -- the paper would benefit from a Conclusions/Recommendations. (I know, that's lazy of me, but I think it would be a nice finish.)
Conclusion: Don't use async/await?
I'm being somewhat glib, but posts like these are really a gigantic warning sign that the abstraction may not be correct.
Async/await is supposed to make things easier, no? If the abstraction is leaking this much, you should start asking if it's actually making things easier. (Seee: Java's Thread.stop()/Thread.interrupt() issues for prior art)
> I started reading this hoping to get some insight into creating a way to abort an in flight promise
At the bottom, the issue you are asking is "How do I smack the event loop Executor that async/await is hiding so my Promise can run out its error/abort code to completion?"
And this is my primary beef with async/await. All of the attempts to hide the Executor are very leaky and demand lots of gymnastics to work around. This is doubly true in a non-GC language like Rust where you can't count on a global thing like the GC to clean everything up eventually.
I suspect that there is something to async/await, and I am grateful for people thrashing it out. However, I suspect that people need to shake off their Javascript-colored glasses before we get down to the "real" abstraction.
Couldn't this be solved by giving the user a dummy object that isn't Clone and that the method takes ownership of?
That sounds like "re-animation", where a destructor leaks something to the outside that keeps the object from being destructed. That was in Microsoft Managed C++, and it was similarly awful.
If all you want to do is clean up something on deletion, how about passing the thing that needs cleanup to a channel, to be handled by another task?
It's almost analogous to monads apparently being wrapper types (but somehow different/with implications?).