Skip to content

Commit

Permalink
CCACHE-65 potential fix for cache stampede
Browse files Browse the repository at this point in the history
Signed-off-by: Sean Corfield <[email protected]>
  • Loading branch information
seancorfield committed Nov 9, 2024
1 parent a587ba0 commit c15b60d
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 14 deletions.
1 change: 1 addition & 0 deletions .clj-kondo/config.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{:lint-as {clojure.core.cache/defcache clojure.core/defrecord}}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ Developer Information
Change Log
====================

* Release 1.2.next in progress
* [CCACHE-65](http://clojure.atlassian.net/browse/CCACHE-65) Use `delay` in `lookup-or-miss` to avoid cache-stampede.
* Release 1.1.234 on 2024-02-19
* Update parent pom and `data.priority-map` versions
* Release 1.0.225 on 2021-12-06
Expand Down
28 changes: 14 additions & 14 deletions src/main/clojure/clojure/core/cache/wrapped.clj
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
Reads from the current version of the atom."
([cache-atom e]
(c/lookup @cache-atom e))
(force (c/lookup @cache-atom e)))
([cache-atom e not-found]
(c/lookup @cache-atom e not-found)))
(force (c/lookup @cache-atom e not-found))))

(def ^{:private true} default-wrapper-fn #(%1 %2))

Expand All @@ -51,23 +51,23 @@
([cache-atom e wrap-fn value-fn]
(let [d-new-value (delay (wrap-fn value-fn e))]
(loop [n 0
v (c/lookup (swap! cache-atom
c/through-cache
v (force (c/lookup (swap! cache-atom
c/through-cache
e
default-wrapper-fn
(fn [_] d-new-value))
e
default-wrapper-fn
(fn [_] @d-new-value))
e
::expired)]
::expired))]
(when (< n 10)
(if (= ::expired v)
(recur (inc n)
(c/lookup (swap! cache-atom
c/through-cache
(force (c/lookup (swap! cache-atom
c/through-cache
e
default-wrapper-fn
(fn [_] d-new-value))
e
default-wrapper-fn
(fn [_] @d-new-value))
e
::expired))
::expired)))
v))))))

(defn has?
Expand Down
25 changes: 25 additions & 0 deletions src/test/clojure/clojure/core/cache/wrapped_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

(ns clojure.core.cache.wrapped-test
(:require [clojure.core.cache.wrapped :as c]
[clojure.core.cache :as cache]
[clojure.test :refer [deftest is]]))

(deftest basic-wrapped-test
Expand Down Expand Up @@ -40,3 +41,27 @@
(recur (+ 1 n)))))
(println "ttl test completed" limit "calls in"
(- (System/currentTimeMillis) start) "ms")))

(deftest cache-stampede
(let [thread-count 100
cache-atom (-> {}
(cache/ttl-cache-factory :ttl 120000)
(cache/lu-cache-factory :threshold 100)
(atom))
latch (java.util.concurrent.CountDownLatch. thread-count)
invocations-counter (atom 0)
values (atom [])]
(dotimes [_ thread-count]
(.start (Thread. (fn []
(swap! values conj
(c/lookup-or-miss cache-atom "my-key"
(fn [_]
(swap! invocations-counter inc)
(Thread/sleep 3000)
"some value")))
(.countDown latch)))))

(.await latch)
(is (= 1 (deref invocations-counter)))
(doseq [v @values]
(is (= "some value" v)))))

0 comments on commit c15b60d

Please sign in to comment.