#1695 Tackling asynchronous events in Fantom

MoOm Sat 12 Nov 2011

Fantom has a nice concurrency model with actors and constness but I think it misses some built-in way to deal with asynchrony. Actors are a nice way to prevent concurrent accesses when dealing with threads but most of the time, a thread isn't actually needed in the first place. Often, we create a thread because we need to perform blocking operations and we don't want to block our whole process while we are waiting for the result. Thus, we end up having a lot of threads that spent most of their time waiting for results. Abusing of threads have the following consequences:

  • Dealing with threads means you have to change your programming model: either you use locks (Java way) or you use actors/constness (Fantom way), but the code always needs to be adapted
  • If we limit the number of threads (as in an actor-pool), all threads can be blocked waiting for a resource, ending up in starving and blocking the whole process
  • Each thread has its own stack: if each stack is 512KB, 1000 threads consumes 500MB just for stacks
  • Context switching has a cost even if is probably negligible in most cases

On the other hand, threads (or actors) are still the perfect solution for any CPU-expensive treatment and to take profit of all the cores of a CPU.

Problem with asynchrony

To illustrate the problem, let's take the following example: we want to upload a local directory and the files it contains on an FTP server, and then return the new number of files the remote directory contains. Here is how we'd write the code if we don't care about blocking:

Int uploadDir(File dir)
{
  try
  {
    ftpClient.createDir(dir.name)
    ftpClient.moveToDir(dir.name)
    dir.listFiles.each |file|
    {
      ftpClient.uploadFile(file)
    }
    return ftpClient.countFiles
  }
  catch (Err err) { echo("Upload has failed"); throw err }
}

To handle asynchronous I/O, methods usually take a completion handler that gets called when the operation is complete. Thus, the above code would need to be rewritten the following way (aka the "node.js" way):

Void uploadDir(File dir, |Int, Err?| completion)
{
  ftpClient.createDir(dir.name) |err|
  {
    ftpClient.moveToDir(dir.name) |err2|
    {
      |File[], |Err?||? recursiveUploadFiles := null;
      recursiveUploadFiles = |File[] remainingFiles, |Err?| completion2|
      {
        if (remainingFiles.size > 0)
        {
          ftpClient.uploadFile(remainingFiles.first) |err3|
          {
            recursiveUploadFiles(remainingFiles[1..-1], completion2)
          }
        }
        else
          completion2(null)
      }
      recursiveUploadFile(files) |err4|
      {
        ftpClient.countFile(completion)
      }
    }
  }
}

Now, if we compare the synchronous and the asynchronous versions, it's easy to see async code can get really ugly and hard to write/read even with a really simple example. In particular, here is a list of the main problems:

  • At each asynchronous operation, we need to add an extra level of nesting
  • Every control-flow statements (for, while...) has to be rewritten in a recursive way. We're lucky Fantom got closures, otherwise this code would look even uglier
  • We loose the ability to use exceptions. I haven't written error-checks in the previous code, as they add even more noise
  • We loose the ability to use return values. As the result is not available when the methods returns, we need to pass the result to the completion handler

Proposed solution

Actually, we already have the perfect tool in Fantom to express the asynchrony. Any methods that returns a Future is per se an asynchronous method. The only thing we're really missing is some way to get notified when the result of a Future is available. To solve this, we would need to add a completion-handler to the Future class that will be called when the result or error is set to the Future. It's probably also important to call this completion handler in an async way in the thread that has instanciated the Future object.

So now that we can know when a Future is set, let's introduce the async and await keywords. These keywords will be available in C# 5.0 and I really think they are the right way to tackle asynchrony. Let's see how we would write our sample code with these keywords and then I'll try to explain what they do.

async Int uploadDir(File dir)
{
  try
  {
    await ftpClient.createDir(dir.name)
    await ftpClient.moveToDir(dir.name)
    dir.listFiles.each |file|
    {
      await ftpClient.uploadFile(file)
    }
    return await ftpClient.countFiles
  }
  catch (Err err) { echo("Upload has failed"); throw err }
}

As we can see, the new code looks a lot like the synchronous/blocking code except the method is now preceeded by the async keyword and each asynchronous call is now preceeded by await.

  • The async keyword just declares that the method is asynchronous and that its return type is actually not an Int but a Future<Int> (in a world where Fantom has generics).
  • The await keyword can only be used in conjunction with a method returning a Future. The compiler will basically rewrite the code this way: it creates a closure with the code "that remains to be executed", pass it to the completion handler of the Future, and call Future.get as the first instruction of the completion handler.

Let's look how this works on a simpler example.

class FtpClient
{
  Future<> moveToDir(Str dirName) { ... }
  Future<Int> countFiles() { ... }
}

async Int countFilesInRemoteDir(File dir)
{
  await ftpClient.moveToDir(dir.name)
  return await ftpClient.countFiles
}

This will get rewritten by the compiler the following way:

Future<Int> countFilesInRemoteDir(File dir)
{
  Future<Int> result

  moveResult := ftpClient.moveToDir(dir.name)
  moveResult.completion = |->|
  {
    try
    {
      moveResult.get
      countResult := ftpClient.countFiles
      countResult.completion = |->|
      {
        try
        {
          count := countResult.get
          result.set(count) // return gets translated into result.set()
        }
        catch (Err err) { result.setError(err) }
      }
    }
    catch (Err err) { result.setError(err) }
  }

  return result
}

The biggest challenge to overcome to do previous transform is to find a way to determine what is the "remaining code to execute" when we encounter the await keyword. I have an idea how to do that, even with control-flow statements, but I just haven't thought yet how it will work with exception handling.

A year ago, I have implemented an analog solution for asynchronous I/O in a big C++ project for my day-to-day work (using coroutines instead of compiler rewritting as I obviously can't do that in C++) and I have found that the await solution works really well in conjunction with Futures. I really hope to see this feature coming in Fantom as asynchrony is quite important these days (for UI code but even server code, just look at node.js), and doing it by hand is just too error-prone/difficult.

On a side note, this topic was about handling asynchronous results (i.e. we wait for a result that we have requested), but there is another aspect of asynchrony that haven't been discussed here: handling unexpected asynchronous events (such as, "A contact sent you a message", "a button has been clicked by the user"). I think this is already correctly tackled by fwt::EventListeners. But when Fantom gets generics, It'll probably be worth it to move this into an async pod (as the async::Signal class) along with event-loops, async I/O classes. But this is a entire different subject.

kaushik Sat 12 Nov 2011

I'd like to work with you on this one. Unless brian and andy already have something in their mind regarding the async and await keywords, we could actually prototype it and see how far we go before these are implemented as keywords.

Just out of my head, to get started, lets say we had a method

Obj? await(Future f)

which can be used instead of the keyword, the problem could be reduced,without keywords, to

Future uploadDir(File dir){
    execute{
        File file := await(ftpclient.uploadFile(dir))
        return result(file)
    }
    onErr|Err e|{
        throw e
    }
}

and we did some load-time fcode manipulation to find calls to "await" and convert it to something like you mentioned(without this fcode manipulation await will fall back being blocking). Exception handlers can be simple closures like above. I'd like to hear more about your ideas on "remaining code to execute" and how would code inside something like "ftpclient.uploadFile" itself would be written. In many cases threads cannot be avoided, but as long as we have ways to free up the current thread, all the tasks could happily execute as multiple events given a thread pool(and actors already provide a great way to do that).

DanielFath Sat 12 Nov 2011

I like the idea of async and await but dislike it as a keyword. I think brian is right. Concurrency should be untangled from the language. Could you use Facets as some sort of "fake" keywords?

Also it you seem mighty certain Fantom will get generics. Basing your framework on that assumption is rather risky.

MoOm Sat 12 Nov 2011

About the keyword thing, I agree with both of you that it would be better if async and await could be isolated from the core language. But compiler support is really needed here. I would really like these transformations to happen at compile-time rather than at load-time as it would allow things like jstub or LLVM compiler to work with this. Which makes me think it might be good to have some way to define pod-specific keywords that would act as compiler plugins.

@kaushik I'd be glad to work with you on that! :)

I'd like to hear more about your ideas on "remaining code to execute"

Ok. Let's take a simplified version of our previous example and let's look at how it is currently translated to FCode. I have voluntarily removed closures to simplify and I have removed exception handling as I haven't thought about that yet:

Void uploadFilesFromDir(File dir)
{
  files := dir.listFiles
  for (i := 0; i < files.size; i++)
  {
    /*await*/ ftpClient.uploadFile(files[i])
  }
}

gets translated to:

uploadFilesFromDir (sys::File dir) -> sys::Void [public]
  [Param 1] dir -> sys::File
  [Local 2] files -> sys::File[]
  [Local 3] i -> sys::Int
  [Code]
    0: LoadVar             1
    3: CallVirtual         sys::File.listFiles() -> sys::File[]
    6: StoreVar            2
    9: LoadInt             0
   12: StoreVar            3
   15: LoadVar             3
   18: LoadVar             2
   21: CallVirtual         sys::List.size() -> sys::Int
   24: CmpLT               sys::Int <=> sys::Int
   29: JumpFalse           67
   32: LoadVar             0
   35: LoadInstance        script::AsyncTest.ftpClient -> script::FtpClient
   38: LoadVar             2
   41: LoadVar             3
   44: CallVirtual         sys::List.get(sys::Int) -> sys::Obj
   47: Coerce              sys::Obj => sys::File
   52: CallVirtual         script::FtpClient.uploadFile(sys::File) -> sys::Void   // await keyword is here
   55: LoadVar             3
   58: CallVirtual         sys::Int.increment() -> sys::Int
   61: StoreVar            3
   64: Jump                15
   67: Return
  [LineNumber] size=2
     27

The obvious answer to get the "code remaining to execute" (I think its called a "continuation" so I call it like this from now) is: "at least, the code that is after the await keyword". But potentially, there is more as we could be nested inside a control-flow statement (like in this case, with the for loop). So we need to consider all the jumping opcodes in this section of the code:

55: LoadVar             3
58: CallVirtual         sys::Int.increment() -> sys::Int
61: StoreVar            3
64: Jump                15
67: Return

There's only one jumping opcode that jumps to opcode at offset 15, so the continuation needs to include code from at least opcode 15:

15: LoadVar             3
18: LoadVar             2
21: CallVirtual         sys::List.size() -> sys::Int
24: CmpLT               sys::Int <=> sys::Int
29: JumpFalse           67
32: LoadVar             0
35: LoadInstance        script::AsyncTest.ftpClient -> script::FtpClient
38: LoadVar             2
41: LoadVar             3
44: CallVirtual         sys::List.get(sys::Int) -> sys::Obj
47: Coerce              sys::Obj => sys::File
52: CallVirtual         script::FtpClient.uploadFile(sys::File) -> sys::Void   // await keyword is here
55: LoadVar             3
58: CallVirtual         sys::Int.increment() -> sys::Int
61: StoreVar            3
64: Jump                15
67: Return

Again, let's look at the new jump opcodes: there is one (JumpFalse 67) but the continuation already include this code, so we're fine. So now, we just need to create a "closure" from this (meaning capturing used local variables and isolate this code into its own object). Once we have done that, the last thing we need to do is to add a "Jump" opcode at the start of the continuation to jump directly at the instruction just after the await keyword, so when the completion-handler gets called, we'll resume directly where we have left. The code of our continuation would then look like this (I haven't done the local variable capturing part as it is quite difficult to do this manually... :)):

0:  Jump                43
3:  LoadVar             3
6:  LoadVar             2
9:  CallVirtual         sys::List.size() -> sys::Int
12: CmpLT               sys::Int <=> sys::Int
17: JumpFalse           55
20: LoadVar             0
23: LoadInstance        script::AsyncTest.ftpClient -> script::FtpClient
26: LoadVar             2
29: LoadVar             3
32: CallVirtual         sys::List.get(sys::Int) -> sys::Obj
35: Coerce              sys::Obj => sys::File
40: CallVirtual         script::FtpClient.uploadFile(sys::File) -> sys::Void   // await keyword was here
43: LoadVar             3
46: CallVirtual         sys::Int.increment() -> sys::Int
49: StoreVar            3
52: Jump                3
55: Return

I'm not sure I'm clear in my explanation so don't hesitate to ask questions :). I think it plays well with closures. I'm not sure how well it works with exception-handling though.

and how would code inside something like "ftpclient.uploadFile" itself would be written.

I see two ways of writing this:

  • First way: you have an existing synchronous FtpClient class. You can easily make an AsyncFtpClient by using an actor:
    class AsyncFtpClient
    {
      Future uploadFile(File file)
      {
        actor := Actor(pool) |File f| { ftpClient.uploadFile(f) }
        return actor.send(file)
      }
      const FtpClient ftpClient
    }
  • Second way: you use an async network API (async sockets with select/poll) and you write your own implementation of the FTP protocol with this API. This is the preferred way as it does not require any thread here (this is the node.js way).

brian Sun 13 Nov 2011

@MoOm,

Thanks for taking the time to post such a thorough write-up. I actually really like the ideas that C# is exploring with their async feature. We actually experience this issue ourselves quite a bit when we use Fantom to write JS/browser code - because if you make 4 async XHRs, then you have to write four levels of nested closures.

My main issue as Daniel mentioned, that a while back we made a big decision to move concurrency out of the core into a separate library. I think this was the right move because it lets us add new concurrency techniques through library additions without locking Fantom into a single model.

We do indeed have the ability to register compiler plug-ins to perform compile time transformations via DSLs. Perhaps the DSL feature as it stands today isn't quite right, but maybe we just need to find the right tweak to that?

I don't think I'm quite ready to tackle something this huge as part of the core. But if you and kaushik want to start prototyping this with a library I think that is the best way to get experience with a new paradigm, and then eventually merge it back into the core. And if you think tweaks to DSL might be the solution, then lets definitely explore that.

kaushik Tue 15 Nov 2011

@MoOm, Can you give me your mail address? or just mail me [email protected]

MoOm Tue 15 Nov 2011

@brian I took a look at DSLs and I think it is maybe too limited to achieve what I'd like to have. As far as I understand, DSLs seem to allow replacing (inplace in the AST) a text by an expression built by the DslPlugin. For async methods, I think we probably need to apply more complex transforms on the AST in order to make this work. Maybe a better way would be to be able to plug custom compiler steps. But for now, I'll just try to patch the compiler and then we can think of a way to make this work as a plug-in.

@kaushik You can contact me at [email protected] . I've rethought about this and I think I've made some mistakes in the previous posts:

  • await cannot be used in the List#each closure as a closure is a normal function, not an async method
  • The transforms should not be applied on the FCode as the Javascript target does not use the FCode representation

To make this work, we probably should take another approach. A solution would be to transform async methods into a state-machine where each state is determined by the await keywords (and local variables used in several states are transformed into fields).

To illustrate this, let's take our previous following example (comments are in the code):

class FtpUtil
{
  async Int uploadDir(Dir dir)
  {
    await ftpClient.makeDir(dir)
    await ftpClient.moveToDir(dir)

    files := dir.listFiles
    for (Int i := 0; i < files.size; i++)  // Note the for loop instead of the 'each' closure
                                           // as 'await' in closures cannot work
    {
      echo("Uploading file ${files[i]}...")
      await ftpClient.uploadFile(files[i])
    }

    return await ftpClient.countFiles
  }
}

will get transformed to (comments are in the code):

// Each async method generates a class (like closures)
class async_FtpUtil_uploadDir
{
  private Future result
  private Future awaitResult

  // One state per level of nesting
  private Int state0
  private Int state1

  // Local variables that are used by several states are now fields
  private Dir _dir
  private File[] _files
  private Int _i

  new make(Dir dir)
  {
    state0 = 0
    state1 = 0
    result = Future()
    _dir = dir
  }

  Future run()
  {
    try
    {
      doRun
    }
    catch (Err e)
    {
      result.setError(e)
    }
    return result
  }

  private Void doRun()
  {
    if (state0 == 0)
    {
      // await get translated to this. We'll resume "doRun" when we get the result
      // or the error. state0 is incrementd so this step is never executed again.
      awaitResult = ftpClient.makeDir(_dir)
      awaitResult.completion = { doRun }
      state0 = 1
      return
    }
    if (state0 == 1)
    {
      awaitResult.get   // awaitResult.get is important even if we don't use
                        // the result in order to throw the error if any
      awaitResult = ftpClient.moveToDir(_dir)
      awaitResult.completion = { doRun }
      state0 = 2
      return
    }
    if (state0 == 2)
    {
      awaitResult.get
      _files = _dir.listFiles
      _i = 0            // The initialisation of the for-loop has to be moved before
                        // the for loop, otherwise it'll be executed at each resume.
      state0 = 3
    }
    if (state0 == 3)
    {
      for (; _i < _files.size; _i++)
      {
        if (state1 == 0)   // We now use "state1" are we are now in level 1 of nesting
        {
          echo("Uploading file ${_files[_i]}...")
          awaitResult = ftpClient.uploadFile(_files[_i])
          awaitResult.completion = { doRun }
          state1 = 1
          return
        }
        if (state1 == 1)
        {
          awaitResult.get
          state1 = 0     // States need to loop
        }
      }
      state0 = 4
    }
    if (state0 == 4)
    {
      awaitResult = ftpClient.countFiles
      awaitResult.completion = { doRun }
      state0 = 5
      return
    }
    if (state0 == 5)
    {
      result.set(awaitResult.get)  // return gets translated to result.set
      state0 = 0
    }
  }
}

I don't see cases where this kind of transform does not work. It is compatible with Javascript and plays well with exception-handling I guess. What are your opinion about this?

kaushik Wed 16 Nov 2011

@MoOm, Yes, I was aware of these problems. Just thinking aloud, before we get into the implementation, the way c# does it, seems to have a few problems :

  1. With closures, things are clear on what executes in parallel and what executes in sequence. if something is defined inside a closure, it happens after the first thing happens, if it's not it "can" get executed in parallel. With await everything happens one at a time.
  2. Sometimes, especially when you are doing something that requires evented execution, you might actually want to return multiple values. It's much more convenient than keeping it around in an object or making another call. With the way await is done in C# it can return only one value.
  3. Closures are actually useful and more clean in some cases. For eg., when it needs to be called multiple times. I'd really want to mix and match closures and "await". in C# async methods are allowed to return values. that means it can be used only in conjunction with "async" keywords

I was just exploring alternative syntaxes. For eg., Let's say await was a "block-like" instead of a "function-like". We could do something like:

async Void isObamaPresident(Str id){
  await getCurrentYearFromDatabase =>  Int year
  await{
    Db.one("select id, name from user where id = $id") => Int id, Str name
    Webclient.fetch("www.whitehouse.gov") => Str html  //executes in parallel
  }

  if(year <= 2011)
    response.write("Hmm...")
  else if(html.index("obama") != null)
    response.write("Yes $name obama is still the president")
  else
    response.write("No $name obama is not president anymore")
}
  • An async method cannot return anything.
  • An await block can contain one or multiple statements. If it has multiple statements each can run in parallel
  • The results of await can be captured using "=>", into variables declared right there or variable declare above the await block. Though declaring variable in the end might seems a little odd to begin with, if you think about it, it's exactly the place where you would define them with closures.
  • Anything after await is guaranteed to execute after the await block-
  • Execeptions can be handled using try,catch

On the implementation part, I'd really love if the implementation would be simple enough to be rewritten within the method itself as closures. for ex. a while loop if contains the "await" keyword be changed form:

while(a > 2){
  await Webclient.fetch("www.whitehouse.gov")
  a++
}

to..

exec.awhile({a>2}){
  await Webclient.fetch("www.whitehouse.gov")
  a++
}

The method "awhile" takes care of while loop logic, but in an evented fashion.

MoOm Wed 16 Nov 2011

With closures, things are clear on what executes in parallel and what executes in sequence. if something is defined inside a closure, it happens after the first thing happens, if it's not it "can" get executed in parallel. With await everything happens one at a time.

Actually, you can do parallel execution with the async keyword. Here is how you would write your example if you want the SQL request and the web-fetch to be executed in parallel:

Future dbResult := Db.one("select id, name from user where id = $id")
Future fetchResult := WebClient.fetch("www.whitehouse.gov")
await dbResult; await fetchResult

You just need to call each async method before the await keyword and then those methods will execute in parallel. It also allows you to execute all the iteration of a loop in parallel (say I want to upload all files on the FTP server in parallel in my previous example).

Sometimes, especially when you are doing something that requires evented execution, you might actually want to return multiple values. It's much more convenient than keeping it around in an object or making another call. With the way await is done in C# it can return only one value.

I am not sure this problem is specific to async methods. Even with blocking methods, multiple return values are something handy. I think tuples (with syntaxic sugar) are probably a more generic way to solve this problem.

Closures are actually useful and more clean in some cases. For eg., when it needs to be called multiple times. I'd really want to mix and match closures and "await". in C# async methods are allowed to return values. that means it can be used only in conjunction with "async" keywords

I'm not sure I get what you mean here. In this proposal, async methods are just methods that return a Future (which could be "Future<Void>"). await is just a way to get the result of the Future without blocking the thread. But you could call async methods without using await if you want (if you don't care about the result and the completion of the process). You can also call the method and then call Future.get() in another thread, but then it'll block the thread.

MoOm Wed 16 Nov 2011

Here is an interesting article about how C# 5 implements it under the hood: link. They use the state-machine approach, with the help of goto (which we cannot use as Javascript does not have it I far as I know).

kaushik Wed 16 Nov 2011

aw Thanks. Got the thing about parallel execution.

I am not sure this problem is specific to async methods. Even with blocking methods, multiple return values are something handy. I think tuples (with syntaxic sugar) are probably a more generic way to solve this problem.

When i needed multiple return values in the past, I had modeled them using closures and thought - Aw.Neat.Closures! Now we are trying to device ways to circumvent them :). But seriously, dosen't that problem double up when you are talking about evented methods that potentially does something blocking and another call would mean another request or creating-encasing classes for the heck of it (given that we don't have closures to model multiple-return values)?

I'm not sure I get what you mean here

Actually what i was exploring was something very simple. Is there a possibility to just convert "await" calls to simple closures instead of "goto"s or "state-machines" . I'vent really given too much thought and I might be totally wrong here or naive, but just bringing in the possibilities for us to think out. I'll leave it up to to decide which one's right depending on pros and cons.

Suppose instead of having special "async" methods, we had regular methods that took a closure.

Void findLastEmployee(Int mgrId, |Employee? lastEmployee, Err? e| completion){
}

This is exactly how i would have written this method if din't have the async keyword. now this method can be called by anyone with or without "await" keywords.

  1. Without await :
    Void caller(){
      findLastEmployee(1)|Employee last|{
       echo("last employee is $last")
      }
    }
  2. or with await:
    Void caller(){
      await findLastEmployee(1) => Employee last
      echo("last employee is $last")
    }    

you see (2) was just a syntax sugar for (1) or something like it. Ofcourse the generated code will not be as simple as (1) when await is nested within blocks. for eg.,

Void caller(){
   if(true){
    await findLastEmployee(1) => |Employee last|	   
   }	 
   echo("done")
}

will have to be compiled to something like :

Void caller(){
  e := Eventor()
  e.next{
      Employee? last 
      if(true){
        findLastEmployee(1)|Employee _last, Err? e|{
          if(e != null) throw e
          this.last = _last
        }
      }
      e.next{
        echo("last employee is $last")
      }
  }
}

A little more complex, but generated code is understandable and still valid fantom.

Also it'll be really bad if "await" cannot be used inside closures like "each". For eg.,

files.each|File f, Int count|{
   await uploadFile(f) => Bool success
   echo("Successfully uploaded file $count")
}

might get converted to:

files.each|File f, Int count|{
   Bool? success
   event.next{
     uploadFile(f)|Bool _success|{success = _success}
   }
   event.next{
     echo("Successfully uploaded file $count")
   }
}

closures actually copy the value of local variable and it will work as-supposed in this case. But I've a feeling you can come up with other cases where it will not work. Just thinking aloud.

brian Wed 16 Nov 2011

The main issue is that you really need continuations to snapshot the runtime state of the call stack. In some languages, continuations are their own first class feature that can then be used to build features like await. I sort of like that approach (in which case then maybe only continuations get built into compiler) and await can just be a library feature. Being able to snapshot the call stack and then pass it around and restore it leads to all sorts of awesome abilities (depending on how far you take it).

Continuations in JVM are messy, although I've seen projects that have gotten them to work using bytecode post-processing and techniques like that.

I haven't seen much on how to build continuations in JavaScript.

Although I do love the idea, I think this strategy may be overreaching without continuations support built into the JVM and JS platforms.

MoOm Wed 16 Nov 2011

@kaushik

When i needed multiple return values in the past, I had modeled them using closures and thought - Aw.Neat.Closures! Now we are trying to device ways to circumvent them :). But seriously, dosen't that problem double up when you are talking about evented methods that potentially does something blocking and another call would mean another request or creating-encasing classes for the heck of it (given that we don't have closures to model multiple-return values)?

I'm sorry but I still don't see how closures can help with the multiple return-values problem. You mentionned the "=>" operator. How will this work? Have you already implemented something similar in Tales or Fanbatis?

Void findLastEmployee(Int mgrId, |Employee? lastEmployee, Err? e| completion)

A completion handler and a returned Future are almost the same thing. They both transmit a result or an error and they can notify when this result/error is available. So this method can be rewritten as:

Future findLastEmployee(Int mgrId)
// or Future<Employee> findLastEmployee(Int mgrId) (if Fantom gets generics)

The advantage of the future approach is that it requires almost no changes if you transform sync code to async code:

  • The result of the operation (here the last employee) is still returned by the method
  • Getting the result will throw an exception if there was an error so error-handling does not need to be modified. With the completion-handler, you need to check the error in each completion-closure.

Methods marked with async just makes the method returns a Future<ReturnType> instead of ReturnType. But you can still call this method without having to use the await keyword. Here is an example:

class FtpClient
{
  async Int countFiles() { ... }
  // or
  Future countFiles() // if you want to implement this manually or with an actore for example
}

ftpClient.countFiles.onCompletion = |result| { echo("There are ${result.get} files) } 
// or
echo("There are ${await ftpClient.countFiles} files") // if you are in an async method

About your Eventor idea, let me think about it and I'll get back to you! :)

MoOm Wed 16 Nov 2011

@brian

Continuation/coroutines are indeed another way to solve this problem. This is the way I've implemented it in a C++ project. I see two main problems with coroutines though:

  • They need their own stack to store the local variables so it'll consume more memory than state-machines (which might become a problem if you want to build a high-performance server using asynchronous I/O)
  • As you said, they probably cannot be implemented in a portable way on the JVM or in Javascript (don't know about CLR). I found this paper some times ago about how to implement coroutines but this does not sound portable.

On the plus side, as they have their own stack, coroutines would also allow to call await in sub-methods (and thus in closures), which is not possible with the state-machine solution. But apart from this, state-machine might already serves as a poor-man coroutines (i.e. multiple return-point, and resuming where the last call returned).

kaushik Thu 17 Nov 2011

I'm sorry but I still don't see how closures can help with the multiple return-values problem.

Well, say a method needs to return two values:

one way:

NameandId getNameAndId(){ 
    n := NameandId()
    n.name = "test"
    n.id = 3
    return n
}

or another way

Void getNameAndId(|Str name, Int id| code){
    code("test", 3)
}

You mentionned the "=>" operator. How will this work?

I was thinking => is just a simple way to copy the results of a closure into a local variables, especially useful when the closure is called just once (being used for returning something).

In the above example

getNameAndId => Str name, Int id

would just be a shortcut for

Str? name; Int? id
getNameAndId|Str _name, Int _id|{ 
    name = _name 
    id = _id
}  

When clubbed with await, maybe all we need to do is move everything that can get executed after "await" into it's own closure (yeild). So..

await getNameAndId => Str name, Int id
echo(id)

would be converted to

event.next{ 
    Str? name; Int? id
    getNameAndId|Str _name, Int _id|{  
      name = _name 
      id = _id
    }
    event.next{
       echo(id)
    }
}

or something like it. There might be some hairy cases(I havent really thought too much) but as long as everyone understands what's happening, if await could simply be reduced to : "make-my-closures", and I can resort to closures whenever I want that'll be cool.

This implementation does not require us to go deep into the compiler, and can be implemented at the source level or just after tokenizing.

if Fantom gets generics

Well i don't know :). Generics does add a lot of complexity. May be cajole brian into a Just for "Future" class ? :)

Edit: Remove some unwanted code

MoOm Thu 17 Nov 2011

@kaushik Ok, I now see what you mean with closures helping with multiple return-values. But I'm not sure we should introduce a new operator (=>) and a new way to return values (by calling a closure) just because we return several values instead of just one. I think tuples are more elegant in this regard:

(Str, Id) getNameAndId()
{
  return "John", Id(12345)
}
name, id := getNameAndId

which would just be syntaxic sugar for:

Tuple getNameAndId()
{
  return Tuple("John", Id(12345))
}
res := getNameAndId
Str name := res.first
Id id := res.second

When using closures to return values, you no longer know, just by looking at the prototype of the method, if the closure will be used as an helper function (as in List#each or List#sort) or to return results. Return types are dedicated for this usage, I think we should stick with them as much as possible.

But I really think multiple return-values is a different subject, I'm not sure we should focus on this right now.

When clubbed with await, maybe all we need to do is move everything that can get executed after "await" into it's own closure (yeild)

Well that's the hard part. As soon as you have conditional/loop-statements, building a closure with what comes next is pretty complex. Especially since we don't have goto in Javascript, so we can't rely on this to resume where we left. Try converting a more complex example (with just a for loop containing several instructions), and you'll see it'll probably look really hairy :)

kaushik Fri 18 Nov 2011

Well that's the hard part. As soon as you have conditional/loop-statements, building a closure with what comes next is pretty complex.

here's a for loop

for(Int i := 0; i<10; i++){
  await getIdAndName => Int id, Str name
  echo("done for $i")
}
if(true){
  echo("done everything")
}

converted to

Int i:= 0
event.asyncfor({i<10}, {i++}, {
    event.next{
       Int id, Str name
       getIdAndName|Int _id, Str _name|{id = _id; name = _name}
       event.next{
           echo(i)
       }
    }
})
event.next{
    if(true){ 
      echo("done everything")
    }
}

like wise for while. You go up from await and everything after a end of block from there will be inside event.next. What i was thinking is, closures already does all the hard part of copying the local variables to instance variables and stuff, should we really have to repeat all that?

Can you mail me a few more examples, we can try to work out if it gets really scary or not.

MoOm Sun 20 Nov 2011

@kaushik Your solution seems to work well! I think it can handle all the cases. Here is a list of pros and cons I see:

Pros:

  • await can be used inside a closure, which is not possible out-of-the-box with the state-machine approach
  • Produced code is more readable than state-machine code (not sure we actually care about that)

Cons:

  • Adds a lot of closures. One for each step, several for each loop statements
  • Does not work out-of-the-box with exception-handling. If there were any try/catch in the initial code, it should be repeated inside each event.next block

.

What i was thinking is, closures already does all the hard part of copying the local variables to instance variables and stuff, should we really have to repeat all that?

I'm not sure this is as hard as with closure. All we have to do is detecting which local vars are used by several async steps and move them to fields (since one async method generates one class).

kaushik Tue 22 Nov 2011

Adds a lot of closures. One for each step, several for each loop statements

You'll add closures and/or rework loops only when it contains an "await". Otherwise you leave it as is.. for eg.,

if(true){
   await..
}
if(..)
for(...)
while(...)
... and thousands of other lines

gets converted to

if(true){
   await..
}
event.next{ //all the statement inside this does not need any reworking
  if(..)
  for(...)
  while(...)
  ... and thousands of other lines
}

Does not work out-of-the-box with exception-handling. If there were any try/catch in the initial code, it should be repeated inside each event.next block

Instead of repeating, if you change try/catch into closures themselves. For eg.,

asyncTry{
   ....
}
asyncCatch|Err e|{e.trace}

This has to be done, as with point (1), only when the try/catch contains "await". Otherwise it's fine to leave it as is.

I'm not sure this is as hard as with closure. All we have to do is detecting which local vars are used by several async steps and move them to fields (since one async method generates one class)

If this is as simple to implement I am cool with this too. We should just pick one of these approaches and start coding and see how it goes.

I suggested that the closures approach would be simple because

  • Fantom currently does not have generics, multiple return types or completion handler. So with closures approach we could start "now"
  • Also, we don't really know, if await would actually make it to fantom core. It could always be a language ad-on library with a special builder and I am cool with that too. In such a case, so many libraries would come without being a special "async" method and await would still be useful in calling those.
  • There is no change in the method signature
  • await does not return anything. This would simplify the finding of the "next" code to execute(in early-on compilation steps)
  • If we did go with the state-machine approach, we absolutely must find a way to use it inside a closure, even if it's a bit of a hack. When writing fantom I always find writing code inside a closure. for eg., an "each{}" block or fanbatis's "transaction{}"
  • It's clear how the generated code will look like - mostly similar to how it would have looked with if I din't use await.

If local vars are moved to instance vars how will it affect debugging and stuff? Do we have to do more work for supporting that?

MoOm Wed 23 Nov 2011

@kaushik Thanks for this good summary! Let me try to answer some of your points.

Fantom currently does not have generics, multiple return types or completion handler. So with closures approach we could start "now"

We still need to implement the => operator you mentionned. Without it, it'll be lot less nice for the user to use the closure-approach. So if we're going to need to modify the compiler to implement this, we might as well implement tuples and make all methods benefit from this! :)

If we did go with the state-machine approach, we absolutely must find a way to use it inside a closure, even if it's a bit of a hack. When writing fantom I always find writing code inside a closure. for eg., an "each{}" block or fanbatis's "transaction{}"

Yes, I agree this is an important problem to solve. I haven't given a lot of thought about this yet. I'll try to see if I can come up with something to solve this.

Also, we don't really know, if await would actually make it to fantom core. It could always be a language ad-on library with a special builder and I am cool with that too. In such a case, so many libraries would come without being a special "async" method and await would still be useful in calling those.

Yes, that's a good point. If we have no way to apply modifications on the AST, the state-machine approach is a no go. Here is what I would propose to be able to do this with minimal modifications to the compiler (pluggable compiler-steps).

// This would be defined in the compiler pod.
// When compiler will encounter this facet, it'll add the
// returned compiler-step into the compiling-process.
// Where exactly the step is inserted remains to be defined. To make this more generic, we probably need several entry-points.
// This is the only modification required on the compiler-side
abstract facet class CompilerStepPlugin
{
  abstract CompilerStep compilerStep()
}


// This would be implemented in the async pod
facet class Async : CompilerStepPlugin
{
  override CompilerStep compilerStep() { return AsyncCompilerStep() }
}

class AsyncCompilerStep : CompilerStep
{
  override Void run()
  {
    ..
  }
}

// The code using the async pod could be written like this
@Async Int uploadDir(File dir)
{
  try
  {
    await(ftpClient.createDir(dir.name))
    await(ftpClient.moveToDir(dir.name))
    dir.listFiles.each |file|
    {
      await(ftpClient.uploadFile(file))
    }
    return await(ftpClient.countFiles)
  }
  catch (Err err) { echo("Upload has failed"); throw err }
}

@brian What is your opinion about this? Do you think that it is something that could be added to the compiler?

brian Fri 25 Nov 2011

@brian What is your opinion about this? Do you think that it is something that could be added to the compiler?

One thing I really want to avoid is "branches" of the Fantom language which aren't immediately readable as such. What I like about DSLs is that there is a clear anchor type to see what DSL type you might be missing to compile certain code. It doesn't always have to be a DSL anchor type, but I want to keep some readable mechanism so that it is clear a pod depends on some compiler plugin.

dobesv Tue 28 Feb 2012

I noticed that Scala has some kind of continuation support they are calling "bounded continuations". See http://www.scala-lang.org/node/2096 and the paper linked at the end.

The basic idea is that you define some block of code that is "continuable" which they do using the "reset" block. This piece of code triggers some kind of altered state such that future calls to the continuation (by calling shift()) will call the part of the code coming after the currently executing part of the call stack in the original reset() block. "shift" is a method that takes a closure and passes the current continuation to it as a parameter, so upon calling that continuation you're invoking the remainder of the code in the reset block. The reset block itself appears to be waiting synchronously for all the code within, although perhaps if the shift() method stores the given contuation for later use then I can't tell whether the original caller of the reset block would wait or return Nothing.

So that means the transformation is to take each line of code in the "reset" block that may have a "shift" call in it and change it so that the rest of the code in the reset block is passed as a hidden parameter (a closure).

Any methods that will call "shift" to "return" values asynchronously are annotated so that the compiler knows whether to split the method up and pass the hidden parameter.

Anyway, something for reference. I hope it helps!

I'd love to see generators and continuations available in Fantom but after a bit of reading I see that the JVM and Javascript implementations of that can be somewhat hairy.

dobesv Tue 28 Feb 2012

In Python I believe what they do is that if a function body has the yield keyword in it then it swithes modes so that the function returns a type of object (a generator) instead of a regular function.

When yield is called in the generator body, all the local variables and other state necessary to continue running are saved. When the next "value" is requested from the generator it resumes the executing code after the last yield statement that was run.

I think the trick here is in "splitting" the function body up into parts if there are conditionals and loops involved.

For example, if you were in the middle of a loop calling yield (and you typically are), you need a way to jump back into the middle of that loop.

Since Python has its own runtime I expect that it can simply "restore" the "function pointer" along with the other local variables and carry on its merry way.

In Fantom's case this isn't an option so a fairly dramatic transformation of loops that contain yield would be needed.

dobesv Wed 29 Feb 2012

Here a couple of libraries that are apparently achieving this goal using bytecode instrumentation:

http://commons.apache.org/sandbox/javaflow/tutorial.html http://code.google.com/p/coroutines/

Somehow I think this could even be done at the source level with a source->source transformation, like this:

  1. Coroutine.suspend() is added as an operation to snapshot the current state and return it to the original caller.
  2. Calls to Coroutine.suspend() throw a special exception
  3. Named functions and methods that may call Coroutine.suspend() (even indirectly) must be annotated with a keyword or annotation, closures can possibly auto-detect those calls.
  4. Call sites to functions and methods that may call Coroutine.suspend() are automatically wrapped in a try/catch block which catches the special exception and pushes local state into the exception
  5. All the control flow surrounding a potential call to Coroutine.suspend() has to be modified so that it applies this state information and restores the function pointer to the appropriate location.
  6. When the coroutine is resumed the state that was previously saved is passed in as a hidden parameter of some kind
  7. To start a Coroutine you use Coroutine.start() and pass a function that calls Coroutine.suspend() directly or indirectly, it returns a Continuation object.
  8. A Continuation object is a const snapshot of the state of the Coroutine at the the last call to suspend OR after the Coroutine has returned from the top function passed to start()
  9. Calling resume() on a Continuation object resumes execution where it left off and returns a new Continuation object next time the Coroutine calls Coroutine.suspend()
  10. You could call resume() multiple times on the same Continuation object, probably, at your own risk.

Having this as the baseline creates something that can be easily extended.

For example, a Producer class could be built on top of this that implement Producer.yield(x) which stores x, calls Continuation.suspend(), and returns the stored x as the next generated value.

A Consumer class could be created that has a method Consumer.receive() which calls Coroutine.suspend(). The client of the consumer then regains control and calls consumer.send(x), which stores x and calls Coroutine.resume(). The receive() method then resumes and returns the new value of x.

An AsyncOperation class could be created for asynchronous I/O with a set of asynchronous I/O methods to use. Each operation sets up a notification (using NIO or whatever) to be woken up when the results are ready and calls Coroutine.suspend(). The appropriate I/O subsystem is given a reference to the wrapper and wakes it up when the data is ready.

For an web server that allows asyncronous request processing, it would have to use the above AsyncOperation paradigm.

Seem feasible? Am I repeating what you were already thinking of?

dobesv Thu 1 Mar 2012

OK I realize I'm talking to myself here, but here's another thought. If we have the technology in place needed to do the above model, a more powerful tool also becomes available. Instead of using suspend() we could instead do a Call with current continuation primitive that is a more powerful building block for coroutines, generators, and asynchronous processing.

Basically you could do Continuation.callWithCurrent(someFunc) and it will snapshot and exit the current execution of the coroutine you are in and then call someFunc, passing a function that, when called, resumes the coroutine at the return from callWithCurrent.

Unlike the call-with-current-continuation feature in Scheme and similar, the continuation given will be limited in scope, it won't include the entire call stack but only the part with the right annotation or whatever.

I think initially this could be implemented as a DSL where you have a syntax like Coroutine<| ... Fantom code ...|>. The DSL would use the built-in Fantom parser but then we would post-process the code to support suspending and creating a continuation function that continues execution.

brian Thu 1 Mar 2012

OK I realize I'm talking to myself here, but here's another thought. I

Its great to capture all these ideas!

Just to restatement my thoughts - I think the idea of continuations is too complicated and immature to add directly into the core. I almost think it would be simpler to port Fantom to a VM that supported continuations :-) But I would like to support whoever wants to work on this effort. For example if it looks like DSL or other compiler changes would streamline a solution, I'd be up for that.

dobesv Sat 3 Mar 2012

Also for reference, I found there's a Javascript-to-Javascript compiler called Traceur that implements coroutines as a transformation of the source code:

http://code.google.com/p/traceur-compiler/wiki/LanguageFeatures#Deferred_Functions http://code.google.com/p/traceur-compiler/source/browse/src/codegeneration/generator/CPSTransformer.js

So, we're not totally trailblazing with the idea of doing the coroutine as a transform on the AST to run on a stack-based VM.

dobesv Tue 20 Mar 2012

I recently ran into a blog post where someone suggested he was able transform JVM bytecode and replace synchronous versions of I/O calls with an asynchronous version that releases the thread's resources while waiting for the I/O.

This is perfect for the server-side use case where asynchronous processing is being done to conserve resources associated with threads rather than any serious desire to do anything asynchronous.

I think it should also be possible to transform Fantom code to operate in a similar manner - certain normally synchronous calls may release control of the current thread while they wait for I/O to complete. Possible approach:

  1. Methods with a certain annotation/facet are copied to a new secret version of the method and transformed to support release/resume of the thread
  2. Calls from one of these methods to another call the alternative version of the method
  3. Calls from one of these methods to a non-instrumented method use the normal stack and suspension is not possible during that invocation
  4. Calls that wait - I/O, Socket connect/accept, Future.get, Actor.sleep are replaced in the instrumented code with versions that release the thread and use Asynchronous I/O, then wake it back up when the operation completes
  5. Add a way for "regular" to call the alternative version of a method to "enter" a "suspendable" call stack

Because of the requirement for an annotation the compiler would be modified to support this and only code compiled with the modified compiler would support being suspended.

Core classes should also be modified/instrumented so that you can suspend inside a List.each operation and things like that.

Be nice if this was less invasive to the Fantom core, though.

dobesv Fri 23 Mar 2012

MoOm:

In the original problem statement the main objection to threads was that you can't have very many of them because of their stack memory usage. Have you tried reducing the stack size using -Xss? How much stack do we really need for a thread?

Login or Signup to reply.