2006-05-26
Continuation based Web Servers
There's a lot of discussion going around at the moment about continuation based web servers. Some of the comments I've seen seem to be based on a misunderstanding of what exactly this type of server provides.
Ian Griffiths has a post about why he thinks continuation based web fraemworks are a bad idea.
Ian writes about 'Abandoned Sessions':
This is very much not analogous to the function returning or throwing an exception. In the world of our chosen abstraction - that of sequential execution of a method - it looks like our thread has hung.
The problem with this is that a lot of the techniques we have learned for resource management stop working. Resource cleanup code may never execute because the function is abandoned mid-flow.
The problem of a user exiting an interaction in 'mid-flow' is not unique to continuation based web servers. If the user is stepping through a multi-form shopping cart checkout process then the data they have entered must be stored someone.
If it's in the database or web session then this data will live for a specified time (usually the session timeout) and if the user doesn't continue the flow then it is deleted.
The same logic occurs in continuation based servers. The continuation, if stored on the server, holds the data that the user has entered during the flow. After a set timeout the continuation is removed. In some systems the continuation data is stored in the web session so this is done automatically.
Like any other web framework the continuation data can be stored in a form field on the clients browser (assuming the framework allows serialisable continuations) so there is no need for this 'garbage collection' to occur.
Ian is right that it is important not to acquire a resource and clean it up after a continuation capture boundary. If the user never returns to continue the flow then it may not be cleaned up....unless your language with continuations also has a 'dynamic-wind' construct. This is like try/finally in Java except you can have code run when a block is entered or exited for any reason. So when a block is exited due to a continuation escape then the resource is automatically released. When it is entered again it is automatically acquired. This approach removes the worry about hanging resources.
It's important to remember that the 'thread' running the web request doesn't hang when the continuation is captured. After capturing it the thread is gracefully exited in the normal manner while the web page data is returned to the user.
Ian continues with Thread Affinity:
With an ordinary sequentially executing function, I can safely assume one thread will run the function from start to finish. But if I'm using continuations to provide the illusion that I've got sequential execution spanning multiple user interactions, then I might get a thread switch every time I generate a web page.
You'll get a thread switch every time the user makes a request in a standard framework that supplies threads from a thread pool. I've not had a situation where switching threads has been a problem. Given the 'dynamic-wind' feature mentioned before, anything that requries thread affinity can be released and re-acquired in the new thread when the continuation is resumed.
The same goes for the 'Web Farms' issue. In a system where the continuation can be serialised or sent to another machine then another machine on a web farm can deserialise the continuation and continue. If this is not the case then session affinity can be used to ensure that a request in the same session is processed by the same machine.
Ian has a number of issues with back button and branching. The nice thing about continuation based frameworks is that this is all handled for you.
If the user hits the back button then they go back to a previous continuation. A continuation is a snapshot of a stack frame so has access to a copy of all local variables at the time that it was captured. This means that the back button 'just works'. Ian writes:
Normal functions don't do that - they only jump back to earlier points if you use flow control constructs such as loops. Giving the user the option to inject goto statements at will is an unusual design choice, but anything that models user journeys as sequential execution of code will have to cope with this kind of rewinding, or it'll break the back button. And I don't know about you, but I hate sites that break the back button. (Yes, Windows Live Search, I'm looking at you.)
I completely agree that breaking the back button is a bad thing. With a continuation based framework you get a working back button for free because the execution stack is wound back to the point of the continuation for the page the user went back to.
You can choose not to have state wound back by most systems by storing data in the database, or keeping them globally in memory. Sometimes you don't want the user to go back. For example, if they've just processed a credit card you don't want them to go back and resubmit the form.
The way to do this is to have a way of marking a block as 'run-once'. If continuations captured within that block are attempted to run again then an error occurs, either displaying a message (don't submit twice) or going back to a known safe point. The framework handles this for you. This is easy to implement because it's as simple as checking at the beginning of the continuation entry if we've been there before by reading a flag.
Session cloning or 'bifurcating' as Ian calls it is handled just as easily. By opening a tab or new window on the existing page you get a resumption of the continuation the user is already looking at. Since these are copies of the execution stack the user effectively gets a copy of all the local variables. Modiying these has no effect on the information on the original page. So there is no need for the programmer to guard against concurrency here as Ian seems to think:
This means I have to write my function in such a way that it can cope not only being rewound, but also to being split so that multiple threads execute the function simultaneously, each taking different paths. But of course because I'm using continuations, each of these threads gets to use the same set of local variables. The fact that I enabled users to inject gotos into my code at will is now looking like a walk in the park - now they can add arbitrary concurrency!
You can choose to have state that does not get cloned of course. Just store it in the database (for example, the shopping cart).
Somethings you want to be 'global' and unaffected by the user using the back button, bookmarks and cloning. These can be stored in the database or some other persistent store. The shopping cart is the obvious example. You wouldn't want the user hitting 'back' and losing the last item put in the shopping cart. Or cloning the session to result in items not being added. Other things you may want wound back (Some types of data entered in a form, etc). These can be local variables. Continuation based servers give you the choice.
I highly recommend reading some of the papers mentioned in a previous posting of mine. They describe in much more detail some of the advantages (and disadvantages) of using continuations to model web flow.