Beyond Java/Continuation Servers

From WikiContent

< Beyond Java
Revision as of 17:12, 17 March 2008 by Docbook2Wiki (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
Beyond Java

I rarely run rapids on blind faith. If there's any danger, I like to know exactly what the water and rocks could do to me, and I need a plan to deal with any potential trouble. On this day, though the consequences for failure were high, the move was easy. I still don't know exactly how it worked, but I watched boater after boater thrust, brace, and arrive in the turbulent boil below The Elbow, a slotted 20-foot drop that guidebooks describe as a deadly entrapment motel. Sure, I could tell you that the move was called a slot move, and I'd need to apply my brace with perfect timing and angle to avoid hitting the wall on the way down. I knew the timing, because I'd been told. It's just the "why" of it that was a mystery. The experts tried to tell me why it worked. Most really didn't know. No one could really tell me with any kind of certainty how the rocks were configured at the bottom. They just knew that at this river level, the move worked. And so it did.

At different points in my programming life, a few tricks held the same kind of mystery for me: recursion as a college student, my first glimpse at reflection shortly thereafter, and now, continuation servers . In this chapter, you'll see continuations, and how they're used in a new class of application servers.


The Problem

Web development, for all its usefulness, often happens with a curious inelegance. It's kind of like making sausage. I like the result, but I don't want to see how it's made. Web programming in Java was better than web programming in alternative languages. It gave you more structure with easier maintenance and, often, better scalability than Visual Basic or Perl-based approaches, and an easier programming model than C++. But for all the benefits, certain problems make it seem clunky and awkward.

What You Want

Current web application servers might be powerful, but they're not convenient or natural. So, what is convenient and natural? It shouldn't take too much effort to figure that out. What if your controllers looked like this:

    if ( =  = true) {;

or this:

    if (shoppingCart.verify());

That's better. What you really want to do is encapsulate the presentation of one or more web screens in a method. Then, more sophisticated page flows would not be a problem. You could simply roll up more and more pages into higher-level components. For example, you could take this code:

    if(checkoutAddress.getSeparateBilling) checkoutBilling.showForm();

and roll it up onto a method:

    public static void showCheckoutWizard() {
      if(checkoutAddress.getSeparateBilling) checkoutBilling.showForm();

so the usage becomes:


in its cleaner, refactored form. But you can't code that way, because your web server won't let you. Creators of most web application servers will sell their soul to keep things stateless and scalable.


Think of living without any short-term memory. Normal conversations in day-to-day life would be nearly impossible. Think of the logistics:

  • You'd have to write down every important phrase of every conversation as it occurred.
  • Then, when someone asked you a question, you'd have to look up the history of your conversation with that person before you could answer.
  • To optimize things, you'd have to decide how much information you should keep close by—say, in your briefcase—versus at home, in your filing cabinets.
  • When information got too old, you'd need to throw it out.
  • You'd have to maintain this whole system and revisit it when it didn't meet your needs.

That's the status quo for web developers. Your briefcase is the HTTP session, and your file cabinet at home is the relational database. It's an insane proposition, but we deal with the tedium because the Web is so important, and stateless solutions scale better. So, you willingly take a pretty large stride away from the ideal solution. Still, each time you struggle with the awkward little edge cases, you ask yourself if there's a better way, some kind of abstraction that fits the problem more neatly.

The Back Button

Saving state within simple conversations does not cover the whole problem. On the Web, conversations are not linear. Users can and do change their minds, pressing the Back button. Some assumptions that you've made as you continue to accumulate data may no longer apply.

Sometimes, you'll want to keep the user from going back, such as when she's made a purchase, or done something to force a committed change in a relational database. In these cases, you can simply punt and disable the Back button. Most often, you need to build special support for the Back button. You may even have to remove data from a database that a user would not have seen yet. Worse, many web designers simply don't solve the problem, and tell the user to expect unintuitive behavior. You've taken one more step back, away from the ideal. Once again, this awkward Back button forces you to deal with things on a case-by-case basis, and it just doesn't feel right.


Web development in Java focuses an incredible amount of brain power around navigation and flow . You'd think controlling flow from the server side would be natural, but servers can't update clients—they can only respond to requests. This simple little truism forces servers to handle hundreds of little requests rather than a couple dozen application flows. It's also hard to synchronize the user interface with the model. You'd like to use a simple method call that controls the user interface and model, but you can't. The web server just doesn't work that way. And you're stepping back again, and you've got that sinking suspicion that there's a cliff behind you somewhere.

Continuation Servers to the Rescue

A new class of web servers called continuation servers is starting to make some real noise. A continuation server uses a programming construct called the continuation to keep enough information about a request to be able to completely reconstruct the context. In technical terms, a continuation saves the execution environment, including the call stack. In practical terms, using continuations in a web server lets the server maintain context for you, freeing you to program in a more natural way.


You've probably played video games. Think of a continuation as a save game feature. As you're playing your game, you save your current game. You can feel free to take your chances with the monster control center. If you die, you simply restore the game. Said another way, a continuation is a snapshot of a point in time. Continuations let you save the system's state (in the form of an execution stack) in one place, and then return to that state on command.

Since I've already introduced Ruby's syntax, I'll first show you continuations in Ruby, where continuation syntax is clean and precise. Then, I'll show you Seaside, the most popular continuation-based server, in Smalltalk.

In Ruby, a code block defines the universe for the continuation. You'll use a continuation object to hold the execution state, consisting of the execution stack. You'll later invoke a call method on the continuation object to restore the system state, replacing the current execution state, including the call stack, with the one in the continuation object. The call returns execution to the point immediately after the code block. From Ruby's perspective, you're conceptually letting your execution state jump back in time.

The Syntax

In Ruby, you get a continuation by calling the callcc method on Kernel and passing it a code block. This block does nothing with the continuation but print its object identifier:

    irb(main):001:0> callcc {|continuation| puts continuation}

This passive little program does more than you think it does. The argument called continuation is a powerful little gem that has the whole execution context, with variable values and the entire call stack, at the time that you called callcc. Look at it as a saved game, or a frozen moment in time. You can return to that moment in time. Specifically, Ruby will return to execute the statement immediately after the continuation block by calling the continuation. Here's a trickier continuation example:

    callcc do |continuation|
      for i in 1..10 do if (i =  = 7)
        puts i
      puts 'This never happens.'
    puts 'Good bye.'

And the output:

    >ruby forloop.rb
    Good bye.

Once again, the whole callcc statement is a point in time. When i is 7, Ruby executes That takes control to the point right after the continuation code block, so the last two numbers don't get printed, and the puts 'This never happens.' in fact doesn't happen. The callcc method loads the application stack in the continuation, abruptly sending execution to the line of code immediately after the continuation code block, or puts 'Good bye.'. It moves execution around a little bit like a goto.

Of course, you'd not usually use continuations to break out of a for loop. Continuations take on a little more power when you pass them out of the code block, such as with a method call.

A More Powerful Example

Keep in mind that the continuation will return the call stack and local variables in the block to the way they were when you made the continuation call. So, this program:

    1  def loop
    2    for i in 1..5 do
    3      puts i
    4      callcc {|continuation| return continuation} if i=  =2
    5    end           # returns here
    6    return nil
    7  end
    9  puts "Before loop call"
    10 cont=loop()
    11 puts "After loop call"
    12 if cont
    13 puts "After continuation call"

gives you this result:

    >ruby continuation.rb
    Before loop call
    After loop call
    After loop call
    After continuation call

So, we were able to exit the loop when something happened and return to the loop on command. Since continuations are so alien, let's look at this example in a little more detail. It's not too bad to read, once you know what's happening. Line 4 saves the game, putting it into a container. Line 12 restores the game. Let's break it down a little further, thinking like a Ruby interpreter:

  • Start on line 9, after the method declaration.
  • Execute line 9, printing the string Before loop call.
  • Execute line 10, calling the method called loop. Put line 10 on the call stack, so you'll remember where to return after the method call.
  • Enter the method loop, specified in line 1.
  • Do the first pass through the for loop in lines 2–5. i has a value of 1. You'll print 1.
  • Start the second pass through the for loop. i now has a value of 2. You'll print 2.
  • At line 4, i is 2, so make the callcc call in three steps. First, make a copy of the call stack. Second, make a copy of the instance variables (i is 2). Third, push the line after the continuation block (line 5) onto the copy of the call stack, so now the continuation's copy of the stack has (line 5, line 10). The call stack simply has (line 10).
  • At line 4, execute the return statement. You'll return the value of continuation to the line on the top of the call stack. The call stack has line 10, so you'll return the value of continuation to line 10. Set cont to the returned continuation. Recall the continuation has the current execution context—the call stack has (line 5, line 10), and variable i has a value of 2.
  • Execute line 11, printing the screen After call loop.
  • Execute line 12. Calling the continuation restores the execution state. Set the value of i to 2. Go to the line number on the top of the call stack so that you'll remove it from the call stack. Now the call stack has only line 10.
  • Execute the rest of the for loop, for i=3, 4, and 5.
  • You'll return nil. The call stack has 10 on it, so you'll return to line 10, and assign cont to nil.
  • Execute lines 13 and 15. Skip line 14 because cont is nil.

This continuation example shows you a few nice capabilities. You can take a snapshot of execution state at some point in time, like we did within the for loop. You can save that execution state in an object, as we did in the cont object. You can then return to the execution state stored in a continuation object at any point.

Why Would You Use Them?

You might first think that continuations are the most useful when you want to break logical control structures, as in implementing a break for our for loop, or processing exceptions. For the most part, though, you want to think "suspend and resume." Continuations are marvelous in these kinds of scenarios. Cooperative multitasking lets one program voluntarily relinquish control to another application, and resume at a later date. This problem is remarkably easy to solve using continuations. A subtler use involves communication. When you've got an application that spans multiple computers with synchronous request/response communication, you often want to suspend control until the remote system responds. When you need to scale this solution, suspending control while you wait frees the system to handle other requests. The system can conveniently resume your application without disruption when the remote system responds, simply by calling a continuation.

Continuation Servers

You can probably begin to see why continuations might be interesting for web servers. If you want to look at a web application as one continuous application with suspend/resume breaks in between to communicate with the user, it makes more sense. While waiting for user input in the form of an HTTP request, the web server could simply store a state, stash the continuation object away in the HTTP session, and instantly return to that frozen point in time when it's time to process another request. Notice in Figure 8-1 that I've conveniently inverted the control. Instead of thinking of a web app as a series of request/response pairs initiated by the user, I can think of a web app as a series of response/request pairs controlled by the server. My server code gets much simpler.

Figure 8-1. Continuation servers invert control from client to server, simplifying the world view, and the code, of the server

Continuation servers invert control from client to server, simplifying the world view, and the code, of the server

Your web application server is no longer composed of many different independent requests. The server can conveniently look at the world as a bunch of simple end-to-end applications. It processes individual requests by loading the state of each user when it's time to process another request, and suspending the user's application when it's time to communicate with the user again. Voilá! Your application can maintain state, and use it to seamlessly control application flow.

At a lower level, the continuation server becomes a collection of web applications with states frozen at a point in time, in the form of continuations. Each user has a session. The continuation server assigns an ID to each session, and organizes the continuations per session. After each request, the continuation server takes a snapshot of the execution state with a continuation object, and associates that continuation with the session. So, a server has multiple sessions, and each session has one or more continuations representing frozen points in time, as shown in Figure 8-2. You can no longer see individual HTTP requests, because they're buried in the application flow. As they should be!

Figure 8-2. A continuation server stores snapshots that have the state of web applications in progress

A continuation server stores snapshots that have the state of web applications in progress

Advantages and Disadvantages

You've seen the primary benefit: you can look at a web application as one big piece, instead of coordinating lots of little requests. That's incredibly powerful. Continuation servers have some other capabilities as well. The Back button problem becomes much easier to solve, because if the Back button is not disabled, you can just revert the application state to the last continuation, or any previous continuation. To disable the Back button, you simply tell the browser and delete past continuations. Threading also becomes trivial, because each thread can work on a private continuation, each with an application's own resources. You don't have to worry about serializing access to a shared session.

Continuation servers work best for applications that have complex state management issues and sophisticated control flows between pages. The continuation server simplifies navigation dramatically by letting you maintain application state between pages.

Continuation servers do have a few problems:

  • The servers typically attach identifiers to URLs, and some don't like ugly URLs (though web sites like use them).
  • You must guarantee session affinity , meaning that after an initial request in a user's session, the same machine must serve the user for every subsequent request. You could overcome this problem with a distributed continuation cache, but just as distributed HTTP sessions are a problem, distributing a continuation cache may not be completely practical.
  • Continuations are more expensive than other session management techniques. I've seen little practical evidence that this has been a problem in production deployments. Still, some believe this approach will not scale as well as traditional web apps. Research on partial continuations will probably solve this problem eventually.

To me, the powerful advantages dwarf the potential disadvantages. It's possible, even likely, that a continuation server in some language will garner enough popularity to serve as a catalyst. Respected programmers Dave Thomas, Glenn Vanderburg, and David Heinemeier Hansson have all pointed to the continuation server as an important technology to watch. Hackers and Painters author, Paul Graham, used continuations in web applications with devastating effect at Viaweb, on an application that eventually became Yahoo! Store. He's also a proponent of continuation servers. Let's see an example of the most popular web framework supporting continuations.


Seaside is a highly productive web development framework written in Smalltalk. Avi Bryant initially developed Seaside in Ruby, in a framework called Iowa. Early Ruby continuations had a few problems, so the original author of Seaside moved to Smalltalk. Since then, he's been improving the framework and using it to deliver commercial applications. Seaside has a few defining characteristics:

  • Seaside renders HTML programmatically. Most Java frameworks render HTML with templates. I don't know enough to advocate one method over another, but it's certainly different, and it works well in Seaside's model.
  • Seaside has a model for components. A Seaside component manages user interface state and renders itself in HTML. Seaside components are highly reusable, and they let you think in increments smaller than a page.
  • Seaside makes it easy to manage a link. You can specify a link with a code block, so links don't get out of sync. The framework manages them for you.
  • Seaside is modal. This is the author's word for a continuation server approach. Seaside lets you express one web page, or multipage web flows, in a single method.
  • Seaside's debugging is the best I've ever seen. From within the browser, you can open a web-based Smalltalk browser, complete with code. You can also inspect the values of all the objects in the application.

Of course, you also get the advantages of using a highly dynamic language. You get a rapid feedback loop, interactive interpretation as needed, and full access to Smalltalk's excellent environments. I used the Squeak IDE for examples in this chapter. Squeak is a dialect of Smalltalk popularized by Disney.

A Little Smalltalk Syntax

Before we get too far, you should know a little Smalltalk syntax. Don't worry. I'm not saying that Smalltalk is the next great language; I just want you to see the power of the best continuations-based server. If you want to follow along, download the Squeak IDE from Start Squeak, click on Tools, and drag a workspace and transcript window onto your desktop. Use your workspace window for input, and look to the transcript window for output.

Smalltalk syntax is quite simple. Type an object name first, the method second, and any parameters third. Let's evaluate a few statements. In your workspace, type:

    Transcript show: 'Hello'

Highlight it, right-click, and then select do it from the menu. (You can also use Alt-D before you press Enter, to evaluate the line.) You should see the word Hello in your Transcript window. Transcript is the object, show: is the method (Smalltalk calls methods messages), and 'Hello' is a parameter.

Like Ruby, Smalltalk supports code blocks, though the syntax is a little different. Evaluate this:

    1 to: 5 do: [:i | Transcript show: i]

First, you see that [ and ] mark the beginning and end of the code block. i is a parameter for the code block. In the declaration, you'll precede it with a colon.

Let's try multiple statements. Smalltalk terminates statements with a period. Logic uses messages and code blocks :

    age := 4.
    (age > 16)
       ifFalse: [Transcript show: 'Youngster.']
       ifTrue: [Transcript show: 'Old timer.']

This bit of code sets age to 4 with the := message. Then, it sends the ifFalse: method to the (age > 16) expression. The first code block is a parameter for ifFalse, and gets called if the expression evaluates to false.

You can see the influence of the elegance of Smalltalk in Java, and other languages, too. Java's garbage collection, design patterns, and collections all share Smalltalk's influence. Consider Hibernate's use of message chaining . If a method doesn't have a return value, it simply returns itself, enabling tighter code like this:


Many ideas from Eclipse have roots in IBM's VisualAge for Java , which first shared IDE code and a virtual machine with a Smalltalk product. Smalltalk syntax is wonderfully consistent.

A Seaside Overview

Seaside is a Smalltalk framework and a server. Remember, a continuation server is different from other web servers, so Seaside must run in its own environment. In Squeak, you'll left-click on the desktop to give you a menu (called the world menu). Then, you'll select Open... → SqueakMap Package Loader. Use it to install four packages: DynamicBindings, KomServices, KomHttpServer, and Seaside, in that order. Now, your Smalltalk image has Seaside. To see it, fire up the server. In Squeak, you'll open a workspace and evaluate:

    WAKom startOn: 9090

WAKom is the name of the server. starton: is a method that tells the server to start on a supplied port, 9090 in this case. In some ways, WAKom is like Tomcat, or any other web application server. You can configure it by pointing your browser to:


You'll see Seaside's configuration screen. Some of the items should look familiar to you. You'll see a list of registered applications, and some configuration options. Later, it will become clear that Seaside is more than Tomcat in Java.

A Seaside Example

Under the /seaside heading, notice the list of apps. One of the examples that you see in the configuration screen is store. Click on it. You'll see SushiNet , one of the more bizarre examples for web frameworks. In the search window, type the word Tuna. Click on two different tunas to add them to your cart. Now click the Back button and notice that you go back to a previous page, just the way it was. Add another tuna to your cart, and you'll notice that the old tuna item is still in your cart. So, you can override the Back button behavior, as needed.


Notice the three boxes across the top of the screen, in Figure 8-3. Seaside is a component-based architecture . Each component has independent rendering, and each has a model behind it.

Figure 8-3. This Seaside application has three major components, each with independent rendering and business logic

This Seaside application has three major components, each with independent rendering and business logic

This component-oriented approach often makes it much easier to design and refactor complex web screens. For example, here's the rendering for the shopping cart:

      html divNamed: 'cart' with: [
        html small: [html bold: 'Your cart:'].
        html table: [
          cart countsAndItems do:
           [:assoc | self renderRowForCount:
                     assoc key of: assoc value on: html ].
          html spacerRow.
            tableRowWith: ''
            with: ''
            with: [html bold: cart totalPrice printStringAsCents].

Notice that Seaside components have code that generates HTML. Java people don't tend to like this approach either, but it's very productive in Seaside. The code in bold generates the table. First, you see the table message passed to the html object. This will generate table tags around the code block. Next, you'll see a loop that processes the items in the cart, a spacer row, and a row with the totals.

Complex Control Flows

For this application, the most complex series of windows is the checkout. Think of how a traditional stateful application would manage the flow of control. Try out the checkout in the application and see how it works. Add a few pieces of sushi to your cart and click on Checkout. This piece of SushiNet will walk you through a few major steps:

  • You'll verify the contents of your cart. If you like your order, you can click "Proceed with checkout." Otherwise, you'll click "Modify my order." So the user makes a decision, and flow changes based on the user's input.
  • You'll specify a shipping address. You can then choose whether to use this address for your billing address. Again, this decision impacts the flow of the application. If you don't want to use the same address for shipping and billing, SushiNet will reuse the component that renders the shipping address for the billing addresses. Nice.
  • You'll enter your credit card information. If it doesn't verify, you'll go back to the same screen. If it does verify, you'll get a success screen.
  • Users can click the Back button at any time. If the user hits the Back button after his order is submitted, he'll get a message that the page has expired.

So, the flow looks something like Figure 8-4. It's not that complicated. You've got four decisions, and based on the decisions, you route the user to the appropriate place.

If you implemented this flow with Java servlets, you'd need to process four or more independent requests, as in Figure 8-5. Each one would have to first load the current state at the beginning of a request, and store the current state at the end of the request. The web flow would be based on the user's

Figure 8-4. This flow has three different user decisions, and would complicate traditional web apps

This flow has three different user decisions, and would complicate traditional web apps

decisions, so you'd have several forwards. Changes in flow would lead to potentially major refactoring.

Figure 8-5. Java servlets view the checkout problem as four or more independent requests

Java servlets view the checkout problem as four or more independent requests

With a continuations approach, the logic becomes almost trivial, as you see in Figure 8-6. You can simply look at the flow as one simple component, called Checkout. That component can handle flows involving more than one component, or more than one page! The code looks seductively simple.

Figure 8-6. With Seaside and other continuation servers, the flow becomes a single, integrated method

With Seaside and other continuation servers, the flow becomes a single, integrated method

Debugging and browsing

Since you have a frozen continuation, it's easy for Seaside to provide a complete snapshot of the execution state. Seaside goes a step further and gives you access to a web-enabled browser. At the bottom of the screen, you should see a few links. Seaside creates them by default for all the applications. Notice that you can do profiling or check memory usage, but I've got something else in mind. Click on the link called Toggle Halos.

You should see a frame with three icons appear around each component. These icons give you a full code browser, an inspector, and a cascading style sheet editor. Click on the browser icon (the first one). Notice that you can see exactly where the execution state is frozen. Next, click on (from left to right) Seaside-Examples-Store, WAStoreTask, and Go. You see the code for the store task.

You'll see the code that implements the cart in Figure 8-4:

      | shipping billing creditCard |
      cart _ WAStoreCart new.
      self isolate:
         [[self fillCart.
        self confirmContentsOfCart]

      self isolate:
         [shipping <- self getShippingAddress.
          billing <- (self useAsBillingAddress: shipping)
                        ifFalse: [self getBillingAddress]
                        ifTrue: [shipping].
        creditCard <- self getPaymentInfo.
        self shipTo: shipping billTo: billing payWith: creditCard].

    self displayConfirmation.


In Seaside, tasks handle business logic. Let's zero in on the code in bold. It handles everything after the cart verification. The self isolate method takes a code block and makes sure everything in the block is an atomic operation, or a transaction. The next line of code is interesting:

    [shipping <- self getShippingAddress.

This statement actually presents the getShippingAddress web page to the user, and puts the resulting address into the shipping address. You can see how the framework inverts control. Now, instead of the browser being in control, Seaside lets you direct traffic from the server. The next three lines show a decision:

    billing <- (self useAsBillingAddress: shipping)
                  ifFalse: [self getBillingAddress]
                  ifTrue: [shipping].

The useAsBillingAddress method presents the decision screen. The expression (self useAsBillingAddress: shipping) returns a Boolean, and will trigger either the ifFalse: or ifTrue: methods. ifFalse: will actually trigger the code block [self getBillingAddress], which sends yet another web page to the user.

Though the Smalltalk syntax may seem awkward, if you're a Struts or Servlet developer, you're probably smiling right now. This approach frees you to work at higher abstractions. You can roll up several components, or pages, into a single task, and the continuation server keeps the management simple. State and navigation issues just melt away.

So What?

I'm pretty sure that continuation servers will prove to be important. I'm equally sure that Seaside is not a killer app that will suddenly spring Smalltalk into the mainstream. Smalltalk has 30 years of reputation to overcome. In this time, Smalltalk has rarely been more than an academic language with small forays into commercial development. The Smalltalk community is smart and has technical vision, but I've not yet seen the marketing leadership that will break Smalltalk into the mainstream. After 30 years, that's not likely to change.

Continuation servers do have some minor hurdles to overcome:

  • So far, the servers require ugly, temporary URLs, because each continuation must have a unique identifier. Users don't like uglier URLs. Like Amazon, Seaside works around this limitation by providing a meaningful piece of the URL, followed by the continuation ID.
  • Continuation servers will not scale as well, because saving continuations is stateful and expensive, though if you think about it, the problem is not quite as bad as it could be. Most of the continuations in a server will have common code for the framework. Only the last part of the call stack should be different from one continuation to the next. Partial continuations should provide a good performance boost.
  • So far, the best servers are on academic languages. Lisp, Smalltalk, and Ruby may be holding them back. And of course, continuation servers may help break one of those languages closer to the mainstream.

Still, in the end, continuation servers will play a role, because they're a much more natural and powerful abstraction, and they represent a much more natural way to program. Systems continually get more processing power, and both short-term and long-term storage get cheaper. Productivity eventually trumps all else. In the end, continuation servers are fast enough. Higher abstractions make us more productive. If you held a gun to my head and forced me to make a prediction, I'd guess that continuation servers will evolve and break into the mainstream, but not on Java, or a derivative like C#. Such a language would have to simulate continuations. The concept is cleanest and purest when it is implemented on a more dynamic, higher-level language. I'd guess that continuation servers, in a language like Python or Ruby, may well prove to provide the foundation for all web application servers, in some not-too-distant future.

Personal tools