Updated: 11/5/2005; 6:05:06 PM.
Chris Double's Radio Weblog

Thursday, September 04, 2003

I had an interesting side effect when moving the continuation based server from storing and retreiving the continuations from a hash table (keyed by url) to storing them on disk and retrieving them from disk. The positive side was the fact that shutting the server down and restarting it still allowed the resumption of sessions in progress.

The unexpected bit (but obvious in hindsite) was code that held object references in the continuation between page views was being 'recreated' on deserialisation. So whereas previously a vector holding data between page views was the same vector, now they were different vectors. For example:

(define (data-entry)
  (let ((data (new-data)))
    (show (mk-data-page1 data))
    ;; (A)
    (set-record-field data "name" (get-param "name"))
    (show (mk-data-page2 data))
    ;; (B)
    (set-record-field data "address1" (get-param "address1"))
    (set-record-field data "address2" (get-param "address2"))
    (set-record-field data "address3" (get-param "address3"))
    (show (mk-data-page3 data))
    ;; (C)
    (set-record-field data "more_data" (get-param "more_data"))
    (hashtable/put! *data* (get-record-field data "id") data)

This is simple code to display three data entry pages in order, setting the data input into the forms into a 'data' object. At the end of all the forms the data is stored in a hashtable and the process repeated.

In the original 'store continuation in hashtable' implementation the continuations weren't reconstructed from serialisation. They were 'in-memory'. So the data objects at (A), (B) and (C) were the same object (ie. eq? return #t). If you used the browser back button between pages and modified the form data the state of those objects was not rolled back.

With the change to deserializing the continuations the data objects at (A), (B) and (C) were all newly copied objects as a result of the continuation deserialisation in the 'show' call. (ie. eq? on those objects returned #f). The object had to be fully serialized since in may be loaded in a completely different machine. This resulted in the state of the object being rolled back when the back button was used. This is the behaviour I actually prefer so it's a good side effect I guess.

Seaside gives you the option of what you want to happen to the state. You can have variables that roll back and some that don't. That's even better.

10:31:22 PM      

Bill Clementson writes about my recents posts on the continuation based web server framework in Sisc Scheme I'm writing.

First I should note that I'm writing this to learn more about the implementation side of this sort of server so much of it is the result of lots of tinkering. If I really wanted to just use such a framework I'd probably use Seaside which is a great system. Doing this I've learnt a lot more about continuations in Scheme. It's been a very worthwhile exercise.

To get things up quickly I used a Java based web server (Jetty). I wrote a handler in Scheme to do the continuation side of things. Jetty is very easy to use from Sisc and fit my goal of wanting to run the server from the REPL and be able to modify the handlers from the REPL in much the same manner as I use AllegroServe under Common Lisp. The server itself runs from a Sisc thread.

To answer Bills query, the non serializable stuff I had to prevent from being wrapped up in the continuation was the request and response objects. These hold references to sockets and streams. In Sisc global state is not held within the continuation object so I stored these objects inside a Scheme parameter object. These are basically thread local variables. Once assigned to these I reset the references to the request and response objects to #f. Code is something like:

(define current-request (make-parameter #f))
(define current-response (make-parameter #f))

(define (http-handler request response path-in-context path-params) (current-request request) (current-response response) (set! request #f) (set! response #f) ...)

To serialize the continuation I use:

; k is the continuation object
(call-with-serial-output-file filename
  (lambda (port) (serialize k port)))

And to get it back:

(call-with-serial-input-file filename deserialize)

Here's the code to something similar to seasides WACounter example:

(define-page (make-counter-page)
  (let ((counter 0))
    (lambda (kurl)
      `(html (head (title "Counter"))
	     (body (p ,(number->string counter))
		   (p ,(block-href (lambda () (inc! counter)) "++")
		      " "
		      ,(block-href (lambda () (dec! counter)) "--")))))))

(define (counter-app) (show (make-counter-page)) (counter-app))

(register-application counter-app)

Once you run the 'register-application' function you get the counter-app function assigned to an url. Accessing that url from the web browser will case the page produced by 'make-counter-page' to be shown. The calls to 'block-href' create 'a href' links to an url which when pressed runs the code in the lambda function and then returns back to the original page. These calls decrement or increment the counter and it gets redisplayed when the original page is shown.

The 'kurl' passed to the page is an url that can be used in the page that when accesse will return from the original show call where you can access form variables, etc. This example doesn't use that functionality. Note that you can use the back button and the state is successfully 'restored' to where it was when you were originally at that page. You can 'clone' the window by opening a new browser and entering the url you are currently at. This effectively makes a copy of the current state with two independant 'counter' variables.

10:17:12 PM      

Avi Bryant writes more about languages with macros. I'm addressing the Dylan related questions (Avi, I tried to leave a comment but the comment facility seems to be broken) below.

If I recall correctly, when it comes to macros that wrap several methods or generic functions then the browsing of things like 'implementors' in the browser shows the expanded method names and arguments it is specialised over. When clicking on those to see the source you get taken to the original unexpanded macro. If you wish to expand the macro you can highlite it and choose 'expand macro' from the menu - as long as you have access to the exported macros and methods that that macro depends upon.

I should add that Dylan is sometimes used as an example of how difficult implementing a macro system for infix languages really is. I don't know whether that is in fact true (never having implemented a Dylan macro system).

Version control is file based in Functional Developer. It allows the use of Visual Sourcesafe from the IDE.

Continuations would certainly be nice to have. It has one shot escaping continuations but these can't be passed outside the scope of the block they were created in. Not quite good enough for a Seaside clone.

Another thing I stumble against sometimes is the ability to seal classes and methods from extension. This allows the compiler to do further optimisations which is a good thing. Functional Developer has a 'color optimisation' mode which displays the different optimisations it will make to your code in a different color. So an unsealed generic will be green meaning it won't be inlined. A sealed generic may be grey meaning fully inlined, etc. The problem is that when deciding to seal or open things you don't know what a user of your class may want to extend some time later. I often hit against sealed generics and classes from built in libraries (like Duim) that I had wished were originally open. Note that this isn't the public interface but, for example, the win32 Duim implementation of that. I wanted to re-use portions of that to wrap my own Win32 controls. But as they were sealed I was unable to. I believe they've been opened in the not-yet-released version (that is, two years ago I was told they were changed to be opened).

Apart from those two small things, I really like Dylan as a language.

4:42:27 PM      

In the Sisc Scheme continuation based web framework I threw together I serialize continuations to disk when created and restore them on the web request. This allows me to shut the server down, restart it and still allow sessions to continue where they left off. It also allows multiple servers to handle the request. I had to do a bit of work to ensure that non-serializable stuff didn't appear in the continuation but it works quite well. The size of the continuation depends on the state stored but a series of small examples produces an average of about 12Kb on disk each.
12:04:58 AM      

Bill Clementson points to an interesting sounding paper titled "Web Interactions". The paper covers a whole lot of areas related to maintaining session state in a web server. Bills post has a good outline of the content.
12:00:29 AM      

© Copyright 2005 Chris Double.
September 2003
Sun Mon Tue Wed Thu Fri Sat
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        
Aug   Oct

Click here to visit the Radio UserLand website.

Listed on BlogShares

Click to see the XML version of this web page.

Click here to send an email to the editor of this weblog.