├── .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** [![Build Status](https://travis-ci.org/darach/dq.svg?branch=master)](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 | 10 | 11 |
dq
dq_callback
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/2Equivalent to queue:cons/2.
daeh/1Equivalent to queue:daeh/1.
drop/1Equivalent to queue:drop/1.
drop_r/1Equivalent to queue:drop_r/1.
filter/2Equivalent to queue:filter/2.
get/1Equivalent to queue:get/1.
get_r/1Equivalent to queue:get_r/1.
head/1Equivalent to queue:head/1.
in/2Equivalent to queue:in/2.
in_r/2Equivalent to queue:in_r/2.
init/1Equivalent to queue:init/1.
is_empty/1Equivalent to queue:is_empty/1.
is_leader/1Is this queue instance the global distributed queue leader?.
is_queue/1Equivalent to queue:is_queue/1.
join/2Equivalent to queue:join/2.
last/1Equivalent to queue:last/1.
len/1Equivalent to queue:len/1.
liat/1Equivalent to queue:liat/1.
member/2Equivalent to queue:member/2.
new/0Create a new global distributed anonymous queue.
new/1Create a global distributed queue with name.
new/2Create a global distributed queue with name and options.
out/1Equivalent to queue:out/1.
out_r/1Equivalent to queue:out_r/1.
peek/1Equivalent to queue:peek/1.
peek_r/1Equivalent to queue:peek_r/1.
reverse/1Equivalent to queue:reverse/1.
snapshot/1Get a snapshot of the distributed queue's current state.
snoc/2Equivalent to queue:snoc/2.
split/2Equivalent to queue:split/2.
tail/1Equivalent 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 |
code_change/4
elected/3
from_leader/3
handle_DOWN/3
handle_call/4
handle_cast/3
handle_info/3
handle_leader_call/4
handle_leader_cast/3Called by leader in response to a leader_cast().
init/1
merge_cand_states/2
surrendered/3
terminate/2
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 | --------------------------------------------------------------------------------