Skip to content

Commit 1377659

Browse files
committed
Try to import ns doc from namespace
1 parent 20de868 commit 1377659

File tree

1 file changed

+2
-231
lines changed

1 file changed

+2
-231
lines changed

src/piotr_yuxuan/closeable_map.clj

Lines changed: 2 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -1,234 +1,5 @@
1-
(ns piotr-yuxuan.closeable-map
2-
;; Manually keep this description in sync with relevant parts of the README.
3-
"In your project, require:
4-
5-
``` clojure
6-
(require '[piotr-yuxuan.closeable-map :as closeable-map :refer [close-with with-tag]])
7-
```
8-
9-
Define an application that can be started, and closed.
10-
11-
``` clojure
12-
(defn start
13-
\"Return a map describing a running application, and which values may
14-
be closed.\"
15-
[config]
16-
(closeable-map/closeable-map
17-
{;; Kafka producers/consumers are `java.io.Closeable`.
18-
:producer (kafka-producer config)
19-
:consumer (kafka-consumer config)}))
20-
```
21-
22-
You can start/stop the app in the repl with:
23-
24-
``` clojure
25-
(comment
26-
(def config (load-config))
27-
(def system (start config))
28-
29-
;; Stop/close all processes/resources with:
30-
(.close system)
31-
)
32-
```
33-
34-
It can be used in conjunction with `with-open` in test file to create
35-
well-contained, independent tests:
36-
37-
``` clojure
38-
(with-open [{:keys [consumer] :as app} (start config)]
39-
(testing \"unit test with isolated, repeatable context\"
40-
(is (= :yay/🚀 (some-business/function consumer)))))
41-
```
42-
43-
You could also use thi library while live-coding to stop and restart
44-
your application whenever a file is changed.
45-
46-
## More details
47-
48-
``` clojure
49-
(defn start
50-
\"Return a map describing a running application, and which values may
51-
be closed.\"
52-
[config]
53-
(closeable-map/closeable-map
54-
{;; Kafka producers/consumers are `java.io.Closeable`.
55-
:producer (kafka-producer config)
56-
:consumer (kafka-consumer config)
57-
58-
;; File streams are `java.io.Closeable` too:
59-
:logfile (io/output-stream (io/file \"/tmp/log.txt\"))
60-
61-
;; Closeable maps can be nested. Nested maps will be closed before the outer map.
62-
:backend/api {:response-executor (close-with (memfn ^ExecutorService .shutdown)
63-
(flow/utilization-executor (:executor config)))
64-
:connection-pool (close-with (memfn ^IPool .shutdown)
65-
(http/connection-pool {:pool-opts config}))
66-
67-
;; These functions receive their map as argument.
68-
::closeable-map/before-close (fn [m] (backend/give-up-leadership config m))
69-
::closeable-map/after-close (fn [m] (backend/close-connection config m))}
70-
71-
;; Any exception when closing this nested map will be swallowed
72-
;; and not bubbled up.
73-
:db ^::closeable-map/swallow {;; Connection are `java.io.Closeable`, too:
74-
:db-conn (jdbc/get-connection (:db config))}
75-
76-
;; Some libs return a zero-argument function which when called
77-
;; stops the server, like:
78-
:server (with-tag ::closeable-map/fn (http/start-server (api config) (:server config)))
79-
;; Gotcha: Clojure meta data can only be attached on 'concrete'
80-
;; objects; they are lost on literal forms (see above).
81-
:forensic ^::closeable-map/fn #(metrics/report-death!)
82-
83-
::closeable-map/ex-handler
84-
(fn [ex]
85-
;; Will be called for all exceptions thrown when closing this
86-
;; map and nested items.
87-
(println (ex-message ex)))}))
88-
```
89-
90-
When `(.close system)` is executed, it will:
91-
92-
- Recursively close all instances of `java.io.Closeable` and
93-
`java.lang.AutoCloseable`;
94-
- Recursively call all stop zero-argument functions tagged with
95-
`^::closeable-map/fn`;
96-
- Skip all nested `Closeable` under a `^::closeable-map/ignore`;
97-
- Silently swallow any exception with `^::closeable-map/swallow`;
98-
- Exceptions to optional `::closeable-map/ex-handler` in key or
99-
metadata;
100-
- If keys (or metadata) `::closeable-map/before-close` or
101-
`::closeable-map/after-close` are present, they will be assumed as
102-
a function which takes one argument (the map itself) and used run
103-
additional closing logic:
104-
105-
``` clojure
106-
(closeable-map
107-
{;; This function will be executed before the auto close.
108-
::closeable-map/before-close (fn [this-map] (flush!))
109-
110-
;; Kafka producers/consumers are java.io.Closeable
111-
:producer (kafka-producer config)
112-
:consumer (kafka-consumer config)
113-
114-
;; This function will be executed after the auto close.
115-
::closeable-map/after-close (fn [this-map] (garbage/collect!))})
116-
```
117-
118-
Some classes do not implement `java.lang.AutoCloseable` but present
119-
some similar method. For example instances of
120-
`java.util.concurrent.ExecutorService` can't be closed but they can be
121-
`.shutdown`:
122-
123-
``` clojure
124-
{:response-executor (close-with (memfn ^ExecutorService .shutdown)
125-
(flow/utilization-executor (:executor config)))
126-
:connection-pool (close-with (memfn ^IPool .shutdown)
127-
(http/connection-pool {:pool-opts config}))}
128-
```
129-
130-
You may also extend this library by giving new dispatch values to
131-
multimethod [[piotr-yuxuan.closeable-map/close!]]. Once evaluated,
132-
this will work accross all your code. The multimethod is dispatched on
133-
the concrete class of its argument:
134-
135-
``` clojure
136-
(import '(java.util.concurrent ExecutorService))
137-
(defmethod closeable-map/close! ExecutorService
138-
[x]
139-
(.shutdown ^ExecutorService x))
140-
141-
(import '(io.aleph.dirigiste IPool))
142-
(defmethod closeable-map/close! IPool
143-
[x]
144-
(.shutdown ^IPool x))
145-
```
146-
147-
## All or nothing
148-
149-
### No half-broken closeable map
150-
151-
You may also avoid partially open state when an exception is thrown
152-
when creating a `CloseableMap`. This is where `closeable-map*` comes
153-
handy. It outcome in one of the following:
154-
155-
- Either everything went right, and all inner forms wrapped by
156-
`closeable` correctly return a value; you get an open instance of `CloseableMap`.
157-
158-
- Either some inner form wrapped by `closeable` didn't return a
159-
closeable object but threw an exception instead. Then all
160-
`closeable` forms are closed, and finally the exception is
161-
bubbled up.
162-
163-
``` clojure
164-
(closeable-map*
165-
{:server (closeable* (http/start-server (api config)))
166-
:kafka {:consumer (closeable* (kafka-consumer config))
167-
:producer (closeable* (kafka-producer config))
168-
:schema.registry.url \"https://localhost\"}})
169-
```
170-
171-
### No half-broken state in general code
172-
173-
In some circumstances you may need to handle exception on the creation
174-
of a closeable map. If an exception happens during the creation of the
175-
map, values already evaluated will be closed. No closeable objects
176-
will be left open with no references to them.
177-
178-
For instance, this form would throw an exception:
179-
180-
``` clojure
181-
(closeable-map/closeable-map {:server (http/start-server (api config))
182-
:kafka {:consumer (kafka-consumer config)
183-
:producer (throw (ex-info \"Exception\" {}))}})
184-
;; => (ex-info \"Exception\" {})
185-
```
186-
187-
`with-closeable*` prevents that kind of broken, partially open states for its bindings:
188-
189-
``` clojure
190-
(with-closeable* [server (http/start-server (api config))
191-
consumer (kafka-consumer config)
192-
producer (throw (ex-info \"Exception\" {}))]
193-
;; Your code goes here.
194-
)
195-
;; Close consumer,
196-
;; close server,
197-
;; finally throw `(ex-info \"Exception\" {})`.
198-
```
199-
200-
You now have the guarantee that your code will only be executed if
201-
all these closeable are open. In the latter example an exception is
202-
thrown when `producer` is evaluated, so `consumer` is closed, then
203-
`server` is closed, and finally the exception is bubbled up. Your
204-
code is not evaluated. In the next example the body is evaluated,
205-
but throws an exception: all bindings are closed.
206-
207-
``` clojure
208-
(with-closeable* [server (http/start-server (api config))
209-
consumer (kafka-consumer config)
210-
producer (kafka-producer config)]
211-
;; Your code goes here.
212-
(throw (ex-info \"Exception\" {})))
213-
;; Close producer,
214-
;; close consumer,
215-
;; close server,
216-
;; finally throw `(ex-info \"Exception\" {})`.
217-
```
218-
219-
When no exception is thrown, leave bindings open and return like a
220-
normal `let` form. If you prefer to close bindings, use `with-open` as
221-
usual.
222-
223-
``` clojure
224-
(with-closeable* [server (http/start-server (api config))
225-
consumer (kafka-consumer config)
226-
producer (kafka-producer config)]
227-
;; Your code goes here.
228-
)
229-
;; All closeable in bindings stay open.
230-
;; => result
231-
```"
1+
(ns ^{:doc (clojure.core/slurp (clojure.java.io/file "README.md"))}
2+
piotr-yuxuan.closeable-map
2323
(:require [clojure.data]
2334
[clojure.walk :as walk]
2345
[potemkin :refer [def-map-type]])

0 commit comments

Comments
 (0)