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.

21 comments:

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

    ReplyDelete
  2. Alot of blogs I see these days don't really provide anything that I'm interested in, but I'm most definitely interested in this one. Just thought that I would post and let you know. Nice! thank you so much! Thank you for sharing.
    lenovo service center in chennai
    lenovo mobile service center in chennai
    lenovo service centre chennai

    ReplyDelete
  3. Thanks for sharing this great article. It made me understand few things about this concept which I never knew before. Keep posting such great articles so that I gain from it. Java Training in Chennai | J2EE Training in Chennai | Advanced Java Training in Chennai | Core Java Training in Chennai | Java Training institute in Chennai

    ReplyDelete
  4. Such a great information for blogger iam a professional blogger thanks…

    Upgrade your career Learn Oracle Training from industry experts gets complete hands on Training, Interview preparation, and Job Assistance at Softgen Infotech.

    ReplyDelete
  5. This piece of information's are really Wonderful...The information's are helpful to enhance the careers...Good Works!!!
    Java training in chennai | Java training in annanagar | Java training in omr | Java training in porur | Java training in tambaram | Java training in velachery

    ReplyDelete
  6. Wonderful article, very useful and well explanation. Your post is extremely incredible. I feel really happy to have seen your webpage and look forward to so many more entertaining times reading here. Thanks once more for all the details.
    Java Training in Chennai

    Java Training in Velachery

    Java Training inTambaram

    Java Training in Porur

    Java Training in Omr

    Java Training in Annanagar

    ReplyDelete
  7. very well explained. I would like to thank you for the efforts you had made for writing this awesome article. This article inspired me to read more. keep it up.
    DevOps Training in Chennai

    DevOps Course in Chennai

    ReplyDelete
  8. It’s always so sweet and also full of a lot of fun for me personally and
    my office colleagues to search your blog a minimum of thrice in a
    week to see the new guidance you have got.
    Hadoop admin training in Chennai
    software testing institute in Chennai
    javascript course in Chennai

    ReplyDelete