[ 
https://issues.apache.org/jira/browse/GROOVY-9381?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18033368#comment-18033368
 ] 

Jochen Theodorou commented on GROOVY-9381:
------------------------------------------

finally found some time to write something here again. So let me come back to 
my points and talk about it in more detail.

h3. Return Values
{code:Java}
  async int foo() {1}
  assert  await foo() == 1
  assert foo().class == Promise.class
  assert await foo() andthen foo() andThen foo() == 1
  assert await foo() << foo() <<  foo() == 1
{code}
Base on that the return value will be wrapped in a Promise The code above would 
cover my expectations. Maybe the line before the last line may needsome kind of 
brackets, but that would be my expectation for a command expression based on a 
andThen method on Promise, which executes the next promise after the inner 
Promise did complete. And maybe we want something like << for operator 
overloading for it... just thinking.

h3. Exception Handling
{code:Java}
    try {
        def user = await fetchUserData(1)
    } catch (UserNotFoundException e) {
        println "Caught error: ${e.message}"
    }
{code}
is quite easy, of course with the usual problem of not having user anymore.
{code:Java}
    try {
        def user = await fetchUserData(1).or(fetchUserData(2))
    } catch (UserNotFoundException e) {
        println "Caught error: ${e.message}"
    }
{code}
In this code I depend on a promise method called or, which will combine the 
Promises to complete if any of the promises completes. Here my expectations are 
that fetchUserData(1) is not called before the or has been executed and that if 
fetchUserData(2) is faster (which it obviously is not here) its exception will 
be thrown and a possible exception from fetchUserData(1) is ignored. You could 
argue that this is an implementation detail of "or", but not  starting 
fetchUserData(1) immediatly is imho not... the await call starts the execution 
of the promise.
{code:Java}
    try {
        def user = await fetchUserData(1).and(fetchUserData(2))
    } catch (UserNotFoundException e) {
        println "Caught error: ${e.message}"
    }
{code}
Similar to "or" this "and" here executes fetchUserData(1) and fetchUserData(2) 
in parallel, but this time waits for both to complete. the question here though 
is... what exception do they throw? Again, strictly speaking an implementation 
detail of "and", but this situation can happen and people need to know ow to 
handle this. 

h3. Using a special return type
{code:Java}
  Promise<Integer> foo(){ return new MyPromise(1)}
  await foo() == 1 
{code}
Do we want to allow for code like this? Of course this means:
{code:Java}
  async Promise<Integer> foo(){ return new MyPromise(1)}
  await foo() instanceof MyPromise 
  await await foo() == 1
{code}
Again... just thinking out loud about options thins we may want to have or 
maybe not. I feel not so sure about chaining await.... but it would make sense 
here, would it not?
{code:Java}
  def foo(){ return new MyPromise(1)}
  await foo() == 1 
{code}
And of course we have to think about the case of calling a method with await, 
that is not async. Exception? Just execute while ignoring await? And actually 
one more
{code:Java}
  async foo(){1}
  def f1 = foo()
  assert await f1 == 1
  assert await f1 == 1
{code}
Should it be possible to wait twice for the same Promise? Sure. But does it 
execute foo() once or twice? Knowing the most likely implementation the answer 
is once... but is that what we want?

h3. async for loop
What I was thinking about is described here: 
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of

h3. Virtual Threads vs Platform Threads and integrations
Well, well.. this topic is actually more complicated than I thought. If I just 
take Virtual Threads, Platform Threads, Executors, Pools, Tasks, Job and all 
that together I reach the following abstractions: 
#  some way to influence what is started
#  how to start 
And here I start to wonder if the Promise API in the PR is not too rich and is 
already implying too much. See for example my andThen, and/or methods above. 
Virtual Threads are cooperative, maybe running in the same Platform Thread and 
as such cannot run truly in parallel in that case. Even if you only go with a 
thread pool for Platform Threads, you cannot expect always Platform Thread 
parallelism. And even if is makes sense to define a thread pool of a certain 
size, depending on the number of cores... if everyone is doing that, then you 
do not gain the results you wish for. So just creating a pool is ok for the 
stand-alone case, but not if you are in a framework. And maybe the pool is not 
about threads, but about a cluster. 

h3. Back to Basics?
I think the real power of async-await becomes more clear when you think about 
it with cooperative multitasking in mind. calling an async method will execute 
that code in a different context, while my context is paused. Or maybe better 
like this... Let us assume I have a cooperative scheduler, that will execute 
code in a single thread. If I await a method call I enqueue the call in the 
scheduler. And once the code completes it will schedule to continue where my 
await left of. that gives a scheduler two slots to cooperatively execute other 
code paths. In that sense virtual threads would be the nearest implementation 
idea to this.  But we are on the JVM, which is really good with Threads. Where 
virtual threads come into play is when high load causes ten-thousands of 
threads to spawn and the context switch starts to cost more than the executed 
code.

I think what we need is the Promise in a basic API where it stores maybe a 
bound MethodHandle as target, the exception and the return value, maybe a 
status. The default implementation should consider to execute this code in 
place, like synchronous code. And we need some kind of Scheduler/Executioner to 
run them otherwise. and for that last part I was thinking that maybe await 
should be a library method instead, that we can import with import static for 
example. We have even support for "import static Foo.bar as await" allowing 
await more as convention then as required method name.
Then I can put all the logic about virtual threads, platform threads, cluster 
execution, scheduled execution, repeatable or not and so one in that 
Implementation and provide the implementation that makes sense to us. 

Where this of course fails is the async for loop idea....

Anyway... I spend hours writing this and I think it is most fair to let others 
read and comment!

> Support async/await like ES7
> ----------------------------
>
>                 Key: GROOVY-9381
>                 URL: https://issues.apache.org/jira/browse/GROOVY-9381
>             Project: Groovy
>          Issue Type: New Feature
>            Reporter: Daniel Sun
>            Priority: Major
>
> Here is an example to show proposed syntax and backend API(Java's 
> {{CompletableFuture}} or GPars's {{{}Promise{}}}), but I think it's better 
> for Groovy to have its own {{Promise}} to decouple with Java API because 
> async/await as a language feature should be as stable as possible.
> {{async}} will generate the {{Awaitable}} instance such as Groovy {{Promise}} 
> implementing the {{Awaitable}} interface, and {{await}} can wait for any 
> {{Awaitable}} instance to complete and unwrap it for the result. 
> {code:java}
> /**
>  * 1. An async function that simulates a network API call.
>  * The 'async' keyword implies it runs asynchronously without blocking.
>  */
> async fetchUserData(userId) {
>     println "Starting to fetch data for user ${userId}..."
>     
>     // Simulate a 1-second network delay.
>     Thread.sleep(1000) 
>     
>     println "Fetch successful!"
>     // The 'async' function implicitly returns a "CompletableFuture" or 
> "Promise" containing this value.
>     return [userId: userId, name: 'Daniel']
> }
> /**
>  * 2. An async function that uses 'await' to consume the result.
>  */
> async processUserData() {
>     println "Process started, preparing to fetch user data..."
>     
>     try {
>         // 'await' pauses this function until fetchUserData completes
>         // and returns the final result directly.
>         def user = await fetchUserData(1)
>         
>         println "Data received: ${user}"
>         return "Processing complete for ${user.name}."
>         
>     } catch (Exception e) {
>         return "An error occurred: ${e.message}"
>     }
> }
> // --- Execution ---
> println "Script starting..."
> // Kick off the entire asynchronous process.
> def future = processUserData()
> // This line executes immediately, proving the process is non-blocking.
> println "Script continues to run while user data is being fetched in the 
> background..."
> def result = future.get()
> println "Script finished: ${result}"
> {code}
> Use async/await with closure or lambda expression:
> {code}
> // use closure
> def c = async {
>     println "Process started, preparing to fetch user data..."
>     
>     try {
>         // 'await' pauses this function until fetchUserData completes
>         // and returns the final result directly.
>         def user = await fetchUserData(1)
>         
>         println "Data received: ${user}"
>         return "Processing complete for ${user.name}."
>         
>     } catch (Exception e) {
>         return "An error occurred: ${e.message}"
>     }
> }
> def future = c()
> {code}
> {code}
> // use lambda expression
> def c = async () -> {
>     println "Process started, preparing to fetch user data..."
>     
>     try {
>         // 'await' pauses this function until fetchUserData completes
>         // and returns the final result directly.
>         def user = await fetchUserData(1)
>         
>         println "Data received: ${user}"
>         return "Processing complete for ${user.name}."
>         
>     } catch (Exception e) {
>         return "An error occurred: ${e.message}"
>     }
> }
> def future = c()
>  {code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to