[toronto-lisp] state machine macro

doug at hcsw.org doug at hcsw.org
Wed Apr 8 20:40:49 UTC 2009

Hi Paul,

On Wed, Apr 08, 2009 at 03:21:16PM -0400 or thereabouts, Paul Tarvydas wrote:
> Yesterday, I talked about the state machine macro I used, but forgot to show the actual macro.  If anybody wants to see it, email me.

Thanks for showing us your approach to state machines. I found it
really interesting and seeing some use cases and the expansion
was enough for me to mostly figure out how your macro works (I think).

In my webserver program Antiweb I have a different approach to
state machines. A state machine is a closure which you can send
messages to by funcalling it. It will return a new closure which
represents the new state of the state machine.

Here is the macro:

(defmacro fsm (name &rest body)
  (unless (symbolp name) (error "fsm needs symbol for name"))
  `(let (,name)
     (declare (ignorable ,name))
     (setq ,name (lambda () , at body))))

And here is how to use it in your light switch use case:

* (fsm on
    (format t "Turning OFF~%")
    (fsm off
      (format t "Turning ON~%")

#<Interpreted Function "FSM ON" {5812CF11}>

Now we can invoke it a few times:

* (funcall *)
Turning OFF
#<Interpreted Function "FSM ON" {5812D7A9}>

* (funcall *)
Turning ON
#<Interpreted Function "FSM ON" {5812CF11}>

* (funcall *)
Turning OFF
#<Interpreted Function "FSM ON" {5812E809}>

Notice that the default on state is the same closure (address 5812CF11)
but that the off state is newly consed every time.

In Antiweb I use this macro for managing the state of connections.
For example, a connection will be in the state "waiting for HTTP
headers" until it receives the terminator "\r\n\r\n" after which
it will go into the "reading HTTP body" state if it was a POST
request. Once the HTTP body is read, it will go back to the
"waiting for HTTP headers" state to support HTTP/1.1 persistent

You can download Antiweb and read our extensive manual here:


Here is list of some Antiweb features if anyone is interested:

* Uses either the kqueue() or epoll() stateful event APIs
  in level-triggered mode
* 10,000 inactive keepalive connections consume about 3M
  of userspace memory
* Connection sockets are transferred between processes with
  sendmsg() for privilege-separated vhosts and SMP/multi-core
* Files are mmap()ed to avoid copying file data to userspace
* Vectored IO (aka scatter-gather IO) used everywhere
* Vhosts can be chroot()ed into separate roots
* Compromised vhosts can't intercept log messages created
  by other vhosts or steal connections destined to other vhosts
* Every Antiweb process is separately exec()ed (not fork()ed) so
  each has its own unique memory randomisation offsets (on systems
  that do this)
* Write direction of socket is shutdown() when closing connection
  (required for HTTP/1.1 persistent connections)
* Native IPv6 support
* Conditional CGI/1.1 compliance
* CGI processes can be restricted with rlimits
* Directory listings
* Download resuming
* Entity tags
* Javascript and CSS content is minimised, gzip encoded,
  cached to the filesystem and served as static content
* Apache-like regexp-based rewrite module
* Memcached-like memory cache module
* BerkeleyDB integration


-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 195 bytes
Desc: not available
URL: <https://mailman.common-lisp.net/pipermail/toronto-lisp/attachments/20090408/1bca4f1d/attachment.sig>

More information about the toronto-lisp mailing list