-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathstack.clj
More file actions
162 lines (137 loc) · 5.03 KB
/
stack.clj
File metadata and controls
162 lines (137 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
(ns tech.v3.resource.stack
"Implementation of stack based resource system. Simple, predictable, deterministic,
and applicable to most problems. Resource contexts are sequences of resources that
need to be, at some point, released."
(:require [clojure.tools.logging :as log])
(:import [java.lang Runnable]
[java.io Closeable]
[java.lang AutoCloseable]
[clojure.lang IFn]
[java.util ArrayList Collections]))
(set! *warn-on-reflection* true)
(defonce ^{:dynamic true
:tag ArrayList} *resource-context* (ArrayList.))
(defonce ^:dynamic *bound-resource-context?* false)
(def ^:dynamic *resource-debug-double-free* nil)
(defn do-release [item]
(when item
(try
(cond
(instance? Runnable item)
(.run ^Runnable item)
(instance? Closeable item)
(.close ^Closeable item)
(instance? AutoCloseable item)
(.close ^AutoCloseable item)
(instance? IFn item)
(item)
:else
(throw (Exception. (format "Item is not runnable, closeable, or an IFn: %s"
(type item)))))
(catch Throwable e
(log/errorf e "Failed to release %s" item)))))
(defn track
"Begin tracking this resource. Resource be released when current resource context
ends. If the item satisfies the PResource protocol, then it can be tracked
itself. Else the dispose function is tracked."
([item dispose-fn]
(when (and *resource-debug-double-free*
(some #(identical? item (first %)) *resource-context*))
(throw (ex-info "Duplicate track detected; this will result in a double free"
{:item item})))
(when-not *bound-resource-context?*
(log/warn "Stack resource tracking used but no resource context bound.
This is probably a memory leak."))
(locking *resource-context*
(.add *resource-context* [item dispose-fn]))
item)
([item]
(track item item)))
(defn ignore-resources
"Ignore these resources for which pred returns true and do not track them.
They will not be released unless added again with track"
[pred]
(locking *resource-context*
(let [^ArrayList retval (.clone *resource-context*)]
(.removeIf *resource-context* (reify java.util.function.Predicate
(test [this val] (boolean (pred (first val))))))
(.removeIf retval (reify java.util.function.Predicate
(test [this val] (boolean (not (pred (first val)))))))
retval)))
(defn ignore
"Ignore specifically this resource."
[item]
(ignore-resources #(= item %))
item)
(defn release
"Release this resource and remove it from tracking. Exceptions propagate to callers."
[item]
(when item
(reduce (fn [acc entry]
(do-release (second entry)))
nil
(ignore-resources #(= item %)))))
(defn release-resource-seq
"Release a resource context returned from return-resource-context."
[res-ctx & {:keys [pred]}]
(Collections/reverse res-ctx)
(if pred
(reduce (fn [acc entry]
(when (pred (nth entry 0))
(do-release (nth entry 1))))
nil res-ctx)
(reduce (fn [acc entry]
(do-release (nth entry 1)))
nil res-ctx)))
(defn release-current-resources
"Release all resources matching either a predicate or all resources currently tracked.
Returns any exceptions that happened during release but continues to attempt to
release anything else in the resource list."
([pred]
(->> (if pred
(ignore-resources pred)
(locking *resource-context*
(let [rv (.clone *resource-context*)]
(.clear *resource-context*)
rv)))
(release-resource-seq)))
([] (release-current-resources nil)))
(defmacro with-resource-context
"Begin a new resource context. Any resources added while this context is open will be
released when the context ends."
[& body]
`(with-bindings {#'*resource-context* (ArrayList.)
#'*bound-resource-context?* true}
(try
~@body
(finally
(release-current-resources)))))
(defn ^:no-doc alist
^ArrayList [data]
(if data
(ArrayList. ^java.util.Collection data)
(ArrayList.)))
(defmacro with-bound-resource-seq
"Run code and return both the return value and the (updated,appended) resources
created.
Returns:
{:return-value retval
:resource-seq resources}"
[resource-seq & body]
;;It is important the resources sequences is a list.
`(with-bindings {#'*resource-context* (alist ~resource-seq)
#'*bound-resource-context?* true}
(try
(let [retval# (do ~@body)]
{:return-value retval#
:resource-seq *resource-context*})
(catch Throwable e#
(release-current-resources)
(throw e#)))))
(defmacro return-resource-seq
"Run code and return both the return value and the resources the code created.
Returns:
{:return-value retval
:resource-seq resources}"
[& body]
`(with-bound-resource-seq [] ~@body))