Reputation: 47609
I have an atom which stores current and historical event counts.
(def buckets (atom (list)))
It starts as 20 empty values.
(reset! buckets (apply list (repeat 20 0)))
=> (0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
The head bucket gets incremented for each event.
(defn- inc-bucket [buckets]
(conj (rest buckets) (inc (first buckets))))
(defn event-happened [] (swap! buckets inc-bucket))
=> (event-happened)
(1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
It gets shifted once a second to give a history of counts.
(defn- shift-buckets [buckets]
(conj (drop-last buckets) 0))
(once-a-second-run (swap! buckets shift-bucket))
=> (swap! buckets shift-bucket)
(0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
This all works fine most of the time. But occasionally (say once a week) I find that the length of buckets
has been reset to 5, not the correct value (20 in this example).
=> @buckets
(0 1 0 0 0)
The only place buckets
is used is in these functions and with a deref
to observe the value.
Something went wrong, and I can't work out how or where. The only place buckets
is modified is during the two above swap!
s and as I think they're being used correctly. Any ideas?
Upvotes: 1
Views: 101
Reputation: 47609
I found the answer. Laziness and unhandled exceptions in atoms. There is no magic.
user=> (reset! buckets (apply list (repeat 20 0)))
(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
user=> (dotimes [_ 99999] (swap! buckets shift-buckets))
nil
user=> @buckets
StackOverflowError clojure.core/map/fn--4557 (core.clj:2627)
user=> (type @buckets)
clojure.lang.Cons
This had showed up in the log, but I hadn't seen it (there's a lot else going on). But here's the crazy thing:
user=> @buckets ; again
(0 0)
I don't know why it would give that value. It's not because a cons cell has a head and a tail, because in production that was (0 0 0 0 0)
. Either way it's undefined behaviour so there's no point guessing.
To fix it, the list must be realised each time.
user=> (defn- shift-buckets-nonlazy [buckets]
#_=> (apply list (conj (drop-last buckets) 0)))
#'user/shift-buckets-nonlazy
user=> (reset! buckets (apply list (repeat 20 0)))
(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
user=> (dotimes [_ 999999] (swap! buckets shift-buckets-nonlazy))
nil
user=> (type @buckets)
clojure.lang.PersistentList
user=> @buckets
(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
Upvotes: 3
Reputation: 91907
The problem has to be in code you haven't posted: everything in your question looks perfectly correct, and can't cause the behavior you describe.
Upvotes: 0