├── .gitignore
├── .gitmodules
├── LICENSE
├── README.markdown
├── architecture.png
├── project.clj
├── src
└── com
│ └── keminglabs
│ └── zmq_async
│ └── core.clj
├── test
└── com
│ └── keminglabs
│ └── zmq_async
│ └── t_core.clj
└── vendor
├── poms
├── linux64
│ └── pom.xml
└── osx64
│ └── pom.xml
└── release.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vendor/jzmq"]
2 | path = vendor/jzmq
3 | url = https://github.com/zeromq/jzmq.git
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Kevin Lynagh
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * The name Kevin Lynagh may not be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL KEVIN LYNAGH BE LIABLE FOR ANY DIRECT,
21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # ZeroMQ Async
2 |
3 | ZeroMQ is a message-oriented socket system that supports many communication styles (request/reply, publish/subscribe, fan-out, &c.) on top of many transport layers with bindings to many languages.
4 | However, ZeroMQ sockets are not thread safe---concurrent usage typically requires explicit locking or dedicated threads and queues.
5 | This library handles all of that for you, taking your ZeroMQ sockets and hiding them behind thread safe core.async channels.
6 |
7 | [Quick start](#quick-start) | [Caveats](#caveats) | [Architecture](#architecture) | [Thanks!](#thanks) | [Other Clojure ZMQ libs](#other-clojure-zeromq-libraries)
8 |
9 | ## Quick start
10 |
11 | Add to your `project.clj`:
12 |
13 | [com.keminglabs/zmq-async "0.1.0"]
14 |
15 | Your system should have ZeroMQ 3.2 installed:
16 |
17 | brew install zeromq
18 |
19 | or
20 |
21 | apt-get install libzmq3
22 |
23 | This library provides one function, `register-socket!`, which associates a ZeroMQ socket with core.async channel(s) `in` (into which strings or byte arrays are written) and/or `out` (whence byte arrays).
24 | Writing a Clojure collection of strings and/or byte arrays sends a multipart message; received multipart messages are put on core.async channels as a vector of byte arrays.
25 |
26 | The easiest way to get started is to have zmq-async create sockets and the backing message pumps automagically for you:
27 |
28 | ```clojure
29 | (require '[com.keminglabs.zmq-async.core :refer [register-socket!]]
30 | '[clojure.core.async :refer [>! ! s-in "pong"))
43 | (close! s-in))
44 |
45 | (go (dotimes [_ n]
46 | (>! c-in "ping")
47 | (println (String. (! !! alts!!]]
4 | [clojure.core.match :refer [match]]
5 | [clojure.set :refer [subset?]]
6 | [clojure.edn :refer [read-string]]
7 | [clojure.set :refer [map-invert]])
8 | (:import java.util.concurrent.LinkedBlockingQueue
9 | (org.zeromq ZMQ ZContext ZMQ$Socket ZMQ$Poller)))
10 |
11 | ;;Some terminology:
12 | ;;
13 | ;; sock: ZeroMQ socket object
14 | ;; addr: address of a sock (a string)
15 | ;; sock-id: randomly generated string ID created by the core.async thread when a new socket is requested
16 | ;; chan: core.async channel
17 | ;; pairing: map entry of {sock-id {:out chan :in chan}}.
18 | ;;
19 | ;; All in/out labels are written relative to this namespace.
20 |
21 | (def BLOCK 0)
22 |
23 | (defn send!
24 | [^ZMQ$Socket sock msg]
25 | (let [msg (if (coll? msg) msg [msg])]
26 | (loop [[head & tail] msg]
27 | ;;TODO: handle byte buffers.
28 | (let [res (.send sock head (if tail (bit-or ZMQ/NOBLOCK ZMQ/SNDMORE) ZMQ/NOBLOCK))]
29 | (cond
30 | (= false res) (println "WARNING: Message not sent on" sock)
31 | tail (recur tail))))))
32 |
33 | (defn receive-all
34 | "Receive all data parts from the socket, returning a vector of byte arrays.
35 | If the socket does not contain a multipart message, returns a plain byte array."
36 | [^ZMQ$Socket sock]
37 | (loop [acc (transient [])]
38 | (let [new-acc (conj! acc (.recv sock))]
39 | (if (.hasReceiveMore sock)
40 | (recur new-acc)
41 | (let [res (persistent! new-acc)]
42 | (if (= 1 (count res))
43 | (first res) res))))))
44 |
45 | (defn poll
46 | "Blocking poll that returns a [val, socket] tuple.
47 | If multiple sockets are ready, one is chosen to be read from nondeterministically."
48 | [socks]
49 | ;;TODO: what's the perf cost of creating a new poller all the time?
50 | (let [n (count socks)
51 | poller (ZMQ$Poller. n)]
52 | (doseq [s socks]
53 | (.register poller s ZMQ$Poller/POLLIN))
54 | (.poll poller)
55 |
56 | ;;Randomly take the first ready socket and its message, to match core.async's alts! behavior
57 | (->> (shuffle (range n))
58 | (filter #(.pollin poller %))
59 | first
60 | (.getSocket poller)
61 | ((juxt receive-all identity)))))
62 |
63 |
64 | (defn zmq-looper
65 | "Runnable fn with blocking loop on zmq sockets.
66 | Opens/closes zmq sockets according to messages received on `zmq-control-sock`.
67 | Relays messages from zmq sockets to `async-control-chan`."
68 | [queue zmq-control-sock async-control-chan]
69 | (fn []
70 | ;;Socks is a map of string socket-ids to ZeroMQ socket objects (plus a single :control keyword key associated with the thread's control socket).
71 | (loop [socks {:control zmq-control-sock}]
72 | (let [[val sock] (poll (vals socks))
73 | id (get (map-invert socks) sock)
74 | ;;Hack coercion so we can have a pattern match against message from control socket
75 | val (if (= :control id) (keyword (String. val)) val)]
76 |
77 | (assert (not (nil? id)))
78 |
79 | (match [id val]
80 |
81 | ;;A message indicating there's a message waiting for us to process on the queue.
82 | [:control :sentinel]
83 | (let [msg (.take queue)]
84 | (match [msg]
85 |
86 | [[:register sock-id new-sock]]
87 | (recur (assoc socks sock-id new-sock))
88 |
89 | [[:close sock-id]]
90 | (do
91 | (.close (socks sock-id))
92 | (recur (dissoc socks sock-id)))
93 |
94 | ;;Send a message out
95 | [[sock-id outgoing-message]]
96 | (do
97 | (send! (socks sock-id) outgoing-message)
98 | (recur socks))))
99 |
100 | [:control :shutdown]
101 | (doseq [[_ sock] socks]
102 | (.close sock))
103 |
104 | [:control msg]
105 | (throw (Exception. (str "bad ZMQ control message: " msg)))
106 |
107 | ;;It's an incoming message, send it to the async thread to convey to the application
108 | [incoming-sock-id msg]
109 | (do
110 | (>!! async-control-chan [incoming-sock-id msg])
111 | (recur socks)))))))
112 |
113 |
114 | (defn sock-id-for-chan
115 | [c pairings]
116 | (first (for [[id {in :in out :out}] pairings
117 | :when (#{in out} c)]
118 | id)))
119 |
120 | (defn command-zmq-thread!
121 | "Helper used by the core.async thread to relay a command to the ZeroMQ thread.
122 | Puts message of interest on queue and then sends a sentinel value over zmq-control-sock so that ZeroMQ thread unblocks."
123 | [zmq-control-sock queue msg]
124 | (.put queue msg)
125 | (send! zmq-control-sock "sentinel"))
126 |
127 | (defn shutdown-pairing!
128 | "Close ZeroMQ socket with `id` and all associated channels."
129 | [[sock-id chanmap] zmq-control-sock queue]
130 | (command-zmq-thread! zmq-control-sock queue
131 | [:close sock-id])
132 | (doseq [[_ c] chanmap]
133 | (when c (close! c))))
134 |
135 | (defn async-looper
136 | "Runnable fn with blocking loop on channels.
137 | Controlled by messages sent over provided `async-control-chan`.
138 | Sends messages to complementary `zmq-looper` via provided `zmq-control-sock` (assumed to be connected)."
139 | [queue async-control-chan zmq-control-sock]
140 | (fn []
141 | ;; Pairings is a map of string id to {:out chan :in chan} map, where existence of :out and :in depend on the type of ZeroMQ socket.
142 | (loop [pairings {:control {:in async-control-chan}}]
143 | (let [in-chans (remove nil? (map :in (vals pairings)))
144 | [val c] (alts!! in-chans)
145 | id (sock-id-for-chan c pairings)]
146 |
147 | (match [id val]
148 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
149 | ;;Control messages
150 |
151 | ;;Register a new socket.
152 | [:control [:register sock chanmap]]
153 | (let [sock-id (str (gensym "zmq-"))]
154 | (command-zmq-thread! zmq-control-sock queue [:register sock-id sock])
155 | (recur (assoc pairings sock-id chanmap)))
156 |
157 | ;;Relay a message from ZeroMQ socket to core.async channel.
158 | [:control [sock-id msg]]
159 | (let [out (get-in pairings [sock-id :out])]
160 | (assert out)
161 | ;;We have a contract with library consumers that they cannot give us channels that can block, so this >!! won't tie up the async looper.
162 | (>!! out msg)
163 | (recur pairings))
164 |
165 | ;;The control channel has been closed, close all ZMQ sockets and channels.
166 | [:control nil]
167 | (let [opened-pairings (dissoc pairings :control)]
168 |
169 | (doseq [p opened-pairings]
170 | (shutdown-pairing! p zmq-control-sock queue))
171 |
172 | (send! zmq-control-sock "shutdown")
173 | ;;Don't recur...
174 | nil)
175 |
176 | [:control msg] (throw (Exception. (str "bad async control message: " msg)))
177 |
178 |
179 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
180 | ;;Non-control messages
181 |
182 | ;;The channel was closed, close the corresponding socket.
183 | [id nil]
184 | (do
185 | (shutdown-pairing! [id (pairings id)] zmq-control-sock queue)
186 | (recur (dissoc pairings id)))
187 |
188 | ;;Just convey the message to the ZeroMQ socket.
189 | [id msg]
190 | (do
191 | (command-zmq-thread! zmq-control-sock queue [id msg])
192 | (recur pairings)))))))
193 |
194 |
195 |
196 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
197 | ;;Public API
198 | (defn create-context
199 | "Creates a zmq-async context map containing the following keys:
200 |
201 | zcontext jzmq ZContext object from which sockets are created
202 | shutdown no-arg fn that shuts down this context, closing all ZeroMQ sockets
203 | addr address of in-process ZeroMQ socket used to control ZeroMQ thread
204 | sock-server server end of zmq pair socket; must be bound via (.bind addr) method before starting the zmq thread
205 | sock-client client end of zmq pair socket; must be connected via (.connect addr) method before starting the async thread
206 | async-control-chan channel used to control async thread
207 | zmq-thread
208 | async-thread"
209 |
210 | ([] (create-context nil))
211 | ([name]
212 | (let [addr (str "inproc://" (gensym "zmq-async-"))
213 | zcontext (ZContext.)
214 | sock-server (.createSocket zcontext ZMQ/PAIR)
215 | sock-client (.createSocket zcontext ZMQ/PAIR)
216 |
217 | ;;Shouldn't have to have a large queue; it's okay to block core.async thread puts since that'll give time for the ZeroMQ thread to catch up.
218 | queue (LinkedBlockingQueue. 8)
219 |
220 | async-control-chan (chan)
221 |
222 | zmq-thread (doto (Thread. (zmq-looper queue sock-server async-control-chan))
223 | (.setName (str "ZeroMQ looper " "[" (or name addr) "]"))
224 | (.setDaemon true))
225 | async-thread (doto (Thread. (async-looper queue async-control-chan sock-client))
226 | (.setName (str "core.async looper" "[" (or name addr) "]"))
227 | (.setDaemon true))]
228 |
229 | {:zcontext zcontext
230 | :addr addr
231 | :sock-server sock-server
232 | :sock-client sock-client
233 | :queue queue
234 | :async-control-chan async-control-chan
235 | :zmq-thread zmq-thread
236 | :async-thread async-thread
237 | :shutdown #(close! async-control-chan)})))
238 |
239 | (defn initialize!
240 | "Initializes a zmq-async context by binding/connecting both ends of the ZeroMQ control socket and starting both threads.
241 | Does nothing if zmq-thread is already started."
242 | [context]
243 | (let [{:keys [addr sock-server sock-client
244 | zmq-thread async-thread]} context]
245 | (when-not (.isAlive zmq-thread)
246 | (.bind sock-server addr)
247 | (.start zmq-thread)
248 |
249 | (.connect sock-client addr)
250 | (.start async-thread)))
251 | nil)
252 |
253 | (def ^:private automagic-context
254 | "Default context used by any calls to `register-socket!` that don't specify an explicit context."
255 | (create-context "zmq-async default context"))
256 |
257 | (defn register-socket!
258 | "Associate ZeroMQ `socket` with provided write-only `out` and read-only `in` ports.
259 | Accepts a map with the following keys:
260 |
261 | :context - The zmq-async context under which the ZeroMQ socket should be maintained; defaults to a global context if none is provided
262 | :in - Write-only core.async port on which you should place outgoing messages
263 | :out - Read-only core.async port on which zmq-async places incoming messages; this port should never block
264 | :socket - A ZeroMQ socket object that can be read from and/or written to (i.e., already bound/connected to at least one address)
265 | :socket-type - If a :socket is not provided, this socket-type will be created for you; must be one of :pair :dealer :router :pub :sub :req :rep :pull :push :xreq :xrep :xpub :xsub
266 | :configurator - If a :socket is not provided, this function will be used to configure a newly instantiated socket of :socket-type; you should bind/connect to at least one address within this function; see http://zeromq.github.io/jzmq/javadocs/ for the ZeroMQ socket configuration options
267 |
268 | "
269 | [{:keys [context in out socket socket-type configurator]}]
270 |
271 | (when (and (nil? socket)
272 | (or (nil? socket-type) (nil? configurator)))
273 | (throw (IllegalArgumentException. "Must provide an instantiated and bound/connected ZeroMQ socket or a socket-type and configurator fn.")))
274 |
275 | (when (and socket (or socket-type configurator))
276 | (throw (IllegalArgumentException. "You can provide a ZeroMQ socket OR a socket-type and configurator, not both.")))
277 |
278 | (when (and (nil? out) (nil? in))
279 | (throw (IllegalArgumentException. "You must provide at least one of :out and :in channels.")))
280 |
281 | (let [context (or context (doto automagic-context
282 | (initialize!)))
283 | ^ZMQ$Socket socket (or socket (doto (.createSocket (context :zcontext)
284 | (case socket-type
285 | :pair ZMQ/PAIR
286 | :pub ZMQ/PUB
287 | :sub ZMQ/SUB
288 | :req ZMQ/REQ
289 | :rep ZMQ/REP
290 | :xreq ZMQ/XREQ
291 | :xrep ZMQ/XREP
292 | :dealer ZMQ/DEALER
293 | :router ZMQ/ROUTER
294 | :xpub ZMQ/XPUB
295 | :xsub ZMQ/XSUB
296 | :pull ZMQ/PULL
297 | :push ZMQ/PUSH))
298 | configurator))]
299 |
300 | (>!! (:async-control-chan context)
301 | [:register socket {:in in :out out}])))
302 |
303 |
304 | (comment
305 | (require '[clojure.pprint :refer [pprint]]
306 | '[clojure.stacktrace :refer [e]]
307 | '[clojure.tools.namespace.repl :refer [refresh refresh-all]])
308 | (clojure.tools.namespace.repl/refresh)
309 |
310 | )
--------------------------------------------------------------------------------
/test/com/keminglabs/zmq_async/t_core.clj:
--------------------------------------------------------------------------------
1 | (ns com.keminglabs.zmq-async.t-core
2 | (:require [com.keminglabs.zmq-async.core :refer :all]
3 | [clojure.core.async :refer [go close! >!! sock-A
20 | ;;Need to use this awkward seq stuff here to compare byte arrays by value
21 | (seq val) => (seq (.getBytes "A message"))))))
22 |
23 | (fact "ZMQ looper"
24 | (with-state-changes [(around :facts
25 | (let [{:keys [zmq-thread addr sock-server sock-client async-control-chan queue]
26 | :as context} (create-context)
27 | _ (do
28 | (.bind sock-server addr)
29 | (.start zmq-thread))
30 | zcontrol (doto sock-client
31 | (.connect addr))]
32 |
33 | (try
34 | ?form
35 | (finally
36 | (send! zcontrol "shutdown")
37 | (.join zmq-thread 100)
38 | (assert (not (.isAlive zmq-thread)))
39 |
40 | ;;Close any hanging ZeroMQ sockets.
41 | (doseq [s (.getSockets (context :zcontext))]
42 | (.close s))))))]
43 |
44 | ;;TODO: rearchitect so that one concern can be tested at a time?
45 | ;;Then the zmq looper would need to use accessible mutable state instead of loop/recur...
46 | (fact "Opens sockets, conveys messages between sockets and async control channel"
47 | (let [test-addr "inproc://open-test"
48 | test-id "open-test"
49 | test-sock (doto (.createSocket (context :zcontext) ZMQ/PAIR)
50 | (.bind test-addr))]
51 |
52 | (command-zmq-thread! zcontrol queue
53 | [:register test-id test-sock])
54 |
55 | (with-open [sock (.createSocket (context :zcontext) ZMQ/PAIR)]
56 | (.connect sock test-addr)
57 |
58 | ;;passes along received messages
59 | (let [test-msg "hihi"]
60 | (.send sock test-msg)
61 |
62 | (let [[id msg] ( test-id
64 | (seq msg) => (seq (.getBytes test-msg))))
65 |
66 | ;;including multipart messages
67 | (let [test-msg ["yo" "what's" "up?"]]
68 | (.send sock "yo" ZMQ/SNDMORE)
69 | (.send sock "what's" ZMQ/SNDMORE)
70 | (.send sock "up?")
71 |
72 | (let [[id msg] ( test-id
74 | (map #(String. %) msg) => test-msg))
75 |
76 | ;;sends messages when asked to
77 | (let [test-msg "heyo"]
78 | (command-zmq-thread! zcontrol queue
79 | [test-id test-msg])
80 | (Thread/sleep 50)
81 | (.recvStr sock ZMQ/NOBLOCK) => test-msg))))))
82 |
83 | (fact "core.async looper"
84 | (with-state-changes [(around :facts
85 | (let [context (create-context)
86 | {:keys [zmq-thread addr sock-server sock-client async-control-chan async-thread queue]} context
87 | acontrol async-control-chan
88 | zcontrol (doto sock-server
89 | (.bind addr))]
90 |
91 | (.connect sock-client addr)
92 | (.start async-thread)
93 | (try
94 | ?form
95 | (finally
96 | (close! acontrol)
97 | (.join async-thread 100)
98 | (assert (not (.isAlive async-thread)))
99 |
100 | ;;Close any hanging ZeroMQ sockets.
101 | (doseq [s (.getSockets (context :zcontext))]
102 | (.close s))))))]
103 |
104 | (fact "Tells ZMQ looper to shutdown when the async thread's control channel is closed"
105 | (close! acontrol)
106 | (Thread/sleep 50)
107 | (.recvStr zcontrol ZMQ/NOBLOCK) => "shutdown")
108 |
109 | (fact "Closes all open sockets when the async thread's control channel is closed"
110 |
111 | ;;register test socket
112 | (register-socket! {:context context :out (chan) :in (chan)
113 | :socket-type :req :configurator #(.connect % "ipc://test-addr")})
114 |
115 | (Thread/sleep 50)
116 | (.recvStr zcontrol ZMQ/NOBLOCK) => "sentinel"
117 |
118 | (let [[cmd sock-id _] (.take queue)]
119 | cmd => :register
120 | ;;Okay, now to actually test what we care about...
121 | ;;close the control socket
122 | (close! acontrol)
123 | (Thread/sleep 50)
124 |
125 | ;;the ZMQ thread was told to close the socket we opened earlier
126 | (.recvStr zcontrol ZMQ/NOBLOCK) => "sentinel"
127 | (.take queue) => [:close sock-id]
128 | (.recvStr zcontrol ZMQ/NOBLOCK) => "shutdown"))
129 |
130 | (fact "Forwards messages recieved from ZeroMQ thread to appropriate core.async channel."
131 | (let [out (chan) in (chan)]
132 |
133 | ;;register test socket
134 | (register-socket! {:context context :out out :in in
135 | :socket-type :req :configurator #(.bind % "ipc://test-addr")})
136 |
137 | (Thread/sleep 50)
138 | (.recvStr zcontrol ZMQ/NOBLOCK) => "sentinel"
139 | (let [[cmd sock-id _] (.take queue)]
140 | cmd => :register
141 | ;;Okay, now to actually test what we care about...
142 | (let [test-msg "hihi"]
143 | ;;pretend the zeromq thread got a message from the socket...
144 | (>!! acontrol [sock-id test-msg])
145 |
146 | ;;and it should get forwarded the the recv port.
147 | ( test-msg))))
148 |
149 | (fact "Forwards messages recieved from core.async 'out' channel to ZeroMQ thread."
150 | (let [out (chan) in (chan)]
151 |
152 | ;;register test socket
153 | (register-socket! {:context context :out out :in in
154 | :socket-type :req :configurator #(.bind % "ipc://test-addr")})
155 |
156 | (Thread/sleep 50)
157 | (.recvStr zcontrol ZMQ/NOBLOCK) => "sentinel"
158 | (let [[cmd sock-id _] (.take queue)]
159 | cmd => :register
160 |
161 | ;;Okay, now to actually test what we care about...
162 | (let [test-msg "hihi"]
163 | (>!! in test-msg)
164 | (Thread/sleep 50)
165 | (.recvStr zcontrol ZMQ/NOBLOCK) => "sentinel"
166 | (.take queue) => [sock-id test-msg]))))))
167 |
168 |
169 | (fact "Integration"
170 | (with-state-changes [(around :facts
171 | (let [context (doto (create-context)
172 | (initialize!))
173 | {:keys [async-thread zmq-thread]} context]
174 |
175 | (try
176 | ?form
177 | (finally
178 | ((:shutdown context))
179 | (.join async-thread 100)
180 | (assert (not (.isAlive async-thread)))
181 |
182 | (.join zmq-thread 100)
183 | (assert (not (.isAlive zmq-thread)))
184 |
185 | ;;Close any hanging ZeroMQ sockets.
186 | (doseq [s (.getSockets (context :zcontext))]
187 | (.close s))))))]
188 |
189 | (fact "raw->wrapped"
190 | (let [addr "ipc://test-addr" test-msg "hihi"
191 | out (chan) in (chan)]
192 |
193 | (register-socket! {:context context :out out :in in
194 | :socket-type :pair :configurator #(.bind % addr)})
195 |
196 | (.send (doto (.createSocket (context :zcontext) ZMQ/PAIR)
197 | (.connect addr))
198 | test-msg)
199 | (String. ( test-msg))
200 |
201 | (fact "raw->wrapped, no explicit context"
202 | (let [addr "ipc://test-addr" test-msg "hihi"
203 | out (chan) in (chan)]
204 |
205 | (register-socket! {:out out :in in
206 | :socket-type :pair :configurator #(.bind % addr)})
207 |
208 | (.send (doto (.createSocket (context :zcontext) ZMQ/PAIR)
209 | (.connect addr))
210 | test-msg)
211 | (String. ( test-msg))
212 |
213 | (fact "wrapped->raw"
214 | (let [addr "inproc://test-addr" test-msg "hihi"
215 | out (chan) in (chan)]
216 |
217 | (register-socket! {:context context :out out :in in
218 | :socket-type :pair :configurator #(.bind % addr)})
219 |
220 | (let [raw (doto (.createSocket (context :zcontext) ZMQ/PAIR)
221 | (.connect addr))]
222 | (>!! in test-msg)
223 |
224 | (Thread/sleep 50) ;;gross!
225 | (.recvStr raw ZMQ/NOBLOCK)) => test-msg))
226 |
227 |
228 | (fact "wrapped pair -> wrapped pair"
229 | (let [addr "inproc://test-addr" test-msg "hihi"
230 | [s-out s-in c-out c-in] (repeatedly 4 chan)]
231 |
232 | (register-socket! {:context context :out s-out :in s-in
233 | :socket-type :pair :configurator #(.bind % addr)})
234 | (register-socket! {:context context :out c-out :in c-in
235 | :socket-type :pair :configurator #(.connect % addr)})
236 |
237 | (>!! c-in test-msg)
238 | (String. ( test-msg))
239 |
240 | (fact "wrapped pair -> wrapped pair w/ multipart message"
241 | (let [addr "inproc://test-addr" test-msg ["hihi" "what's" "up?"]
242 | [s-out s-in c-out c-in] (repeatedly 4 chan)]
243 |
244 | (register-socket! {:context context :out s-out :in s-in
245 | :socket-type :pair :configurator #(.bind % addr)})
246 | (register-socket! {:context context :out c-out :in c-in
247 | :socket-type :pair :configurator #(.connect % addr)})
248 |
249 | (>!! c-in test-msg)
250 | (map #(String. %) ( test-msg))
251 |
252 |
253 | (fact "wrapped req <-> wrapped rep, go/future"
254 | (let [addr "inproc://test-addr"
255 | [s-out s-in c-out c-in] (repeatedly 4 chan)
256 | n 5
257 | server (go
258 | (dotimes [_ n]
259 | (assert (= "ping" (String. (! s-in "pong"))
262 | :success)
263 |
264 | client (future
265 | (dotimes [_ n]
266 | (>!! c-in "ping")
267 | (assert (= "pong" (String. ( :success
277 | (close! c-out)
278 | (close! s-out)
279 | (close! server)
280 | ( :success))
281 |
282 | (fact "wrapped req <-> wrapped rep, go/go"
283 | (let [addr "inproc://test-addr"
284 | [s-out s-in c-out c-in] (repeatedly 4 chan)
285 | n 5
286 | server (go
287 | (dotimes [_ n]
288 | (assert (= "ping" (String. (! s-in "pong"))
291 | :success)
292 |
293 | client (go
294 | (dotimes [_ n]
295 | (>! c-in "ping")
296 | (assert (= "pong" (String. ( :success
308 | (close! c-out)
309 | (close! s-out)
310 | (close! server)
311 | ( :success))))
312 |
313 | (fact "register-socket! throws errors when given invalid optmaps"
314 | (register-socket! {}) => (throws IllegalArgumentException)
315 | (register-socket! {:out (chan) :in (chan)}) => (throws IllegalArgumentException)
316 | (register-socket! {:socket-type :req :configurator identity}) => (throws IllegalArgumentException)
317 | (register-socket! {:socket "grr" :out (chan) :in (chan)
318 | :socket-type :req :configurator identity}) => (throws IllegalArgumentException))
--------------------------------------------------------------------------------
/vendor/poms/linux64/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | com.keminglabs
4 | jzmq-linux64
5 | jar
6 | VERSION
7 |
8 |
--------------------------------------------------------------------------------
/vendor/poms/osx64/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | com.keminglabs
4 | jzmq-osx64
5 | jar
6 | VERSION
7 |
8 |
--------------------------------------------------------------------------------
/vendor/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | #This script builds jzmq and pushes the native JAR
6 |
7 | cd jzmq
8 | git reset HEAD --hard
9 |
10 |
11 | ###############
12 | #Build library
13 |
14 | # Note, homebrew's pkg-config has issues; run:
15 | #
16 | # eval `brew --config | grep HOMEBREW_PREFIX | sed 's/: /=/'`
17 | # sudo bash -c 'echo '$HOMEBREW_PREFIX/share/aclocal' >> `aclocal --print-ac-dir`/dirlist'
18 | #
19 | # to resolve.
20 |
21 | ./autogen.sh
22 | ./configure
23 | make
24 |
25 |
26 | ###############
27 | #Create jars
28 |
29 | VERSION=`git rev-parse HEAD | cut -c 1-7`
30 |
31 | mvn versions:set -DgenerateBackupPoms=false \
32 | -DnewVersion=$VERSION
33 |
34 | ## It's not possible to change the group id using the maven versions plugin; use good ol' sed instead
35 | sed -i.bak "s/org.zeromq/com.keminglabs/" pom.xml
36 |
37 | mvn package
38 |
39 | JAR="jzmq-$VERSION.jar"
40 | if [ -f "target/$JAR" ]; then
41 | mvn install:install-file -Dfile="target/$JAR" \
42 | -DgroupId=com.keminglabs \
43 | -Dversion=$VERSION \
44 | -Dpackaging=jar \
45 | -DartifactId=jzmq
46 | echo "lein deploy clojars com.keminglabs/jzmq $VERSION 'vendor/jzmq/target/$JAR' 'vendor/jzmq/pom.xml'"
47 | fi
48 |
49 | OSX_JAR="jzmq-$VERSION-native-x86_64-Mac OS X.jar"
50 | if [ -f "target/$OSX_JAR" ]; then
51 | mvn install:install-file -Dfile="target/$OSX_JAR" \
52 | -DgroupId=com.keminglabs \
53 | -Dversion=$VERSION \
54 | -Dpackaging=jar \
55 | -DartifactId=jzmq-osx64
56 | mkdir -p osx64-pom
57 | cat ../poms/osx64/pom.xml | sed "s/VERSION/$VERSION/" > osx64-pom/pom.xml
58 | echo "lein deploy clojars com.keminglabs/jzmq-osx64 $VERSION 'vendor/jzmq/target/$OSX_JAR' 'vendor/jzmq/osx64-pom/pom.xml'"
59 | fi
60 |
61 | LINUX_JAR="jzmq-$VERSION-native-amd64-Linux.jar"
62 | if [ -f "target/$LINUX_JAR" ]; then
63 | mvn install:install-file -Dfile="target/$LINUX_JAR" \
64 | -DgroupId=com.keminglabs \
65 | -Dversion=$VERSION \
66 | -Dpackaging=jar \
67 | -DartifactId=jzmq-linux64
68 | mkdir -p linux64-pom
69 | cat ../poms/linux64/pom.xml | sed "s/VERSION/$VERSION/" > linux64-pom/pom.xml
70 | echo "lein deploy clojars com.keminglabs/jzmq-linux64 $VERSION 'vendor/jzmq/target/$LINUX_JAR' 'vendor/jzmq/linux64-pom/pom.xml'"
71 | fi
72 |
73 |
74 | echo "Success! Please push generated JARs to Clojars by copy/pasting 'lein deploy' strings printed above."
75 |
76 |
--------------------------------------------------------------------------------