Tuesday, September 8, 2009

Hot Code Swapping with Clojure

Reading the Clojure mailing list, you'll notice that people frequently ask about updating code at runtime without restarting their program. Erlang includes this feature out of the box and has dubbed it, "Hot Code Swapping". It's not baked into Clojure nor is it regularly advertised as a feature, but it's easy to do and potentially very useful.

Before we go forward with an example, I'd like to remind you that this isn't Erlang, and that yanking the rug out from under a library and shoving in a new one could potentially lead to disastrous results if you haven't planned ahead in your program design.

If your library is purely functional with no side effects, then updating it at runtime is going to be much safer than swapping in a file containing mutable data. There's also an issue of atomicity to consider. I've come to find out that reloading a library is not an atomic operation. This could potentially cause some issues depending on your application structure. The def function and defn macro are both atomic which will prevent the possibility of partially defined functions creeping in, but you'll need to watch out for functions that depend on each other being temporarily out of date. With the warnings out of the way, let's get to it.

The easiest way to update a running program is to embed a remote REPL into your code. This is a relatively simple thing to do since Clojure's builtin repl function is designed to allow custom input and output streams. The clojure.contrib.server-socket library actually includes some code to assist with spawning a remote REPL, but I tend to take the hard road everywhere, so I just rolled my own solution.
; file: repl_server.clj
(ns repl-server
  (:import
     (java.net ServerSocket Socket)
     (java.io InputStreamReader PrintWriter)
     (java.util.concurrent ThreadPoolExecutor TimeUnit LinkedBlockingQueue)
     (java.net BindException)
     (clojure.lang LineNumberingPushbackReader)))

(defn bind-server [port backlog]
  "binds to the given port with the backlog specified"
   (new #^ServerSocket ServerSocket port backlog))

(defn server-loop [tasklet socket min-threads max-threads]
  "accepts connections and delegates them to the specified task"
  (let [exec (ThreadPoolExecutor. min-threads max-threads 60 TimeUnit/SECONDS
                                  (LinkedBlockingQueue.))]
    (loop []
      (let [accepted-socket (.accept #^ServerSocket socket)]
        (.submit exec #^Callable #(with-open [accepted-socket accepted-socket]
                                             (tasklet accepted-socket))))
      (recur))))

(defn spawn-repl [socket]
  "spawns a repl on a given socket"
  (let [input (new LineNumberingPushbackReader 
                   (new InputStreamReader (.getInputStream socket)))
        output (new PrintWriter (.getOutputStream socket) true)]
    (binding [*in*  input
              *out* output
              *err* output]
      (clojure.main/repl))))

(def #^ServerSocket *ss* (bind-server 12345 25))
(def repl-server (future (server-loop spawn-repl *ss* 5 100)))
Here we have a simple REPL server ready for embedding into any program we see fit. It runs in a loop accepting connections and then invoking spawn-repl for any client that connects. We use the binding form to set *in*, *out*, and *err* temporarily and then return them to their original values. We set the server to run on port 12345 and then run it in it's own thread via a call to future. It's worth noting that setting the port and actually invoking the server would be better done elsewhere, but this is just a trivial example, so don't crucify me.

Next we'll create a stupidly simple library which we'll be hot swapping.
; file: greeting.clj
(ns greeting)

(def message "hello world")

(defn print-message []
  (println "message:" message))
We follow this with a main file that ties it all together.
(ns main
  (:use greeting
        repl-server))

(defn main []
  (loop []
    (print-message)
    (Thread/sleep 1000)
    (recur)))

(main)
The main function simply runs in an infinite loop calling print-message every one second. From here we're ready to test some hot code swapping.
$ clj main.clj
message: hello world
message: hello world
...
So far so good, now let's connect to the REPL.
$ nc localhost 12345
clojure.core=> (ns greeting)
nil
greeting=> (def message "hola amigo!")
#'greeting/message
At this point the main window is now printing the updated message.
message: hola amigo!
message: hola amigo!
...
Now let's make a change to greeting.clj.
; file: greeting.clj
(ns greeting)

(def message "8675309")

(defn print-message []
  (println "jenny:" message))
With the new file in place, we request a reload. Keep in mind that this isn't atomic (as previously stated), but it is convenient for loading larger chunks of code.
$ nc localhost 12345
clojure.core=> (require 'greeting :reload)
The output of our main loop confirms that the reload worked as intended.
jenny: 8675309
jenny: 8675309
jenny: 8675309
...
This has obviously been a dead-simple example, but it should demonstrate the flexibility that Clojure provides when it comes to changing things at runtime.

1 comment:

  1. What about redefining structures? Are the instances updated? You can do it in CLOS

    ReplyDelete