diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn new file mode 100644 index 0000000..f40cd26 --- /dev/null +++ b/.clj-kondo/config.edn @@ -0,0 +1 @@ +{:lint-as {clojure.core.cache/defcache clojure.core/defrecord}} diff --git a/README.md b/README.md index 228dc8b..bf79cc7 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/main/clojure/clojure/core/cache/wrapped.clj b/src/main/clojure/clojure/core/cache/wrapped.clj index 3b825aa..1aba3f9 100644 --- a/src/main/clojure/clojure/core/cache/wrapped.clj +++ b/src/main/clojure/clojure/core/cache/wrapped.clj @@ -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)) @@ -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? diff --git a/src/test/clojure/clojure/core/cache/wrapped_test.clj b/src/test/clojure/clojure/core/cache/wrapped_test.clj index 49deaa1..c9ca4e1 100644 --- a/src/test/clojure/clojure/core/cache/wrapped_test.clj +++ b/src/test/clojure/clojure/core/cache/wrapped_test.clj @@ -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 @@ -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)))))