Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is defn thread-safe?

Tags:

clojure

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

like image 238
Kirill Trofimov Avatar asked Mar 03 '11 13:03

Kirill Trofimov


1 Answers

"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
like image 196
Arthur Ulfeldt Avatar answered Jan 04 '23 12:01

Arthur Ulfeldt