I was recently reading some online discussions about continuations in web frameworks, and it was clear from the discussions that most of the people involved didn’t have a clear idea of when continuations would be useful, nor did I run across any good examples in my brief survey. Continuations can be hard to understand, especially in the absence of good example cases. But there is a very specific kind of situation in web programming in which continuations are exactly the right solution.
I am working on an application in which the user can tie into their WordPress.com blog. The application goes through several preparatory steps, then redirects to WordPress, where the user authenticates to the blog, then returns and goes through several more steps in my application. It’s really a very straightforward list of tasks, but not all of the tasks can happen at the same time, in the same request, or even on the same server. What is needed is a way to save the procedure in process at any point along the way, and pick it up again when the user comes back for the next step.
A continuation is nothing more than a stored procedure-in-process that can be resumed later. In the context of a web application, the procedure-in-process can be stored and then picked up by the user when they return. A continuation provides a way to model a web application as a procedure, even if the procedure requires several request and response cycles.
Unfortunately, most programming languages (and therefore, web frameworks) don’t support continuations, which are seen as too arcane or hard to understand. Even those that have something called “continuations” often don’t have the kind of continuation that is needed to do this particular programming job, which is one where the state of a procedure can be stored as an object or file (“picked”) for an indefinite period of time until the user happens to return.
I once wrote just such a thing in Stackless Python, by creating Python generators that could be pickled, and therefore, stored as files. I fondly called them continuators. When a continuator was reloaded from the file, it would continue from where it left off; if the same continuator was reloaded, it would re-run from the same point. This behavior is quite special, because generators in regular Python (CPython and the like) cannot be pickled, and they are run-once affairs. It is conceivable that a single-process web application, or one which uses strict session affinity, could make use of generators in “normal” Python to serve as a kind of continuation. But pickling enables the generator to be stored, and therefore loaded by whatever process happens to be the next one called. And it also allows the generator to be run from the same point any number of times, which means that the web application “back” button can work as expected.
But I haven’t yet found the old code that I wrote (something like 8 years ago), and I’m not currently building on Stackless. So I have to solve the application problem by more conventional means.
The other two main ways to solve this kind of situation are (a) to use a different url for each step of the application, or (b) to create a state machine, and store state on the server as the application proceeds. This approach is not at all ideal — for one, it completely breaks the “Back” button. But it does provide a way through. The basic idea of a state machine is that the application runs through the same overall loop several times. Each time through, the state changes — some data is stored, or some question is answered, or some authentication token is received. On each iteration, there is a massive if-then-elif-then-elif… structure that tests for the different possible states, and carries on accordingly. It works, but it’s not pretty, and it can be very hard to trace.
Some people say that continuations make it hard to follow control flow, but I found in using my homebrew continuators that it made application logic very sensible. So perhaps I’ll look harder for that old code, and see if it can be used in web programming (which now requires concurrency and real-time — topics for other posts!). Or I might see if Scala continuations will support the kind of continuators that I have in mind.
What solutions have you used for the problem of storing procedure-in-process in a web application? What has worked well for you?