sakh1979
sakh1979

Reputation: 119

Using atom and future is creating race condition in my Clojure program

I have a web service written in Clojure. It has a simple GET method implemented, which returns back a JSON object representing the router's current position and different time counters.

My code has a bunch of atoms to keep track of time. Each atom represents different activities a machine might be doing at a given time. For example: calibrating, idle, stuck, or working:

(def idle-time (atom 0))
(def working-time (atom 0))
(def stuck-time (atom 0))
(def calibration-time (atom 0))

Towards the end I have a loop that updates the position and time counters every 15 seconds:

(defn update-machine-info []
  (let [machine-info (parse-data-files)]
    (update-time-counters machine-info)
    (reset! new-state (merge machine-info
                             {:idleCounter        @idle-time
                              :workingCounter     @working-time
                              :stuckCounter       @stuck-time
                              :calibrationCounter @calibration-time}))))

(loop []
  (future
    (Thread/sleep 15000)
    (update-machine-info)
    (recur)))

Currently this code runs into race condition, meaning the position and time counters are not updating. However, the Web Service still responses back a proper JSON response, albeit with old values.

Web Service is using Cheshire to generate map into JSON, here my GET implementation:

(defroutes app-routes
  (GET "/" [] (resource :available-media-types ["application/json"]
                        :handle-ok (generate-string (get-machine-information))))
  (route/not-found "Not Found"))

Should I be using refs instead of atoms? Am I using future correctly? Is (Thread/sleep 15000) causing the issue, because atoms are async?

Please let me know if you see an obvious bug in my code.

Upvotes: 1

Views: 209

Answers (2)

Tim X
Tim X

Reputation: 4235

I think what is happening is that the process where you call your futures is terminating before your futures actually execute. For what your doing, futures are probably the wrong type of construct. I also don't think your loop future recor sequence is doing what you think.

There is a lot of guesswork here as it isn't clear exactly where you are actually defining and calling your code. I think you probably want to use something like agents, which you need to setup in the root process and then send a message to them in your handler before you return your response.

Upvotes: 0

StianE
StianE

Reputation: 3175

I don't think you can reliably recur inside a future to a loop that's outside the future (not completely sure), but why not try something like this instead?

(future 
  (loop []
    (Thread/sleep 15000)
    (update-machine-info)
    (recur)))

That way loop/recur stays within the same thread.

Other than that, it's possible that if update-machine-counters throws an exception the loop will stop, and you'll never see the exception because the future is never dereferenced. An agent ( http://clojure.org/agents ) might be better suited for this, since you can register an error handler.

Upvotes: 2

Related Questions