Skip to content

Commit d73ff1a

Browse files
committed
Tracing GC.
Based partially on datacrypt-project#24 Rewritten to primarily use core.async. * src/hitchhiker/tree/konserve.cljc (create-id): new function; prepends the current timestamp as hex to the UUID key. (KonserveBackend.-write-node): use create-id to generate the storage ID. * src/hitchhiker/tree/tracing-gc/konserve.cljc: new namespace. * src/hitchhiker/tree/tracing-gc.cljc: new namespace. * .gitignore: ignore IntelliJ files. * project.clj: update konserve to 0.6.0-SNAPSHOT.
1 parent 5775714 commit d73ff1a

File tree

5 files changed

+98
-3
lines changed

5 files changed

+98
-3
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ pom.xml.asc
1313
*.swo
1414
*.swn
1515
*~
16+
/.idea
17+
*.iml
18+
profiles.clj
19+
/scripts

project.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
[com.taoensso/carmine "2.12.2" :scope "provided"]
1010
[org.clojure/core.rrb-vector "0.0.14"]
1111
[org.clojure/core.cache "0.7.2"]
12-
[io.replikativ/konserve "0.5.1"]]
12+
[io.replikativ/konserve "0.6.0-SNAPSHOT"]]
1313
:aliases {"bench" ["with-profile" "profiling" "run" "-m" "hitchhiker.bench"]}
1414
:jvm-opts ["-server" "-Xmx3700m" "-Xms3700m"]
1515
:profiles {:test
@@ -42,7 +42,7 @@
4242
:compiler {:main hitchhiker.tree.core
4343
:asset-path "js/out"
4444
:output-to "resources/public/js/core.js"
45-
:output-dir "resources/public/js/out" }}
45+
:output-dir "resources/public/js/out"}}
4646
;; inspired by datascript project.clj
4747
{:id "test"
4848
:source-paths ["src" "test"]

src/hitchhiker/tree/bootstrap/konserve.cljc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@
5353
[key]
5454
(ha/promise-chan key))
5555

56+
(defn create-id
57+
"Generate a storage ID from a content UUID.
58+
59+
Adds a (hexadecimal) timestamp in milliseconds to the start of the
60+
key; this is so the GC can properly delete keys that don't exist in
61+
the tree anymore"
62+
[uuid]
63+
(format "%016x.%s" (System/currentTimeMillis) uuid))
64+
5665
(defrecord KonserveAddr [store last-key konserve-key storage-addr]
5766
n/INode
5867
(-last-key [_] last-key)
@@ -90,7 +99,7 @@
9099
(ha/go-try
91100
(swap! session update-in [:writes] inc)
92101
(let [pnode (encode node)
93-
id (h/uuid pnode)
102+
id (create-id (h/uuid pnode))
94103
ch (k/assoc-in store [id] node)]
95104
(ha/<? ch)
96105
(konserve-addr store
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
(ns hitchhiker.tree.tracing-gc
2+
(:require [clojure.core.async :as async]
3+
[clojure.tools.logging :as log]
4+
[hitchhiker.tree :as hh]
5+
[hitchhiker.tree.node :as n]
6+
[hitchhiker.tree.utils.async :as ha]))
7+
8+
(defprotocol IGCScratch
9+
(observe-addr! [this addr] "Marks the given addr as being currently active")
10+
(observed? [this addr] "Returns true if the given addr was observed"))
11+
12+
(defmacro do-<!
13+
"Force into an async taking call.
14+
15+
Evaluates to <! when in an async backend.
16+
Wraps form in a thread when non-async."
17+
[& form]
18+
(ha/if-async?
19+
`(async/<! ~@form)
20+
`(async/<! (async/thread ~@form))))
21+
22+
(defn trace-gc!
23+
"Does a tracing GC and frees up all unused keys.
24+
This is a simple mark-sweep algorithm.
25+
26+
gc-scratch should be an instance of IGCScratch
27+
gc-roots should be a list of the roots of currently active trees.
28+
all-keys should be a core.async channel that will contain every key in storage.
29+
delete-fn will be called on every key that should be deleted during the sweep phase. It is expected to return a channel that yields when the item is deleted."
30+
[gc-scratch gc-roots all-keys delete-fn]
31+
(let [mark-phase (async/go-loop [roots gc-roots]
32+
(when-let [root (first roots)]
33+
(loop [nodes [root]]
34+
(when-let [node (first nodes)]
35+
(log/debug :task ::trace-gc! :phase :marking :visiting-node (async/poll! (:storage-addr node)))
36+
(let [node (if (hh/resolved? node)
37+
node
38+
(do-<! (do
39+
(log/debug :task ::trace-gc! :phase :marking :resolve-node node)
40+
(n/-resolve-chan node))))
41+
nodes (if (hh/index-node? node)
42+
(into (subvec nodes 1) (:children node))
43+
(subvec nodes 1))]
44+
(when-let [address (async/poll! (:storage-addr node))]
45+
(async/<! (observe-addr! gc-scratch address)))
46+
(recur nodes))))
47+
(recur (rest roots))))]
48+
(async/go
49+
(async/<! mark-phase)
50+
(loop []
51+
(when-let [address (async/<! all-keys)]
52+
(when-not (async/<! (observed? gc-scratch address))
53+
(async/<! (delete-fn address)))
54+
(recur))))))
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
(ns hitchhiker.tree.tracing-gc.konserve
2+
(:require [clojure.tools.logging :as log]
3+
[hitchhiker.tree.tracing-gc :as gc]
4+
[konserve.core :as k]
5+
#?(:clj [clojure.core.async :as async]
6+
:cljs [cljs.core.async :as async :include-macros true])))
7+
8+
(defn- within-epoch?
9+
[address epoch]
10+
(if-let [addr-ts (some-> (when (string? address)
11+
(re-matches #"([0-9a-f]{16})\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" address))
12+
(second))]
13+
(not (pos? (compare addr-ts epoch)))
14+
true))
15+
16+
(defrecord KonserveGCScratch [store epoch]
17+
gc/IGCScratch
18+
(observe-addr! [_ addr]
19+
(log/debug :task ::gc/observe-addr! :addr addr)
20+
(k/assoc store addr :marked))
21+
22+
(observed? [_ addr]
23+
(async/go
24+
(log/debug :task ::gc/observed? :phase :begin :addr addr)
25+
(let [result (or (within-epoch? addr epoch)
26+
(= :marked (async/<! (k/get store addr))))]
27+
(log/debug :task ::gc/observed? :phase :end :addr addr :result result)
28+
result))))

0 commit comments

Comments
 (0)