├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── cover.spec
├── dialyzer.ignore-warnings
├── doc
├── README.md
├── dq.md
├── dq_callback.md
├── edoc-info
├── erlang.png
└── stylesheet.css
├── rebar.config
├── src
├── dq.app.src
├── dq.erl
└── dq_callback.erl
└── test
└── dq_SUITE.erl
/.gitignore:
--------------------------------------------------------------------------------
1 | *.beam
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: erlang
2 | otp_release:
3 | - 17.1
4 | - 17.0
5 | - R16B03
6 | - R16B02
7 | script: make
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015. Darach Ennis < darach at gmail dot com >.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to permit
8 | persons to whom the Software is furnished to do so, subject to the
9 | following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included
12 | in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 | USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | REBAR:=rebar
2 |
3 | .PHONY: all build test clean doc
4 |
5 | all: test
6 |
7 | build:
8 | $(REBAR) get-deps compile
9 |
10 | buildplt:
11 | if [ ! -f .plt ]; then \
12 | dialyzer --build_plt --output_plt .plt --apps kernel stdlib ; \
13 | fi
14 |
15 | pltclean:
16 | @rm .plt
17 |
18 | dialyze: buildplt
19 | @ERL_LIBS=deps dialyzer --fullpath -Wno_undefined_callbacks \
20 | --plts .plt \
21 | -r ebin --src src \
22 | | grep -v -f ./dialyzer.ignore-warnings
23 |
24 | test: build
25 | $(REBAR) skip_deps=true ct
26 |
27 | clean:
28 | $(REBAR) clean
29 | -rm -rvf deps ebin doc
30 |
31 | doc:
32 | $(REBAR) doc
33 |
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # **dq** [](https://travis-ci.org/darach/dq)
2 |
3 | > Distributed Fault Tolerant Queue
4 |
5 | ## Status
6 |
7 | Experimental.
8 |
9 | ## Overview
10 |
11 | **dq** is a distributed fault tolerant queue based on Ulf Wiger's and Thomas Arts deadlock-resolving [locks](http://github.com/uwiger/locks) library.
12 |
13 | The queue exposes the core, extended and Okasaki queue APIs but the queue is redundant and uses distributed erlang to maintain consistent state on many nodes. The queue can detect new nodes and recover from lost nodes and netsplits.
14 |
15 | ## Sample usage
16 |
17 | A walkthrough of using the queue library.
18 |
19 | **1. Start one or many distributed erlang nodes**
20 |
21 | ```
22 | $ ERL_LIBS=deps erl -pa ebin -sname bill@darach -setcookie ted
23 | Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
24 |
25 | Eshell V5.10.3 (abort with ^G)
26 | (bill@darach)1>
27 | ```
28 |
29 | **2. Ensure that the locks application is available and start it**
30 |
31 | ```
32 | (bill@darach)1> application:ensure_all_started(locks).
33 | {ok,[locks]}
34 | (bill@darach)2>
35 | ```
36 |
37 | **3. Create a distributed queue**
38 |
39 | ```
40 | (bill@darach)2> {ok,Q} = dq:new(myq).
41 | {ok,<0.46.0>}
42 | (bill@darach)3>
43 | ```
44 |
45 | **4. Start using the queue**
46 |
47 | ```
48 | bill@darach)3> [ dq:in(X,Q) || X <- lists:seq(1,3) ].
49 | [{[1],[]},{[2],[1]},{[3,2],[1]}]
50 | (bill@darach)4>
51 | ```
52 |
53 | **5. We can start other nodes at any time ...**
54 |
55 | ```
56 | $ ERL_LIBS=deps erl -pa ebin -sname jill@darach -setcookie ted
57 | Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
58 |
59 | Eshell V5.10.3 (abort with ^G)
60 | (jill@darach)1> application:ensure_all_started(locks).
61 | {ok,[locks]}
62 | (jill@darach)2> {ok,Q} = dq:new(myq).
63 | {ok,<0.46.0>}
64 | (jill@darach)3> [ dq:in(X,Q) || X <- lists:seq(7,9) ].
65 | [{[7],[]},{"\b",[7]},{"\t\b",[7]}]
66 | (jill@darach)4>
67 | ```
68 |
69 | Notice that the distributed queue on each node are distinct?
70 | The nodes haven't been joined into a cluster and do not know about each other yet.
71 |
72 | **6. Join nodes into a cluster**
73 |
74 | ```
75 | (jill@darach)4> net_adm:ping(bill@darach).
76 | pong
77 | (jill@darach)5>
78 | ```
79 | The distinct queues have now been merged into a single distributed queue.
80 | This queue can be operated on from any of the nodes in the cluster.
81 |
82 | ```
83 | (bill@darach)4> dq:in_r(777,Q).
84 | {[9,8,7,3,2],[777,1]}
85 | (bill@darach)5>
86 | ```
87 |
88 | **7. There can be more than one distributed queue active in a cluster**
89 |
90 | Create and populate a new queue on one node:
91 |
92 | ```
93 | (bill@darach)5> {ok,P} = dq:new(otherq).
94 | {ok,<0.54.0>}
95 | (bill@darach)6> dq:in(1,P).
96 | {[1],[]}
97 | (bill@darach)7>
98 | ```
99 |
100 | As the cluster is already active, we just need a reference to it on
101 | the other cluster nodes to operate on it:
102 |
103 | ```
104 | (jill@darach)5> {ok,P} = dq:new(otherq).
105 | {ok,<0.56.0>}
106 | (jill@darach)6> dq:in(2,P).
107 | {[2],[1]}
108 | (jill@darach)7>
109 | ```
110 |
111 | ## TODO
112 |
113 | 1. Make merge function replaceable
114 | 2. Resolve travis common test issues
115 | 3. Priority queue option
116 |
117 | ## NOTES
118 |
119 | * It is not enough to run distributed erlang nodes. They must also be connected.
120 | * The locks application must be running on at least 2 nodes...
121 | * Calling erlang:disconnect_node/1 on a 2 node cluster is not supported by locks
122 | * Calling dq:<fun>/<arity> on a >1 node cluster with locks running on a single node will fail
123 | * Calling dq:<fun>/<arity> on a 1 node cluster with locks running on a single node will succeed
124 | * Any queue state in a local queue before being adjoined to a cluster will be lost (not merged)
125 |
126 | ## Enjoy!
127 |
--------------------------------------------------------------------------------
/cover.spec:
--------------------------------------------------------------------------------
1 | {incl_mods, [
2 | dq
3 | , dq_callback
4 | ]}.
5 |
--------------------------------------------------------------------------------
/dialyzer.ignore-warnings:
--------------------------------------------------------------------------------
1 | dbg:p/2
2 | dbg:tpl/2
3 | dbg:tracer/0
4 | erlang:get_module_info/1
5 | erlang:get_module_info/2
6 | locks_leader:ask_candidates/2
7 | locks_leader:info/1
8 | locks_leader:leader_call/3
9 | locks_leader:new_candidates/1
10 | locks_leader:start_link/2
11 | locks_leader:start_link/4
12 | locks_leader:leader_info/0
13 |
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # The dq application #
4 |
5 |
6 | ## Modules ##
7 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/doc/dq.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module dq #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 |
10 |
11 |
12 | ## Data Types ##
13 |
14 |
15 |
16 |
17 | ### dq() ###
18 |
19 |
20 | __abstract datatype__: `dq()`
21 |
22 |
23 |
24 |
25 | ### item() ###
26 |
27 |
28 |
29 |
30 | item() = any()
31 |
32 |
33 |
34 |
35 |
36 | ## Function Index ##
37 |
38 |
39 | cons/2 | Equivalent to queue:cons/2. |
daeh/1 | Equivalent to queue:daeh/1. |
drop/1 | Equivalent to queue:drop/1. |
drop_r/1 | Equivalent to queue:drop_r/1. |
filter/2 | Equivalent to queue:filter/2. |
get/1 | Equivalent to queue:get/1. |
get_r/1 | Equivalent to queue:get_r/1. |
head/1 | Equivalent to queue:head/1. |
in/2 | Equivalent to queue:in/2. |
in_r/2 | Equivalent to queue:in_r/2. |
init/1 | Equivalent to queue:init/1. |
is_empty/1 | Equivalent to queue:is_empty/1. |
is_leader/1 | Is this queue instance the global distributed queue leader?. |
is_queue/1 | Equivalent to queue:is_queue/1. |
join/2 | Equivalent to queue:join/2. |
last/1 | Equivalent to queue:last/1. |
len/1 | Equivalent to queue:len/1. |
liat/1 | Equivalent to queue:liat/1. |
member/2 | Equivalent to queue:member/2. |
new/0 | Create a new global distributed anonymous queue. |
new/1 | Create a global distributed queue with name. |
new/2 | Create a global distributed queue with name and options. |
out/1 | Equivalent to queue:out/1. |
out_r/1 | Equivalent to queue:out_r/1. |
peek/1 | Equivalent to queue:peek/1. |
peek_r/1 | Equivalent to queue:peek_r/1. |
reverse/1 | Equivalent to queue:reverse/1. |
snapshot/1 | Get a snapshot of the distributed queue's current state. |
snoc/2 | Equivalent to queue:snoc/2. |
split/2 | Equivalent to queue:split/2. |
tail/1 | Equivalent to queue:tail/1. |
40 |
41 |
42 |
43 |
44 | ## Function Details ##
45 |
46 |
47 |
48 | ### cons/2 ###
49 |
50 |
51 |
52 | cons(Item::item(), Queue::dq()) -> queue()
53 |
54 |
55 |
56 | Equivalent to queue:cons/2.
57 |
58 |
59 | ### daeh/1 ###
60 |
61 |
62 |
63 | daeh(Queue::dq()) -> queue() | empty
64 |
65 |
66 |
67 | Equivalent to queue:daeh/1.
68 |
69 |
70 | ### drop/1 ###
71 |
72 |
73 |
74 | drop(Queue::dq()) -> queue() | empty
75 |
76 |
77 |
78 | Equivalent to queue:drop/1.
79 |
80 |
81 | ### drop_r/1 ###
82 |
83 |
84 |
85 | drop_r(Queue::dq()) -> queue() | empty
86 |
87 |
88 |
89 | Equivalent to queue:drop_r/1.
90 |
91 |
92 | ### filter/2 ###
93 |
94 |
95 |
96 | filter(Fun::fun((item()) -> boolean()), Queue::dq()) -> queue()
97 |
98 |
99 |
100 | Equivalent to queue:filter/2.
101 |
102 |
103 | ### get/1 ###
104 |
105 |
106 |
107 | get(Queue::dq()) -> item() | empty
108 |
109 |
110 |
111 | Equivalent to queue:get/1.
112 |
113 |
114 | ### get_r/1 ###
115 |
116 |
117 |
118 | get_r(Queue::dq()) -> item() | empty
119 |
120 |
121 |
122 | Equivalent to queue:get_r/1.
123 |
124 |
125 | ### head/1 ###
126 |
127 |
128 |
129 | head(Queue::dq()) -> item() | empty
130 |
131 |
132 |
133 | Equivalent to queue:head/1.
134 |
135 |
136 | ### in/2 ###
137 |
138 |
139 |
140 | in(Item::item(), Queue::dq()) -> item()
141 |
142 |
143 |
144 | Equivalent to queue:in/2
145 |
146 |
147 | ### in_r/2 ###
148 |
149 |
150 |
151 | in_r(Item::item(), Queue::dq()) -> item()
152 |
153 |
154 |
155 | Equivalent to queue:in_r/2
156 |
157 |
158 | ### init/1 ###
159 |
160 |
161 |
162 | init(Queue::dq()) -> item() | empty
163 |
164 |
165 |
166 | Equivalent to queue:init/1.
167 |
168 |
169 | ### is_empty/1 ###
170 |
171 |
172 |
173 | is_empty(Queue::dq()) -> boolean()
174 |
175 |
176 |
177 | Equivalent to queue:is_empty/1
178 |
179 |
180 | ### is_leader/1 ###
181 |
182 |
183 |
184 | is_leader(Queue::dq()) -> boolean()
185 |
186 |
187 |
188 | Is this queue instance the global distributed queue leader?
189 |
190 |
191 | ### is_queue/1 ###
192 |
193 |
194 |
195 | is_queue(Queue::pid()) -> boolean()
196 |
197 |
198 |
199 | Equivalent to queue:is_queue/1
200 |
201 |
202 | ### join/2 ###
203 |
204 |
205 |
206 | join(Queue::dq(), Q2::queue()) -> queue()
207 |
208 |
209 |
210 | Equivalent to queue:join/2.
211 |
212 |
213 | ### last/1 ###
214 |
215 |
216 |
217 | last(Queue::dq()) -> item() | empty
218 |
219 |
220 |
221 | Equivalent to queue:last/1.
222 |
223 |
224 | ### len/1 ###
225 |
226 |
227 |
228 | len(Queue::dq()) -> non_neg_integer()
229 |
230 |
231 |
232 | Equivalent to queue:len/1
233 |
234 |
235 | ### liat/1 ###
236 |
237 |
238 |
239 | liat(Queue::dq()) -> item() | empty
240 |
241 |
242 |
243 | Equivalent to queue:liat/1.
244 |
245 |
246 | ### member/2 ###
247 |
248 |
249 |
250 | member(Item::item(), Queue::dq()) -> boolean()
251 |
252 |
253 |
254 | Equivalent to queue:member/2.
255 |
256 |
257 | ### new/0 ###
258 |
259 |
260 |
261 | new() -> dq()
262 |
263 |
264 |
265 | Create a new global distributed anonymous queue
266 |
267 |
268 | ### new/1 ###
269 |
270 |
271 |
272 | new(Name::term()) -> dq()
273 |
274 |
275 |
276 | Create a global distributed queue with name
277 |
278 |
279 | ### new/2 ###
280 |
281 |
282 |
283 | new(Name::term(), Opts::proplists:proplist()) -> dq()
284 |
285 |
286 |
287 | Create a global distributed queue with name and options
288 |
289 |
290 | ### out/1 ###
291 |
292 |
293 |
294 | out(Queue::dq()) -> item()
295 |
296 |
297 |
298 | Equivalent to queue:out/1
299 |
300 |
301 | ### out_r/1 ###
302 |
303 |
304 |
305 | out_r(Queue::dq()) -> item()
306 |
307 |
308 |
309 | Equivalent to queue:out_r/1
310 |
311 |
312 | ### peek/1 ###
313 |
314 |
315 |
316 | peek(Queue::dq()) -> item()
317 |
318 |
319 |
320 | Equivalent to queue:peek/1.
321 |
322 |
323 | ### peek_r/1 ###
324 |
325 |
326 |
327 | peek_r(Queue::dq()) -> item()
328 |
329 |
330 |
331 | Equivalent to queue:peek_r/1.
332 |
333 |
334 | ### reverse/1 ###
335 |
336 |
337 |
338 | reverse(Queue::dq()) -> queue()
339 |
340 |
341 |
342 | Equivalent to queue:reverse/1.
343 |
344 |
345 | ### snapshot/1 ###
346 |
347 |
348 |
349 | snapshot(Queue::dq()) -> queue()
350 |
351 |
352 |
353 | Get a snapshot of the distributed queue's current state
354 |
355 |
356 | ### snoc/2 ###
357 |
358 |
359 |
360 | snoc(Queue::dq(), Item::item()) -> queue()
361 |
362 |
363 |
364 | Equivalent to queue:snoc/2.
365 |
366 |
367 | ### split/2 ###
368 |
369 |
370 |
371 | split(N::pos_integer(), Queue::dq()) -> {queue(), queue()}
372 |
373 |
374 |
375 | Equivalent to queue:split/2.
376 |
377 |
378 | ### tail/1 ###
379 |
380 |
381 |
382 | tail(Queue::dq()) -> queue() | empty
383 |
384 |
385 |
386 | Equivalent to queue:tail/1.
387 |
--------------------------------------------------------------------------------
/doc/dq_callback.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module dq_callback #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | __Behaviours:__ [`locks_leader`](/Users/darach/Personal/GitHub/dq/deps/locks/doc/locks_leader.md).
9 |
10 |
11 |
12 | ## Data Types ##
13 |
14 |
15 |
16 |
17 | ### state() ###
18 |
19 |
20 |
21 |
22 | state() = #state{}
23 |
24 |
25 |
26 |
27 |
28 | ## Function Index ##
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ## Function Details ##
37 |
38 |
39 |
40 | ### code_change/4 ###
41 |
42 |
43 |
44 | code_change(FromVsn::string(), State::term(), Info::locks_leader:leader_info(), Extra::term()) -> {ok, state()}
45 |
46 |
47 |
48 |
49 |
50 |
51 | ### elected/3 ###
52 |
53 |
54 |
55 | elected(State::state(), I::locks_leader:leader_info(), Pid::pid() | undefined) -> {ok, term(), state()} | {reply, term(), state()} | {ok, _AmLeaderMsg, _FromLeaderMsg, state()} | {error, term()}
56 |
57 |
58 |
59 |
60 |
61 |
62 | ### from_leader/3 ###
63 |
64 |
65 |
66 | from_leader(Op::term(), State::state(), I::locks_leader:leader_info()) -> {ok, state()}
67 |
68 |
69 |
70 |
71 |
72 |
73 | ### handle_DOWN/3 ###
74 |
75 | `handle_DOWN(Pid, S, I) -> any()`
76 |
77 |
78 |
79 |
80 | ### handle_call/4 ###
81 |
82 |
83 |
84 | handle_call(X1::term(), From::pid(), State::state(), I::locks_leader:leader_info()) -> {reply, term(), state()} | {noreply, state()} | {stop, term(), term(), state()} | {reply, term(), term(), state()}
85 |
86 |
87 |
88 |
89 |
90 |
91 | ### handle_cast/3 ###
92 |
93 |
94 |
95 | handle_cast(Msg::term(), S::state(), I::locks_leader:leader_info()) -> {noreply, state()} | {stop, term(), term(), state()} | {reply, term(), state()} | {reply, term(), term(), state()}
96 |
97 |
98 |
99 |
100 |
101 |
102 | ### handle_info/3 ###
103 |
104 |
105 |
106 | handle_info(Msg::term(), State::state(), Info::locks_leader:leader_info()) -> {noreply, state()} | {stop, term(), term(), state()} | {reply, term(), state()} | {reply, term(), term(), state()}
107 |
108 |
109 |
110 |
111 |
112 |
113 | ### handle_leader_call/4 ###
114 |
115 |
116 |
117 | handle_leader_call(Op::term(), From::pid(), State::state(), I::locks_leader:leader_info()) -> {reply, Reply, NState} | {reply, Reply, term(), NState} | {noreply, state()} | {stop, term(), Reply, NState}
118 |
119 |
120 |
121 |
122 |
123 |
124 | ### handle_leader_cast/3 ###
125 |
126 |
127 |
128 | handle_leader_cast(Msg::term(), S::term(), I::locks_leader:leader_info()) -> {ok, state()}
129 |
130 |
131 |
132 | Called by leader in response to a [leader_cast()](/Users/darach/Personal/GitHub/dq/deps/locks/doc/locks_leader.md#leader_cast-2).
133 |
134 |
135 | ### init/1 ###
136 |
137 |
138 |
139 | init(Queue::queue()) -> {ok, state()}
140 |
141 |
142 |
143 |
144 |
145 |
146 | ### merge_cand_states/2 ###
147 |
148 | `merge_cand_states(Q, ListOfCandQs) -> any()`
149 |
150 |
151 |
152 |
153 | ### surrendered/3 ###
154 |
155 | `surrendered(State, X2, I) -> any()`
156 |
157 |
158 |
159 |
160 | ### terminate/2 ###
161 |
162 |
163 |
164 | terminate(Reason::term(), State::state()) -> ok
165 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/doc/edoc-info:
--------------------------------------------------------------------------------
1 | %% encoding: UTF-8
2 | {application,dq}.
3 | {packages,[]}.
4 | {modules,[dq,dq_callback]}.
5 |
--------------------------------------------------------------------------------
/doc/erlang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/darach/dq/25957e8e6678eec188ba332204108591be3603aa/doc/erlang.png
--------------------------------------------------------------------------------
/doc/stylesheet.css:
--------------------------------------------------------------------------------
1 | /* standard EDoc style sheet */
2 | body {
3 | font-family: Verdana, Arial, Helvetica, sans-serif;
4 | margin-left: .25in;
5 | margin-right: .2in;
6 | margin-top: 0.2in;
7 | margin-bottom: 0.2in;
8 | color: #000000;
9 | background-color: #ffffff;
10 | }
11 | h1,h2 {
12 | margin-left: -0.2in;
13 | }
14 | div.navbar {
15 | background-color: #add8e6;
16 | padding: 0.2em;
17 | }
18 | h2.indextitle {
19 | padding: 0.4em;
20 | background-color: #add8e6;
21 | }
22 | h3.function,h3.typedecl {
23 | background-color: #add8e6;
24 | padding-left: 1em;
25 | }
26 | div.spec {
27 | margin-left: 2em;
28 | background-color: #eeeeee;
29 | }
30 | a.module,a.package {
31 | text-decoration:none
32 | }
33 | a.module:hover,a.package:hover {
34 | background-color: #eeeeee;
35 | }
36 | ul.definitions {
37 | list-style-type: none;
38 | }
39 | ul.index {
40 | list-style-type: none;
41 | background-color: #eeeeee;
42 | }
43 |
44 | /*
45 | * Minor style tweaks
46 | */
47 | ul {
48 | list-style-type: square;
49 | }
50 | table {
51 | border-collapse: collapse;
52 | }
53 | td {
54 | padding: 3
55 | }
56 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %% -*- mode: erlang -*-
2 | {cover_enabled, true}.
3 |
4 | {erl_opts, [
5 | debug_info,
6 | % warn_export_all,
7 | warn_obsolete_guard,
8 | % warn_unused_import,
9 | warn_unused_vars,
10 | warn_shadow_vars,
11 | warnings_as_errors
12 | ]}.
13 |
14 | {clean_files, ["test/*.beam"]}.
15 | {ct_extra_params, "-sname dq_test@127.0.0.1 -ct_hooks cth_surefire -logdir logs"}.
16 | {ct_use_shortnames, true}.
17 | {cover_export_enabled, true}.
18 |
19 | {edoc_opts, [{doclet, edown_doclet},
20 | {app_default, "http://www.erlang.org/doc/man"},
21 | {doc_path, []},
22 | {top_level_readme,
23 | {"./doc/README.md",
24 | "https://github.com/darach/dq", "master"}}]}.
25 |
26 | {xref_checks,
27 | [
28 | undefined_function_calls,
29 | undefined_functions,
30 | locals_not_used,
31 | deprecated_functions_calls,
32 | deprecated_functions
33 | ]}.
34 |
35 | {deps, [
36 | {edown, ".*", {git, "git://github.com/uwiger/edown.git", {tag, "v0.2.4"}}},
37 | {locks, ".*", {git, "git://github.com/uwiger/locks.git", {branch, "master"}}}
38 | ]}.
39 |
--------------------------------------------------------------------------------
/src/dq.app.src:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %% Copyright (c) 2015 Darach Ennis < darach at gmail dot com >
3 | %%
4 | %% Permission is hereby granted, free of charge, to any person obtaining a
5 | %% copy of this software and associated documentation files (the
6 | %% "Software"), to deal in the Software without restriction, including
7 | %% without limitation the rights to use, copy, modify, merge, publish,
8 | %% distribute, sublicense, and/or sell copies of the Software, and to permit
9 | %% persons to whom the Software is furnished to do so, subject to the
10 | %% following conditions:
11 | %%
12 | %% The above copyright notice and this permission notice shall be included
13 | %% in all copies or substantial portions of the Software.
14 | %%
15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 | %% OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | %% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
18 | %% NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 | %% DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 | %% OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21 | %% USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | %%
23 | %% File: dq.app.src. Application library descriptor.
24 | %%
25 | %% -------------------------------------------------------------------
26 | {application, dq,
27 | [
28 | {description, "Fault Tolerant Distributed Erlang Queue library"},
29 | {vsn, "0.1.0"},
30 | {registered, []},
31 | {applications, [
32 | kernel,
33 | stdlib
34 | ]},
35 | {modules, []},
36 | {env, []}
37 | ]}.
38 |
--------------------------------------------------------------------------------
/src/dq.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %% Copyright (c) 2015 Darach Ennis < darach at gmail dot com >
3 | %%
4 | %% Permission is hereby granted, free of charge, to any person obtaining a
5 | %% copy of this software and associated documentation files (the
6 | %% "Software"), to deal in the Software without restriction, including
7 | %% without limitation the rights to use, copy, modify, merge, publish,
8 | %% distribute, sublicense, and/or sell copies of the Software, and to permit
9 | %% persons to whom the Software is furnished to do so, subject to the
10 | %% following conditions:
11 | %%
12 | %% The above copyright notice and this permission notice shall be included
13 | %% in all copies or substantial portions of the Software.
14 | %%
15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 | %% OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | %% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
18 | %% NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 | %% DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 | %% OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21 | %% USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | %%
23 | %% File: dq.erl. Distributed Queue API. Follows the standard Erlang queue API.
24 | %%
25 | %% -------------------------------------------------------------------
26 | -module(dq).
27 |
28 | -export([new/0]).
29 | -export([new/1]).
30 | -export([new/2]).
31 | -export([is_leader/1]).
32 | -export([snapshot/1]).
33 |
34 | -export([is_queue/1]).
35 | -export([is_empty/1]).
36 | -export([len/1]).
37 |
38 | -export([in/2]).
39 | -export([in_r/2]).
40 |
41 | -export([out/1]).
42 | -export([out_r/1]).
43 |
44 | -export([reverse/1]).
45 | -export([split/2]).
46 | -export([join/2]).
47 |
48 | -export([filter/2]).
49 | -export([member/2]).
50 |
51 | -export([get/1]).
52 | -export([get_r/1]).
53 | -export([drop/1]).
54 | -export([drop_r/1]).
55 | -export([peek/1]).
56 | -export([peek_r/1]).
57 |
58 | -export([cons/2]).
59 | -export([head/1]).
60 | -export([tail/1]).
61 | -export([snoc/2]).
62 | -export([daeh/1]).
63 | -export([last/1]).
64 | -export([liat/1]).
65 | -export([init/1]).
66 |
67 | -opaque dq() :: pid().
68 | -type item() :: any().
69 | -type queue() :: any().
70 |
71 | -export_type([dq/0]).
72 | -export_type([item/0]).
73 |
74 | -spec new() -> dq().
75 | %% @doc Create a new global distributed anonymous queue
76 | new() ->
77 | locks_leader:start_link(dq_callback, queue:new()).
78 |
79 | -spec new(term()) -> dq().
80 | %% @doc Create a global distributed queue with name
81 | new(Name) ->
82 | new(Name,[]).
83 |
84 | -spec new(term(),proplists:proplist()) -> dq().
85 | %% @doc Create a global distributed queue with name and options
86 | new(Name,Opts) ->
87 | Debug = lists:keyfind(debug,1,Opts),
88 | case Debug of
89 | {debug,true} ->
90 | dbg:tracer(),
91 | dbg:tpl(locks_leader,x),
92 | dbg:tpl(locks_agent,x),
93 | dbg:tpl(dq_callback,x),
94 | dbg:tpl(dq,x),
95 | dbg:p(all,[c]);
96 | _Other ->
97 | no_debug
98 | end,
99 | locks_leader:start_link(Name, dq_callback, queue:new(), Opts).
100 |
101 | -define(timeout, 2000).
102 | -define(r(Queue,Expr,L), locks_leader:leader_call(Queue, {read, fun(Q) -> Expr end}, ?timeout)).
103 | -define(p(Queue,Expr,L), locks_leader:leader_call(Queue, {pop, fun(Q) -> Expr end}, ?timeout)).
104 | -define(w(Queue,Expr,L), locks_leader:leader_call(Queue, {write, fun(Q) -> Expr end}, ?timeout)).
105 |
106 | -spec is_queue(pid()) -> boolean().
107 | %% @doc Equivalent to queue:is_queue/1
108 | is_queue(Queue) ->
109 | ?r(Queue, queue:is_queue(Q), is_queue).
110 |
111 | -spec is_empty(dq()) -> boolean().
112 | %% @doc Equivalent to queue:is_empty/1
113 | is_empty(Queue) ->
114 | ?r(Queue, queue:is_empty(Q), is_empty).
115 |
116 | -spec len(dq()) -> non_neg_integer().
117 | %% @doc Equivalent to queue:len/1
118 | len(Queue) ->
119 | ?r(Queue, queue:len(Q), len).
120 |
121 | -spec in(item(), dq()) -> item().
122 | %% @doc Equivalent to queue:in/2
123 | in(Item, Queue) ->
124 | ?w(Queue, queue:in(Item,Q), in).
125 |
126 | -spec in_r(item(), dq()) -> item().
127 | %% @doc Equivalent to queue:in_r/2
128 | in_r(Item, Queue) ->
129 | ?w(Queue, queue:in_r(Item,Q), in_r).
130 |
131 | -spec out(dq()) -> item().
132 | %% @doc Equivalent to queue:out/1
133 | out(Queue) ->
134 | ?p(Queue, queue:out(Q), out).
135 |
136 | -spec out_r(dq()) -> item().
137 | %% @doc Equivalent to queue:out_r/1
138 | out_r(Queue) ->
139 | ?p(Queue, queue:out_r(Q), out_r).
140 |
141 | -spec reverse(dq()) -> queue().
142 | %% @doc Equivalent to queue:reverse/1.
143 | reverse(Queue) ->
144 | ?w(Queue, queue:reverse(Q), reverse).
145 |
146 | -spec split(pos_integer(), dq()) -> {queue(),queue()}.
147 | %% @doc Equivalent to queue:split/2.
148 | split(N, Queue) ->
149 | ?r(Queue, queue:split(N, Q), split).
150 |
151 | -spec join(dq(), queue()) -> queue().
152 | %% @doc Equivalent to queue:join/2.
153 | join(Queue,Q2) ->
154 | ?w(Queue, queue:join(Q, Q2), join).
155 |
156 | -spec filter(fun((item()) -> boolean()), dq()) -> queue().
157 | %% @doc Equivalent to queue:filter/2.
158 | filter(Fun, Queue) ->
159 | ?r(Queue, queue:filter(Fun, Q), filter).
160 |
161 | -spec member(item(), dq()) -> boolean().
162 | %% @doc Equivalent to queue:member/2.
163 | member(Item, Queue) ->
164 | ?r(Queue, queue:member(Item, Q), member).
165 |
166 | -spec get(dq()) -> item() | empty.
167 | %% @doc Equivalent to queue:get/1.
168 | get(Queue) ->
169 | case is_empty(Queue) of
170 | true -> empty;
171 | false -> ?r(Queue, queue:get(Q), get)
172 | end.
173 |
174 | -spec get_r(dq()) -> item() | empty.
175 | %% @doc Equivalent to queue:get_r/1.
176 | get_r(Queue) ->
177 | case is_empty(Queue) of
178 | true -> empty;
179 | false -> ?r(Queue, queue:get_r(Q), get_r)
180 | end.
181 |
182 | -spec drop(dq()) -> queue() | empty.
183 | %% @doc Equivalent to queue:drop/1.
184 | drop(Queue) ->
185 | case is_empty(Queue) of
186 | true -> empty;
187 | false -> ?w(Queue, queue:drop(Q), drop)
188 | end.
189 |
190 | -spec drop_r(dq()) -> queue() | empty.
191 | %% @doc Equivalent to queue:drop_r/1.
192 | drop_r(Queue) ->
193 | case is_empty(Queue) of
194 | true -> empty;
195 | false -> ?w(Queue, queue:drop_r(Q), drop_r)
196 | end.
197 |
198 | -spec peek(dq()) -> item().
199 | %% @doc Equivalent to queue:peek/1.
200 | peek(Queue) ->
201 | ?r(Queue, queue:peek(Q), peek).
202 |
203 | -spec peek_r(dq()) -> item().
204 | %% @doc Equivalent to queue:peek_r/1.
205 | peek_r(Queue) ->
206 | ?r(Queue, queue:peek_r(Q), peek_r).
207 |
208 | -spec cons(item(),dq()) -> queue().
209 | %% @doc Equivalent to queue:cons/2.
210 | cons(Item,Queue) ->
211 | ?w(Queue, queue:cons(Item,Q), cons).
212 |
213 | -spec head(dq()) -> item() | empty.
214 | %% @doc Equivalent to queue:head/1.
215 | head(Queue) ->
216 | case is_empty(Queue) of
217 | true -> empty;
218 | false -> ?r(Queue, queue:head(Q), head)
219 | end.
220 |
221 | -spec tail(dq()) -> queue() | empty.
222 | %% @doc Equivalent to queue:tail/1.
223 | tail(Queue) ->
224 | case is_empty(Queue) of
225 | true -> empty;
226 | false -> ?r(Queue, queue:tail(Q), tail)
227 | end.
228 |
229 | -spec snoc(dq(),item()) -> queue().
230 | %% @doc Equivalent to queue:snoc/2.
231 | snoc(Queue,Item) ->
232 | ?w(Queue, queue:snoc(Q,Item), snoc).
233 |
234 | -spec daeh(dq()) -> queue() | empty.
235 | %% @doc Equivalent to queue:daeh/1.
236 | daeh(Queue) ->
237 | case is_empty(Queue) of
238 | true -> empty;
239 | false -> ?r(Queue, queue:daeh(Q), daeh)
240 | end.
241 |
242 | -spec last(dq()) -> item() | empty.
243 | %% @doc Equivalent to queue:last/1.
244 | last(Queue) ->
245 | case is_empty(Queue) of
246 | true -> empty;
247 | false -> ?r(Queue, queue:last(Q), last)
248 | end.
249 |
250 | -spec liat(dq()) -> item() | empty.
251 | %% @doc Equivalent to queue:liat/1.
252 | liat(Queue) ->
253 | case is_empty(Queue) of
254 | true -> empty;
255 | false -> ?w(Queue, queue:liat(Q), liat)
256 | end.
257 |
258 | -spec init(dq()) -> item() | empty.
259 | %% @doc Equivalent to queue:init/1.
260 | init(Queue) ->
261 | case is_empty(Queue) of
262 | true -> empty;
263 | false -> ?w(Queue, queue:init(Q), init)
264 | end.
265 |
266 | -spec is_leader(dq()) -> boolean().
267 | %% @doc Is this queue instance the global distributed queue leader?
268 | is_leader(Queue) ->
269 | InfoList = locks_leader:info(Queue),
270 | {leader,LeaderPid} = lists:keyfind(leader,1,InfoList),
271 | {leader_node,LeaderNode} = lists:keyfind(leader_node,1,InfoList),
272 | Queue =:= LeaderPid andalso LeaderNode =:= node().
273 |
274 | -spec snapshot(dq()) -> queue().
275 | %% @doc Get a snapshot of the distributed queue's current state
276 | snapshot(Queue) ->
277 | InfoList = locks_leader:info(Queue),
278 | {mod_state,{state,_,QueueState}} = lists:keyfind(mod_state,1,InfoList),
279 | QueueState.
280 |
--------------------------------------------------------------------------------
/src/dq_callback.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %% Copyright (c) 2015 Darach Ennis < darach at gmail dot com >
3 | %%
4 | %% Permission is hereby granted, free of charge, to any person obtaining a
5 | %% copy of this software and associated documentation files (the
6 | %% "Software"), to deal in the Software without restriction, including
7 | %% without limitation the rights to use, copy, modify, merge, publish,
8 | %% distribute, sublicense, and/or sell copies of the Software, and to permit
9 | %% persons to whom the Software is furnished to do so, subject to the
10 | %% following conditions:
11 | %%
12 | %% The above copyright notice and this permission notice shall be included
13 | %% in all copies or substantial portions of the Software.
14 | %%
15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 | %% OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | %% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
18 | %% NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 | %% DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 | %% OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21 | %% USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | %%
23 | %% File: dq_callback.erl Distributed Queue API locks_leader behaviour callback
24 | %%
25 | %% -------------------------------------------------------------------
26 | -module(dq_callback).
27 | -behaviour(locks_leader).
28 |
29 | -export([init/1]).
30 | -export([elected/3]).
31 | -export([surrendered/3]).
32 | -export([handle_DOWN/3]).
33 | -export([handle_leader_call/4]).
34 | -export([handle_leader_cast/3]).
35 | -export([from_leader/3]).
36 | -export([handle_call/4]).
37 | -export([handle_cast/3]).
38 | -export([handle_info/3]).
39 | -export([terminate/2]).
40 | -export([code_change/4]).
41 |
42 | %% for testing
43 | -export([merge_cand_states/2]).
44 |
45 | -type queue() :: any().
46 |
47 | -record(state, {
48 | am_leader = false :: boolean(),
49 | queue = undefined :: undefined | queue()
50 | }).
51 |
52 | -type state() :: #state{}.
53 |
54 | -define(event(E), event(?LINE, E)).
55 |
56 | -spec init(queue()) -> {ok, state()}.
57 | init(Queue) ->
58 | ?event({init, Queue}),
59 | {ok, #state{queue = Queue}}.
60 |
61 | -spec elected(state(), locks_leader:leader_info(), pid() | undefined) ->
62 | {ok, term(), state()}
63 | | {reply, term(), state()}
64 | | {ok, _AmLeaderMsg, _FromLeaderMsg, state()}
65 | | {error, term()}.
66 | elected(#state{queue = Queue} = S, I, undefined) ->
67 | ?event(elected_leader),
68 | case locks_leader:new_candidates(I) of
69 | [] ->
70 | ?event({elected, Queue}),
71 | {ok, {sync, Queue}, S#state{am_leader = true}};
72 | Cands ->
73 | ?event({new_candidates, Cands}),
74 | NewQueue = merge_candidates(Queue, I),
75 | {ok, {sync, NewQueue}, S#state{am_leader = true, queue = NewQueue}}
76 | end;
77 | elected(#state{queue = Queue} = S, _E, Pid) when is_pid(Pid) ->
78 | {reply, {sync, Queue}, S#state{am_leader = true}}.
79 |
80 | merge_candidates(Q, I) ->
81 | {Good, _Bad} = locks_leader:ask_candidates(merge, I),
82 | merge_cand_states(Q, Good).
83 |
84 | merge_cand_states(Q, ListOfCandQs) ->
85 | lists:foldl(
86 | fun({C, {true, Q2}}, Acc) ->
87 | ?event({merge_got, C, Q2}),
88 | merge(Acc, Q2);
89 | ({C, false}, Acc) ->
90 | ?event({merge_got, C, false}),
91 | Acc
92 | end, Q, ListOfCandQs).
93 |
94 | merge(Q1,Q2) ->
95 | case queue:out(Q2) of
96 | {empty, _Q} ->
97 | Q1;
98 | {{value,V}, Q3} ->
99 | case queue:member(V,Q1) of
100 | true -> merge(Q1, Q3);
101 | false -> merge(queue:in(V,Q1), Q3)
102 | end
103 | end.
104 |
105 | surrendered(#state{queue = _OurQueue} = S, {sync, LeaderQueue}, _I) ->
106 | ?event({surrendered, LeaderQueue}),
107 | {ok, S#state{queue = LeaderQueue, am_leader = false}}.
108 |
109 | handle_DOWN(_Pid, S, _I) ->
110 | {ok, S}.
111 |
112 | -spec handle_leader_call(term(), pid(), state(), locks_leader:leader_info()) ->
113 | {reply, Reply, NState} |
114 | {reply, Reply, term(), NState} |
115 | {noreply, state()} |
116 | {stop, term(), Reply, NState}.
117 |
118 | handle_leader_call({read,F} = Op, _From, #state{queue = Queue} = S, _I) ->
119 | ?event({handle_leader_call, Op}),
120 | Reply = F(Queue),
121 | {reply, Reply, {read, F}, S#state{queue = Queue}};
122 | handle_leader_call({pop,F} = Op, _From, #state{queue = Queue} = S, _I) ->
123 | ?event({handle_leader_call, Op}),
124 | {_Value, NewQueue}=Ret = F(Queue),
125 | {reply, Ret, {pop, F}, S#state{queue=NewQueue}};
126 | handle_leader_call({write,F} = Op, _From, #state{queue = Queue} = S, _I) ->
127 | ?event({handle_leader_call, Op}),
128 | NewQueue = F(Queue),
129 | {reply, NewQueue, {write, F}, S#state{queue = NewQueue}};
130 | handle_leader_call({leader_lookup,F} = Op, _From, #state{queue = Queue} = S, _I) ->
131 | ?event({handle_leader_call, Op}),
132 | Reply = F(Queue),
133 | {reply, Reply, S#state{queue = Queue}}.
134 |
135 | -spec handle_leader_cast(term(), term(), locks_leader:leader_info()) ->
136 | {ok, state()}.
137 | %% @doc Called by leader in response to a {@link locks_leader:leader_cast/2. leader_cast()}.
138 | handle_leader_cast(_Msg, S, _I) ->
139 | ?event({handle_leader_cast, _Msg}),
140 | {ok, S}.
141 |
142 | -spec from_leader(term(), state(), locks_leader:leader_info()) -> {ok, state()}.
143 | from_leader({sync, D}, #state{} = S, _I) ->
144 | {ok, S#state{queue = D}};
145 | from_leader({read,_F} = Op, #state{queue = Queue} = S, _I) ->
146 | ?event({from_leader, Op}),
147 | {ok, S#state{queue = Queue}};
148 | from_leader({write,F} = Op, #state{queue = Queue} = S, _I) ->
149 | ?event({from_leader, Op}),
150 | NewQueue = F(Queue),
151 | {ok, S#state{queue = NewQueue}};
152 | from_leader({pop,F} = Op, #state{queue = Queue} = S, _I) ->
153 | ?event({from_leader, Op}),
154 | {_Value, NewQueue} = F(Queue),
155 | {ok, S#state{queue = NewQueue}}.
156 |
157 | -spec handle_call(term(), pid(), state(), locks_leader:leader_info()) ->
158 | {reply, term(), state()}
159 | | {noreply, state()}
160 | | {stop, term(), term(), state()}
161 | | {reply, term(), term(), state()}.
162 | handle_call(merge, _From, #state{am_leader = AmLeader, queue = Queue} = S, _I) ->
163 | case AmLeader of
164 | true -> {reply, {true, Queue}, S};
165 | false -> {reply, false, S}
166 | end;
167 | handle_call({read, F}, _From, #state{queue = Queue} = S, _I) ->
168 | Reply = F(Queue),
169 | {reply, Reply, S}.
170 |
171 | -spec handle_cast(term(), state(), locks_leader:leader_info()) ->
172 | {noreply, state()}
173 | | {stop, term(), term(), state()}
174 | | {reply, term(), state()}
175 | | {reply, term(), term(), state()}.
176 | handle_cast(_Msg, S, _I) ->
177 | {noreply, S}.
178 |
179 | -spec handle_info(term(), state(), locks_leader:leader_info()) ->
180 | {noreply, state()}
181 | | {stop, term(), term(), state()}
182 | | {reply, term(), state()}
183 | | {reply, term(), term(), state()}.
184 | handle_info(_Msg, State, _Info) ->
185 | {noreply, State}.
186 |
187 | -spec code_change(string(), term(), locks_leader:leader_info(), term()) ->
188 | {ok, state()}.
189 | code_change(_FromVsn, State, _Info, _Extra) -> {ok, State}.
190 |
191 | -spec terminate(term(),state()) -> ok.
192 | terminate(_Reason, _State) -> ok.
193 |
194 | -spec event(integer(), term()) -> ok.
195 | event(_Line, _Event) -> ok.
196 |
--------------------------------------------------------------------------------
/test/dq_SUITE.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %% Permission is hereby granted, free of charge, to any person obtaining a
3 | %% copy of this software and associated documentation files (the
4 | %% "Software"), to deal in the Software without restriction, including
5 | %% without limitation the rights to use, copy, modify, merge, publish,
6 | %% distribute, sublicense, and/or sell copies of the Software, and to permit
7 | %% persons to whom the Software is furnished to do so, subject to the
8 | %% following conditions:
9 | %%
10 | %% The above copyright notice and this permission notice shall be included
11 | %% in all copies or substantial portions of the Software.
12 | %%
13 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | %% OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15 | %% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
16 | %% NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17 | %% DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | %% OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
19 | %% USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | %% -------------------------------------------------------------------
21 | %% @author Darach Ennis
22 | %% @copyright (C) 2014, Darach Ennis
23 | %%
24 | %% @doc
25 | %%
26 | %% @end
27 | %% -------------------------------------------------------------------
28 | -module(dq_SUITE).
29 |
30 | -export([all/0]).
31 | -export([suite/0]).
32 | -export([init_per_suite/1]).
33 | -export([end_per_suite/1]).
34 | -export([groups/0]).
35 |
36 | -export([t_new_anon/1]).
37 | -export([t_new_named/1]).
38 | -export([t_usage/1]).
39 | -export([t_netsplitheal/1]).
40 |
41 | -include_lib("common_test/include/ct.hrl").
42 |
43 | -define(proptest(TC), proper:quickcheck(TC,[{numtests,10000}])
44 | orelse ct:fail({counterexample, proper:counterexample(TC)})).
45 |
46 | all() ->
47 | [
48 | {group, dq}
49 | ].
50 |
51 | suite() ->
52 | [{ct_hooks,[cth_surefire]}, {timetrap, {seconds, 30}}].
53 |
54 | groups() ->
55 | [
56 | {dq, [], [
57 | t_new_anon,
58 | t_new_named,
59 | t_usage,
60 | t_netsplitheal
61 | ]}
62 | ].
63 |
64 | init_per_suite(Config) ->
65 | {ok,Apps} = application:ensure_all_started(locks),
66 | [ {apps,Apps} | Config ].
67 |
68 | end_per_suite(Config) ->
69 | {apps,Apps} = lists:keyfind(apps,1,Config),
70 | [ application:stop(App) || App <- Apps ],
71 | Config.
72 |
73 | t_new_anon(_Config) ->
74 | {ok,A} = dq:new(),
75 | {ok,B} = dq:new(),
76 | true = A =/= B,
77 | 0 = dq:len(A),
78 | 0 = dq:len(B),
79 | true = dq:is_queue(A),
80 | true = dq:is_queue(B),
81 | dq:in(1,A),
82 | {[1],[]} = dq:snapshot(B),
83 | dq:in(2,B),
84 | {[2],[1]} = dq:snapshot(B),
85 | {[2],[1]} = dq:snapshot(A),
86 | ok.
87 |
88 | t_new_named(_Config) ->
89 | {ok,A} = dq:new(),
90 | true = dq:is_leader(A),
91 | {ok,B} = dq:new(),
92 | false = dq:is_leader(B),
93 | {ok,C} = dq:new(jed,[]),
94 | {ok,_} = dq:new(ned,[{debug,true}]),
95 | {ok,_} = dq:new(bed,[{debug,false}]),
96 | {ok,_} = dq:new(sed,[]),
97 | true = dq:is_leader(C),
98 | true = A =/= B,
99 | true = A =/= C,
100 | 0 = dq:len(A),
101 | 0 = dq:len(B),
102 | 0 = dq:len(C),
103 | true = dq:is_empty(A),
104 | true = dq:is_empty(B),
105 | true = dq:is_empty(C),
106 | true = dq:is_queue(A),
107 | true = dq:is_queue(B),
108 | true = dq:is_queue(C),
109 | dq:in(1,A),
110 | {[1],[]} = dq:snapshot(A),
111 | {[1],[]} = dq:snapshot(B),
112 | {[],[]} = dq:snapshot(C),
113 | false = dq:is_empty(A),
114 | false = dq:is_empty(B),
115 | true = dq:is_empty(C),
116 | dq:in(2,B),
117 | {[2],[1]} = dq:snapshot(A),
118 | {[2],[1]} = dq:snapshot(B),
119 | {[],[]} = dq:snapshot(C),
120 | dq:in(3,C),
121 | {[2],[1]} = dq:snapshot(A),
122 | {[2],[1]} = dq:snapshot(B),
123 | {[3],[]} = dq:snapshot(C),
124 | false = dq:is_empty(C),
125 | ok.
126 |
127 | t_usage(_Config) ->
128 | {ok,A} = dq:new(),
129 | {ok,B} = dq:new(),
130 | {ok,C} = dq:new(other),
131 | dq:in(2,A),
132 | dq:in_r(1,A),
133 | {{value,1},{[],[2]}} = dq:out(A),
134 | {{value,2},{[],[]}} = dq:out_r(A),
135 | {empty,{[],[]}} = dq:out(A),
136 | L = lists:seq(1,9),
137 | [ dq:in(X,A) || X <- L ],
138 | L = queue:to_list(dq:snapshot(B)),
139 | R = lists:reverse(L),
140 | dq:reverse(A),
141 | R = queue:to_list(dq:snapshot(B)),
142 | {{[5,6,7,8],[9]},{[1],[4,3,2]}} = dq:split(5,A),
143 | R = queue:to_list(dq:snapshot(B)),
144 | _X = dq:join(A,queue:in(10,queue:new())),
145 | [9,8,7,6,5,4,3,2,1,10] = queue:to_list(dq:snapshot(B)),
146 | {[10],[8,6,4,2]} = dq:filter(fun(I) -> I rem 2 == 0 end, A),
147 | true = dq:member(10,A),
148 | true = dq:member(2,A),
149 | false = dq:member(11,A),
150 | 9 = dq:get(B),
151 | 10 = dq:get_r(B),
152 | empty = dq:get_r(C),
153 | empty = dq:get(C),
154 | {[10],[8,7,6,5,4,3,2,1]} = dq:drop(A),
155 | {[1,2,3],[8,7,6,5,4]} = dq:drop_r(A),
156 | empty = dq:drop(C),
157 | empty = dq:drop_r(C),
158 | {value,8} = dq:peek(B),
159 | {value,1} = dq:peek_r(B),
160 | empty = dq:peek(C),
161 | empty = dq:peek_r(C),
162 | {[1,2,3],[11,8,7,6,5,4]} = dq:cons(11,B),
163 | 11 = dq:head(A),
164 | empty = dq:head(C),
165 | {[1,2,3],[8,7,6,5,4]} = dq:tail(A),
166 | empty = dq:tail(C),
167 | {[12,1,2,3],[11,8,7,6,5,4]} = dq:snoc(B,12),
168 | 12 = dq:daeh(A),
169 | empty = dq:daeh(C),
170 | 12 = dq:last(B),
171 | empty = dq:last(C),
172 | {[1,2,3],[11,8,7,6,5,4]} = dq:liat(A),
173 | empty = dq:liat(C),
174 | {[2,3],[11,8,7,6,5,4]} = dq:init(B),
175 | empty = dq:init(C),
176 |
177 | % last, but not least, make sure both instances are equivalent
178 | {[2,3],[11,8,7,6,5,4]} = dq:snapshot(A),
179 | {[2,3],[11,8,7,6,5,4]} = dq:snapshot(B),
180 |
181 | ok.
182 |
183 | t_netsplitheal(_Config) ->
184 | %% We let lock_leaders hang wet and assume it just works,
185 | %% But, we need to test the candidate state merge of queues
186 | %% which is done here...
187 | %% @TODO Find evidence of easy to orchestrate multi-node CT tests for inspiration ...
188 |
189 | CandidateStates = [{c, {true, {[1,2],[3]}}}, {c, false}, {c, {true, {[3,4],[5]}}}],
190 |
191 | % 3 is in common and is considered duplicate, and so removed, ...
192 | {[4,5,1,2,3,666],[777]} = dq_callback:merge_cand_states({[666],[777]},CandidateStates),
193 |
194 | ok.
195 |
--------------------------------------------------------------------------------