direct-style RPC

66 views
Skip to first unread message

Aaron Novstrup

unread,
May 27, 2011, 2:01:50 AM5/27/11
to scal...@googlegroups.com
Hi all,

I haven't had much time to contribute to the project lately, but I recently got interested in delimited continuations and decided to combine a learning experience with my next contribution.  The result is a step toward allowing GWT developers to write asynchronous callbacks with code that looks like standard synchronous method calls. e.g.

asynchronously {
   val answer = m1.callAsync
   // execution won't go here unless the async call returns successfully
   field.setText(answer)
   tryAsync {
      val nextMessage = m2.callAsync
      displayMessage(nextMessage)
   } catchAsync {
      // this block handles a failure response on the m2 call
      case ex: IllegalStateException => displayMessage("illegal state")
   }
}
// execution continues here immediately after m1.callAsync is invoked

My preliminary code to support this functionality is attached, including tests that demonstrate its use.  I'm hoping that the CPS transform occurs early enough in compilation that similar code will (eventually) work with the Scala-jribble backend. Lex/Grzegorz, do either of you know whether that's the case? The code is pretty much self-contained (it just depends on the continuations plugin and scala-test), so it should be pretty simple to compile and play with.  I faked the RPC to get it working, but it's easy to see how it would work with real GWT RPC.

Enjoy!
~Aaron
AsyncTest.scala
Async.scala

Grzegorz Kossakowski

unread,
May 27, 2011, 3:57:02 AM5/27/11
to scal...@googlegroups.com
2011/5/27 Aaron Novstrup <aaron.n...@gmail.com>:

Hi Aaron,

Nice stuff! This would be one of killer features of Scala+GWT. :-)

I don't know that much about CPS transform (yet) but I'm guessing it
occurs early enough. We can verify that by running scalac on your
example with -Xshow-phases. If you post the output of that option I'll
tell you more.

Also, it's worth mentioning that I got myself back to project lately
after a long period of being busy with my school projects/exams. I'm
trying to find out how far we are from having Showcase running.

Oh, and on 4th of July I'll be moving to Lausanne and joining
Typesafe. I'll be working on Scala+GWT full-time again.

--
Grzegorz Kossakowski

Aaron Novstrup

unread,
May 27, 2011, 9:52:38 AM5/27/11
to scal...@googlegroups.com
On Fri, May 27, 2011 at 12:57 AM, Grzegorz Kossakowski <grzegorz.k...@gmail.com> wrote:
I don't know that much about CPS transform (yet) but I'm guessing it
occurs early enough. We can verify that by running scalac on your
example with -Xshow-phases. If you post the output of that option I'll
tell you more.

CPS is the 10th phase (right after liftcode). That should be well before anything that scala-jribble does.

phase name  id  description
----------  --  -----------
parser   1  parse source into ASTs, perform simple desugaring
namer   2  resolve names, attach symbols to named trees
packageobjects   3  load package objects
typer   4  the meat and potatoes: type the trees
superaccessors   5  add super accessors in traits and nested classes
pickler   6  serialize symbol tables
refchecks   7  reference/override checking, translate nested objects
selectiveanf   8  
liftcode   9  reify trees
selectivecps  10  
uncurry  11  uncurry, translate function values to anonymous classes
tailcalls  12  replace tail calls by jumps
specialize  13  @specialized-driven class and method specialization
explicitouter  14  this refs to outer pointers, translate patterns
erasure  15  erase types, add interfaces for traits
lazyvals  16  allocate bitmaps, translate lazy vals into lazified defs
lambdalift  17  move nested functions to top level
constructors  18  move field definitions into constructors
flatten  19  eliminate inner classes
mixin  20  mixin composition
cleanup  21  platform-specific cleanups, generate reflective calls
icode  22  generate portable intermediate code
inliner  23  optimization: do inlining
closelim  24  optimization: eliminate uncalled closures
dce  25  optimization: eliminate dead code
jvm  26  generate JVM bytecode
terminal  27  The last phase in the compiler chain
  
 
Also, it's worth mentioning that I got myself back to project lately
after a long period of being busy with my school projects/exams. I'm
trying to find out how far we are from having Showcase running.

Excellent!  I noticed (while working on this Async code) that Showcase doesn't exercise GWT RPC.  It may be worthwhile, as an intermediate milestone, to get a small app running that exercises a wider range of GWT's features (Showcase is focused on UI, I think).
 
Oh, and on 4th of July I'll be moving to Lausanne and joining
Typesafe. I'll be working on Scala+GWT full-time again.

That's great news!  I'm a little jealous. :-) 

Grzegorz Kossakowski

unread,
May 27, 2011, 10:14:08 AM5/27/11
to scal...@googlegroups.com
2011/5/27 Aaron Novstrup <aaron.n...@gmail.com>:

> On Fri, May 27, 2011 at 12:57 AM, Grzegorz
> Kossakowski <grzegorz.k...@gmail.com> wrote:
>>
>> I don't know that much about CPS transform (yet) but I'm guessing it
>> occurs early enough. We can verify that by running scalac on your
>> example with -Xshow-phases. If you post the output of that option I'll
>> tell you more.
>
> CPS is the 10th phase (right after liftcode). That should be well before
> anything that scala-jribble does.

Thanks for that list. Yes, jribble backend runs right after cleanup
which is really late in the pipeline. It means that, in principle, CPS
should work just fine and should be completely transparent to jribble
backend. It's hard to verify this hypothesis at the moment because we
are using outdated (one year old) fork of scalac. It's on my roadmap
to perform an update but this will happen in late June.

> Excellent!  I noticed (while working on this Async code) that Showcase
> doesn't exercise GWT RPC.  It may be worthwhile, as an intermediate
> milestone, to get a small app running that exercises a wider range of GWT's
> features (Showcase is focused on UI, I think).

Yep, Showcase uses lots of UI features of GWT. Isn't it that GWT RPC
heavily depends on Generators?

If you could come up with some more sophisticated app than our hello
world smaller (in terms of code and used features) than Showcase that
would be perfect. It would make my work much easier.

>> Oh, and on 4th of July I'll be moving to Lausanne and joining
>> Typesafe. I'll be working on Scala+GWT full-time again.
>
> That's great news!  I'm a little jealous. :-)

Thanks :-)
Actually, I should clarify that I'm joining those guys as an intern
not full-timer. Still, I'll have a pleasure to work with a lot of
great people. :-)

--
Grzegorz Kossakowski

Lex Spoon

unread,
May 27, 2011, 12:56:57 PM5/27/11
to scal...@googlegroups.com
That is awesome that you are working on getting continuations to work
with Scala+GWT. A real bragging point indeed.

However, be careful about the choice of application. Synchronous RPC
is a dragon with many skeletons in front of its cave. It's been many
times tried but never successfully.

http://labs.oracle.com/techrep/1994/smli_tr-94-29.pdf (a real classic)
http://blog.lexspoon.org/2010/06/evidence-from-successful-usage.html
http://blog.lexspoon.org/2009/01/method-calls-should-be-immediate.html
http://code.google.com/webtoolkit/doc/1.6/DevGuideServerCommunication.html#DevGuideGettingUsedToAsyncCalls


There are other uses of continuations, however. For example, they
enable coroutines....


Lex Spoon

Aaron Novstrup

unread,
May 27, 2011, 2:27:39 PM5/27/11
to scal...@googlegroups.com
Lex,

I'll take a look at those references, but I'm not sure we're talking about the same thing.  There's nothing synchronous about the RPCs -- the coding style just *looks* synchronous.  Under the hood, standard asynchronous RPCs would be used.  The API and type system should ensure that asynchronous calls could only be used in an "asynchronous" context, so the developer would be aware of where of where RPCs are occurring.  Furthermore, the code would not block.  For example,

text = "initial"
asynchronously {
   val response = obj.someAsyncMethod()
   text = response
}
text = "waiting..."

Assuming obj.someAsyncMethod() successfully returns "done", the values taken on by `text` would be "initial", "waiting...", "done", in that order.  This code would be completely equivalent to

text = "initial"
obj.someAsyncMethod(
   onSuccess = { (response: String) => text = response }
   onFailure(t: Throwable) = {}
)
text = "waiting..."

Lex Spoon

unread,
May 27, 2011, 6:26:12 PM5/27/11
to scal...@googlegroups.com
There is more going on here than just an implementation of sync RPC.
However, from the point of view of the code inside the asynchronously
block, the call to callAsync is indeed a sync RPC. When the method is
called, local execution stops until the RPC response comes back. It
doesn't matter that the implementation uses an async RPC under the
hood. It's the observable semantics that matters.

There are other applications than sync RPC, however! For example,
continuations should be very helpful in getting actors to work in a
browser....

Lex

Aaron Novstrup

unread,
May 27, 2011, 8:01:11 PM5/27/11
to scal...@googlegroups.com
I'm sure there are lots of other applications of delimited continuations, but I don't think I follow your argument about the direct-style asynchronous RPC approach.

The async call appears synchronous *by default* from the point of view of code inside the asynchronously block, but there's a critical distinction from synchronous RPC: the developer can always make the call return immediately, just by wrapping it in another asynchronously block. Because all of the calls are actually asynchronous, the developer has fine-grained control over where his code will appear to block.

For example:

field1 = "starting up..."    // 1
field2 = "starting up..."    // 2
asynchronously {
   val x = firstAsyncCall()    // 3
   field1 = x                        // 5
   asynchronously {
      val y = secondAsyncCall()    // 6
      field2 = y                             // 8
   }
   field2 = "waiting..."    // 7
}
field1 = "waiting..."    // 4

Any method that's going to make a "blocking" call would have to be annotated in order to type check (this is part of how the delimited continuations plugin works), so the developer is protected from having a method call "block" when he didn't expect it to. And of course, there's no real blocking so the browser would always remain responsive. 

The type system, by requiring such annotations, would encourage the developer to use asynchronous calls with care (i.e. he's probably not going to wrap his whole program in an asynchronously block, since that would require annotating the types on all method calls, recursively).

As far as I can tell, this approach doesn't have the weaknesses of synchronous RPC, but it does have the advantages.  I'd love to hear counter-arguments, though!

Lex Spoon

unread,
May 28, 2011, 3:23:21 PM5/28/11
to scal...@googlegroups.com
If you really want to work on it, then I hope it is an enjoyable
project. I just thought you might like to know some of the history of
the idea.

Lex Spoon

Aaron Novstrup

unread,
May 28, 2011, 3:29:28 PM5/28/11
to scal...@googlegroups.com
I very much appreciate the feedback, and I certainly don't want to waste my efforts.  My sense at this point is that this approach is sufficiently different from synchronous RPC to warrant further investigation, but I'd value any further input on that point.  What did you think about the arguments I laid out in the last email?

Varga Endre Sándor

unread,
May 29, 2011, 5:43:40 AM5/29/11
to scal...@googlegroups.com
Hello!


> The async call appears synchronous *by default* from the point of view of
> code inside the asynchronously block, but there's a critical distinction
> from synchronous RPC: the developer can always make the call return
> immediately, just by wrapping it in another asynchronously block. Because
> all of the calls are actually asynchronous, the developer has
> fine-grained
> control over where his code will appear to block.

I have to side with Aaron on this matter. The trick is here that the
continuations are delimited, allowing the developer to use blocks of
apparent synchrony to improve readability, while allowing code outside the
blocks to proceed asynchronously. As I see, there is not much difference
between callbacks, and the parts of an "asynchronously" block after the
"blocking" call. Am I right? (I never used delimited continuations before)
It seems to me that this is more like a syntactic sugar for callbacks than
real blocking RPC.

I think this style can be useful if the client code has long, linear
conversations. In these cases asynchronous code ends up with a lot of
state management. With delimited continuations this state management is
provided by the compiler when executing the continuations transformation
phase. Again, I have a limited understanding of the continuations plugin,
so I am happy to hear any corrections.


Lex Spoon

unread,
May 29, 2011, 8:55:38 AM5/29/11
to scal...@googlegroups.com
On Sat, May 28, 2011 at 3:29 PM, Aaron Novstrup
<aaron.n...@gmail.com> wrote:
> I very much appreciate the feedback, and I certainly don't want to waste my
> efforts.  My sense at this point is that this approach is sufficiently
> different from synchronous RPC to warrant further investigation, but I'd
> value any further input on that point.  What did you think about the
> arguments I laid out in the last email?

You give two main arguments.

First, there is the argument that programmers only have to worry about
methods marked CPS-able. However, that sounds to me like saying that
programmers don't have problems if they don't use the facility very
much. If a substantial number of methods are marked as CPS-able, then
any call to any of those methods must be written extra carefully
compared to a system without the facility.

Second, there is an argument that programmers can defend against any
one call blocking by wrapping it in an asynchronously block. However,
the problem is not in dealing with any one method call possibly
blocking. The problem is that *lots* of method calls might block.


Lex Spoon

Aaron Novstrup

unread,
May 29, 2011, 11:31:53 AM5/29/11
to scal...@googlegroups.com
On Sun, May 29, 2011 at 5:55 AM, Lex Spoon <l...@lexspoon.org> wrote:
On Sat, May 28, 2011 at 3:29 PM, Aaron Novstrup
<aaron.n...@gmail.com> wrote:
> I very much appreciate the feedback, and I certainly don't want to waste my
> efforts.  My sense at this point is that this approach is sufficiently
> different from synchronous RPC to warrant further investigation, but I'd
> value any further input on that point.  What did you think about the
> arguments I laid out in the last email?

You give two main arguments.

First, there is the argument that programmers only have to worry about
methods marked CPS-able. However, that sounds to me like saying that
programmers don't have problems if they don't use the facility very
much. If a substantial number of methods are marked as CPS-able, then
any call to any of those methods must be written extra carefully
compared to a system without the facility.

  1. It's fairly burdensome to mark types as CPS-able, so I think that in itself is a fairly good argument.
  2. Indiscriminately marking methods this way would be bad style, and should be actively discouraged in the documentation.  I've never been convinced by that argument that a language should not have this or that feature because of the potential for abuse (indeed, many of Scala's features fall into this category).  This argument seems to boil down to the same thing.
  3. If possible, I'd like the type system to make even stronger guarantees -- rather than just marking types CPS-able, they'd have to be marked with a more specific type (unique to this facility).
 
Second, there is an argument that programmers can defend against any
one call blocking by wrapping it in an asynchronously block. However,
the problem is not in dealing with any one method call possibly
blocking. The problem is that *lots* of method calls might block.

Note that a developer would only have to worry that method calls might block if
  • He puts them in an asynchronously block, in which case the method only blocks the code in the block.
  • His own method is CPS-annotated, in which case any call could potentially block the rest of the method (and any other code up the call stack up to the boundary). For this reason, developers should be advised about the dangers of explicitly annotating methods in this way.


Aaron Novstrup

unread,
May 29, 2011, 11:31:59 AM5/29/11
to scal...@googlegroups.com
2011/5/29 Varga Endre Sándor <ven...@hit.bme.hu>


I have to side with Aaron on this matter. The trick is here that the continuations are delimited, allowing the developer to use blocks of apparent synchrony to improve readability, while allowing code outside the blocks to proceed asynchronously. As I see, there is not much difference between callbacks, and the parts of an "asynchronously" block after the "blocking" call. Am I right? (I never used delimited continuations before) It seems to me that this is more like a syntactic sugar for callbacks than real blocking RPC.
 
Exactly. It's just syntactic sugar for callbacks.  The continuation (from the async call to the end of the asynchronously block) would get packaged up as a normal AsyncCallback.

Ben Hutchison

unread,
May 30, 2011, 1:31:41 AM5/30/11
to scal...@googlegroups.com
IMO, (simulated) synchronous calls in GWT would be awesome!

 

On Saturday, 28 May 2011 02:56:57 UTC+10, lexspoon wrote:

Synchronous RPC
is a dragon with many skeletons in front of its cave. It's been many
times tried but never successfully.

What do you mean? Synchronous RPC is used successfully all the time. HTTP, Web services, RMI, Corba, the world is full of it.
 

My problem with Waldo & co 's classic piece (with which Im familiar) is that I feel they talk more about problems than really offering any viable alternatives.

And I note Waldo went to on to work on JINI, which used synchronous RPC in the form of RMI.

Simply, if you depend upon some data to proceed, /you just need to wait/!

-Ben

Aaron Novstrup

unread,
Jun 2, 2011, 3:01:01 AM6/2/11
to scal...@googlegroups.com
What do you think about using scala-gwt-dlx[1] as a test app?  It's quite small, exercises GWT RPC, and already has a Scala backend.

[1] http://code.google.com/p/scala-gwt-dlx

On Fri, May 27, 2011 at 7:14 AM, Grzegorz Kossakowski <grzegorz.k...@gmail.com> wrote:
2011/5/27 Aaron Novstrup <aaron.n...@gmail.com>:

Grzegorz Kossakowski

unread,
Jun 2, 2011, 4:54:34 AM6/2/11
to scal...@googlegroups.com
2011/6/2 Aaron Novstrup <aaron.n...@gmail.com>

What do you think about using scala-gwt-dlx[1] as a test app?  It's quite small, exercises GWT RPC, and already has a Scala backend.

Looks good. If you could fork https://github.com/scalagwt/scalagwt-sample and add Scala version of that app it would be fantastic. Do not translate SudokuApi as it's uses annotations that are not supported yet. If keep Java version we should be fine.

Also, do not use xml as it has it's own problems (small but I don't have time to fix them now).

--
Grzegorz Kossakowski

James Moore

unread,
Jun 2, 2011, 3:40:33 PM6/2/11
to scal...@googlegroups.com
On Sun, May 29, 2011 at 8:31 AM, Aaron Novstrup
<aaron.n...@gmail.com> wrote:
> 2011/5/29 Varga Endre Sándor <ven...@hit.bme.hu>
> Exactly. It's just syntactic sugar for callbacks.  The continuation (from
> the async call to the end of the asynchronously block) would get packaged up
> as a normal AsyncCallback.

It looks a lot like F# async workflows - useful stuff.

http://blogs.msdn.com/b/dsyme/archive/2007/10/11/introducing-f-asynchronous-workflows.aspx

--
James Moore
ja...@restphone.com
http://blog.restphone.com/

Aaron Novstrup

unread,
Jun 5, 2011, 8:19:53 AM6/5/11
to scal...@googlegroups.com

Aaron Novstrup

unread,
Jun 5, 2011, 10:36:58 AM6/5/11
to scal...@googlegroups.com
In addition to that fork, I also published a version (https://github.com/anovstrup/scalagwt-sample/tree/gwtdlx-client) in which all of the code is client-side.  Rather than sending the sudoku puzzle up to the server for solution, it will (should) solve it on the client.  The really cool part is that it will (should) do this without blocking the browser by employing the GWT Scheduler-based control structures I'm working on.  

These structures allow developers to use deferred/incremental commands with a direct style:

sched {
   defer // defer the rest of the computation until after the browser's event loop
   var i = 0
   while (i < 10000)
      f.setText(f.getText + (i % 10))
      i += 1
      yieldc // yield control to the scheduler (i.e. schedule the rest of the computation as an incremental command)
   }
   sleep(3000) // defer the rest of the computation for 3 seconds
   f.setText(f.getText + "\nDone!")
}

The sudoku code looks like this (highly simplified for brevity):

def solve(puzzle: Puzzle) = {
   sched { // delimits continuations
      val solution = search(puzzle)
      solution match {
         case None => displayFailure
         case Some(s) => displaySolution(s)
   }
}

def search(puzzle: Puzzle): Solution @schedulable = {
   yieldc
   if (puzzle isSolved) puzzle.solution
   else search(puzzle + someNewConstraint)
}

GWTC isn't quite up to building the sudoku solver yet, so there are likely bugs (thus the "shoulds" above).  But the code compiles in scalac!
Reply all
Reply to author
Forward
0 new messages