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':
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:
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:
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:
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.
Categories: web, continuations
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.
Categories: web, continuations

1 Comments:
I was well aware that all these issues exist even in systems that don't use continuations. In fact that was pretty central to my point. :)
My point was: all these issues are intrinsic to the way the web works, but are not features you would expect to encounter in the execution of normal code. This means code is a bad representation for web user journies. It's an unsuitable abstraction because it is completely the wrong shape. You can construct workarounds for all the issues, which is why you've been able to respond to most of the specific issues I raise. But those are all symptoms of the underlying mismatch, which you've not addressed.
Incidentally, the argument that the session will time out misses the point. Would you really be happy to use session timeout as a way of releasing a resource such as a transaction?
Obviously it's a really bad idea to let a transaction span multiple web pages. But using continuations in this style makes it really easy to do that by accident - it won't be at all obvious from the code that such a problem exists. Compare that with the traditional style - you'd have to go out of your way to get a transaction to span multiple pages without continuations.
The fact that bugs become less visible is one of my main objections to this technique.
Likewise with thread switches, yes of course those happen in normal web frameworks. The point is that you expect that. But most people don't expect a single function to flit from thread to thread.
Again, it boils down to the fact that you're taking ordinary web framework behaviour but making that behaviour emerge in extraordinary places.
"By opening a tab or new window on the existing page you get a resumption of the continuation the user is already looking at."
Yes - that was exactly the problem. Often that's really not what you want. This is a case of making the developer's problems disappear by making the user's life worse. By deferring to this automatic behaviour, you're abdicating your responsibility to work out what the right interaction model is.
So you might thing that "The nice thing about continuation based frameworks is that this is all handled for you. " But in saying this, you are essentially saying "My life is now easy. Who cares about the user." Nice for the developer. Crappy for the user.
Implementation-centric interaction design rarely yields good results.
Obviously you're not really so cavalier as the picture I'm painting - you're clearly aware of the issue because you raise the fact that for some things, such as shopping baskets, you have to rely on mechanisms that defeat the default behaviour continuations will offer you. But doesn't the very fact that you're having to do this strike you as a code smell?
It's a direct upshot of the underlying problem: the fundamental mismatch between the behaviour of the system, and the representation you've chosen.
Interaction design is hard. There is no silver bullet. A technical gizmo that makes the technical problems vanish is no substitute.
Post a Comment
<< Home