Skip to content

Commit c15b60d

Browse files
committed
CCACHE-65 potential fix for cache stampede
Signed-off-by: Sean Corfield <[email protected]>
1 parent a587ba0 commit c15b60d

File tree

4 files changed

+42
-14
lines changed

4 files changed

+42
-14
lines changed

.clj-kondo/config.edn

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{:lint-as {clojure.core.cache/defcache clojure.core/defrecord}}

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ Developer Information
173173
Change Log
174174
====================
175175

176+
* Release 1.2.next in progress
177+
* [CCACHE-65](http://clojure.atlassian.net/browse/CCACHE-65) Use `delay` in `lookup-or-miss` to avoid cache-stampede.
176178
* Release 1.1.234 on 2024-02-19
177179
* Update parent pom and `data.priority-map` versions
178180
* Release 1.0.225 on 2021-12-06

src/main/clojure/clojure/core/cache/wrapped.clj

+14-14
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
3030
Reads from the current version of the atom."
3131
([cache-atom e]
32-
(c/lookup @cache-atom e))
32+
(force (c/lookup @cache-atom e)))
3333
([cache-atom e not-found]
34-
(c/lookup @cache-atom e not-found)))
34+
(force (c/lookup @cache-atom e not-found))))
3535

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

@@ -51,23 +51,23 @@
5151
([cache-atom e wrap-fn value-fn]
5252
(let [d-new-value (delay (wrap-fn value-fn e))]
5353
(loop [n 0
54-
v (c/lookup (swap! cache-atom
55-
c/through-cache
54+
v (force (c/lookup (swap! cache-atom
55+
c/through-cache
56+
e
57+
default-wrapper-fn
58+
(fn [_] d-new-value))
5659
e
57-
default-wrapper-fn
58-
(fn [_] @d-new-value))
59-
e
60-
::expired)]
60+
::expired))]
6161
(when (< n 10)
6262
(if (= ::expired v)
6363
(recur (inc n)
64-
(c/lookup (swap! cache-atom
65-
c/through-cache
64+
(force (c/lookup (swap! cache-atom
65+
c/through-cache
66+
e
67+
default-wrapper-fn
68+
(fn [_] d-new-value))
6669
e
67-
default-wrapper-fn
68-
(fn [_] @d-new-value))
69-
e
70-
::expired))
70+
::expired)))
7171
v))))))
7272

7373
(defn has?

src/test/clojure/clojure/core/cache/wrapped_test.clj

+25
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
(ns clojure.core.cache.wrapped-test
1010
(:require [clojure.core.cache.wrapped :as c]
11+
[clojure.core.cache :as cache]
1112
[clojure.test :refer [deftest is]]))
1213

1314
(deftest basic-wrapped-test
@@ -40,3 +41,27 @@
4041
(recur (+ 1 n)))))
4142
(println "ttl test completed" limit "calls in"
4243
(- (System/currentTimeMillis) start) "ms")))
44+
45+
(deftest cache-stampede
46+
(let [thread-count 100
47+
cache-atom (-> {}
48+
(cache/ttl-cache-factory :ttl 120000)
49+
(cache/lu-cache-factory :threshold 100)
50+
(atom))
51+
latch (java.util.concurrent.CountDownLatch. thread-count)
52+
invocations-counter (atom 0)
53+
values (atom [])]
54+
(dotimes [_ thread-count]
55+
(.start (Thread. (fn []
56+
(swap! values conj
57+
(c/lookup-or-miss cache-atom "my-key"
58+
(fn [_]
59+
(swap! invocations-counter inc)
60+
(Thread/sleep 3000)
61+
"some value")))
62+
(.countDown latch)))))
63+
64+
(.await latch)
65+
(is (= 1 (deref invocations-counter)))
66+
(doseq [v @values]
67+
(is (= "some value" v)))))

0 commit comments

Comments
 (0)