10

Can I redefine function in real-time without side effects? Is defn thread-safe?

Kirill Trofimov
  • 1,218
  • 1
  • 14
  • 26

2 Answers2

14

"thread safe enough for development, not for use in production."

using defn to redefine functions can break functions that call it if they are running while the call changes. it's ok in development because you can just restart after it breaks. It's safe enough if you can control when the function you are changing is called.

defn is a macro that resolves to somthing like

(def name (fn [args] (code-here)))

so it creates an instance of a function and then puts it into the root binding of a var. vars are a mutable data structure to allow for per-thread values. so when you call defn that assigns the base value that all threads will see. if another thread then changed the var to point at some other function it would change it's copy with out affecting any other threads. all the old threads would still see the old copy

When you re-bind the root value of a var by calling def again (through the defn macro) you change the value that every thread which has not set it's own value will see. threads that have decided to set their own values will continue to see the value they themselves set and not have to worry about the value being changed out from under them.


single thread no race

When a function call is made the current value of the var with the name of the function, as seen by the thread doing the calling (this is important), is used. so if the value of the var changes then all future calls will see the new value; but they will only see changes to the root binding or their own thread-local binding. so first the normal case with only a root binding:

user=> (defn foo [] 4)
#'user/foo
user=> (defn bar [] (foo))
#'user/bar
user=> (bar)
4
user=> (defn foo [] 6)
#'user/foo
user=> (bar)
6

two threads, still no race

then we run another thread and in that thread redefine foo to return 12 instead

user=> (.start (Thread. (fn [] (binding  [foo (fn [] 12)] (println (bar))))))
nil
user=> 12

the value of foo (as seen by bar) is still unchanged in the first thread (the one running the repl)

user=> (bar)
6
user=> 

two threads and a race condition

next we will change the value of the root binding out from under a thread with no local binding and see that the value of the function foo changes half way through a function running in another thread:

user=> (.start (Thread. (fn [] (println (bar)) 
                        (Thread/sleep 20000) 
                        (println (bar)))))                        
nil
user=> 6                ;foo at the start of the function

user=> (defn foo [] 7)  ;in the middle of the 20 seond sleep we redefine foo
#'user/foo
user=> 7                ; the redefined foo is used at the end of the function

If the change to foo (which is called indirectly) had changed the number of arguments this would have been a crash instead of a wrong answer (which is arguably better). At this point it is fairly clear that somthing needs to be done if we want to use vars and devn for changing our functions.


how to use vars with no race condition

You really may want functions to not change mid call so you can use a thread-local binding to protect your self from this by changing the function running in the new thread to save the current value of foo into its thread-local bindings:

user=> (.start (Thread. (fn [] (binding [foo foo] (println (bar)) 
                                                  (Thread/sleep 20000)
                                                  (println (bar))))))
nil
user=> 7

user=> (defn foo [] 9)
#'user/foo
user=> 7

The magic is in the expression (binding [foo foo] (code-that-uses-foo)) this could be read as "assign a thread local value to foo of the current value of foo" that way it stays consistent until the end of the binding form and into anything that is called from that binding form.


Clojure gives you choices, but you must choose

vars are good enough to hold your functions and redefine them to your hearts content while developing code. using code to automatically redefine functions very quickly on a deployed system using vars would be less wise. Not because vars are not thread safe, but because in this context vars are the wrong mutable structure to hold your function. Clojure has mutable structure for every use case and in the case of rapid automated editing of functions that need to stay consistent through the running of a transaction you would do better to hold you're functions in refs. What other language lets you choose the structure that holds your functions!*

  • not a real question, just about any functional language can do this
Arthur Ulfeldt
  • 90,827
  • 27
  • 201
  • 284
10

Yes, it's thread safe.... but it does have side effects. Hence you may get unexpected results depending on what you are trying to do.

In essence, defn on an existing function will rebind the corresponding var in the namespace.

This means that:

  • Future accesses to the var will get the new version of the function
  • Existing copies of the old function that were previously read from the var will not change

As long as you understand and are comfortable with that - you should be OK.

EDIT: In response to Arthur's comment, here's an example:

; original function
(defn my-func [x] (+ x 3))

; a vector that holds a copy of the original function
(def my-func-vector [my-func])

; testing it works
(my-func 2)
=> 5
((my-func-vector 0) 2)
=> 5

; now redefine the function
(defn my-func [x] (+ x 10))

; direct call to my-func uses the new version, but the vector still contains the old version....
(my-func 2)
=> 12
((my-func-vector 0) 2)
=> 5
mikera
  • 105,238
  • 25
  • 256
  • 415
  • the var is resolved for each and every call. existing functions will see the new value. the value of the var (and function) can change in the middle of execution. – Arthur Ulfeldt Mar 03 '11 at 19:39
  • @Arther, not quite true since copies of the function can exist which don't go through the var, I've posted an example which proves this..... – mikera Mar 03 '11 at 20:32
  • this only works if you are calling the functions directly. you cant use this to control functions called by the functions you call. you need bindings to solve this. – Arthur Ulfeldt Mar 03 '11 at 20:48
  • Sure Arthur, but you can create a race condition with any mutable state that is accessed concurrently :-) don't think that was quite what the OP was getting at..... I interpreted thread-safe in this context to mean "can be used safely as an atomic operation while other threads are running" – mikera Mar 05 '11 at 13:07