├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.properties
├── build.xml
├── conf
└── maven-mongo-java-distributed-lock.xml
├── lib
├── junit-4.8.2.jar
└── mongo-2.7.3.jar
└── src
├── main
├── META-INF
│ └── MANIFEST.MF
└── com
│ └── deftlabs
│ └── lock
│ └── mongo
│ ├── DistributedLock.java
│ ├── DistributedLockException.java
│ ├── DistributedLockOptions.java
│ ├── DistributedLockSvc.java
│ ├── DistributedLockSvcFactory.java
│ ├── DistributedLockSvcOptions.java
│ └── impl
│ ├── BaseDao.java
│ ├── LockDao.java
│ ├── LockDef.java
│ ├── LockHistoryDao.java
│ ├── LockHistoryDef.java
│ ├── LockImpl.java
│ ├── LockState.java
│ ├── Monitor.java
│ └── SvcImpl.java
├── test
└── com
│ └── deftlabs
│ └── lock
│ └── mongo
│ ├── DistributedLockIntTests.java
│ ├── DriverIntTests.java
│ └── StandaloneThreadLockTests.java
└── unit
└── com
└── deftlabs
└── lock
└── mongo
└── DistributedLockUnitTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS*
2 | *#*
3 | *~
4 | *.swp
5 | build
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 |
2 | language: java
3 |
4 | script: 'ant test'
5 |
6 | services:
7 | - mongodb
8 |
9 | jdk:
10 | - oraclejdk7
11 | - openjdk6
12 | - openjdk7
13 |
14 | env:
15 | - DB=mongodb
16 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | mongo-java-distributed-lock
3 | =============
4 |
5 | About
6 | ------------
7 |
8 | A Java distributed lock library that uses [MongoDB](http://www.mongodb.org/) as the central sync point. [See the Wiki](https://github.com/deftlabs/mongo-java-distributed-lock/wiki) for more information.
9 |
10 | This library is currently in alpha.
11 |
12 | Usage
13 | ------------
14 |
15 | * [Javadocs](http://api.deftlabs.com/mongo-java-distributed-lock)
16 | * [Getting Started](https://github.com/deftlabs/mongo-java-distributed-lock/wiki/Getting-Started)
17 |
18 |
19 | Build
20 | ------------
21 |
22 | [](http://travis-ci.org/deftlabs/mongo-java-distributed-lock)
23 |
24 |
25 | License
26 | ------------
27 |
28 | Copyright 2011, Deft Labs.
29 |
30 | Licensed under the Apache License, Version 2.0 (the "License");
31 | you may not use this file except in compliance with the License.
32 | You may obtain a copy of the License at:
33 |
34 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
35 |
36 | Unless required by applicable law or agreed to in writing, software
37 | distributed under the License is distributed on an "AS IS" BASIS,
38 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
39 | See the License for the specific language governing permissions and
40 | limitations under the License.
41 |
42 |
--------------------------------------------------------------------------------
/build.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2011, Deft Labs.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at:
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | javac.source=1.5
18 |
19 | lib.version=0.1.6
20 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 | ----------------------------------------
301 |
302 | - Compile the Java files ......................... compile
303 |
304 | - Create the jar file ............................ jar
305 |
306 | - Create ALL of the build jar files .............. jar.all
307 |
308 | - Clean the source tree .......................... clean
309 |
310 | - Run the unit tests ............................. unit
311 |
312 | - Run the integration tests ...................... test
313 |
314 | - Generate the Javadocs .......................... javadocs
315 |
316 | ----------------------------------------
317 |
318 |
319 |
320 |
321 |
322 |
323 |
--------------------------------------------------------------------------------
/conf/maven-mongo-java-distributed-lock.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.deftlabs
5 | mongo-java-distributed-lock
6 | jar
7 | Mongo Java Distributed Lock
8 | @VERSION@
9 | A distributed lock backed by MongoDB
10 | https://deftlabs.com
11 |
12 |
13 |
14 | The Apache Software License, Version 2.0
15 | http://www.apache.org/licenses/LICENSE-2.0.txt
16 | repo
17 |
18 |
19 |
20 |
21 | https://github.com/deftlabs
22 |
23 |
24 |
25 |
26 | org.mongodb
27 | mongo-java-driver
28 | [2.6,)
29 |
30 |
31 |
32 |
33 |
34 | Ryan Nitz
35 | Deft Labs
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/lib/junit-4.8.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deftlabs/mongo-java-distributed-lock/33ec19a3a89c57edef44077eb3162365becbae49/lib/junit-4.8.2.jar
--------------------------------------------------------------------------------
/lib/mongo-2.7.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deftlabs/mongo-java-distributed-lock/33ec19a3a89c57edef44077eb3162365becbae49/lib/mongo-2.7.3.jar
--------------------------------------------------------------------------------
/src/main/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Bundle-ManifestVersion: 2
3 | Bundle-Name: mongo-java-distributed-lock
4 | Bundle-SymbolicName: com.deftlabs.lock.mongo
5 | Bundle-Version: @VERSION@
6 | Import-Package: com.mongodb
7 | Export-Package: com.deftlabs.lock.mongo, com.deftlabs.lock.mongo.impl
8 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/DistributedLock.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | // Mongo
20 | import org.bson.types.ObjectId;
21 |
22 | // Java
23 | import java.util.concurrent.locks.Lock;
24 |
25 | /**
26 | * The distributed lock interface.
27 | */
28 | public interface DistributedLock extends Lock {
29 |
30 | /**
31 | * Returns true if the lock is currently locked by the local process.
32 | */
33 | public boolean isLocked();
34 |
35 | /**
36 | * Returns the lock name.
37 | */
38 | public String getName();
39 |
40 | /**
41 | * Returns the lock id if current locked (null if not).
42 | */
43 | public ObjectId getLockId();
44 |
45 | /**
46 | * Returns the options used to configure this lock.
47 | */
48 | public DistributedLockOptions getOptions();
49 |
50 | /**
51 | * Wakeup any blocked threads. This should ONLY be used by the lock service,
52 | * not the user.
53 | */
54 | public void wakeupBlocked();
55 |
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/DistributedLockException.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | /**
20 | * The distributed lock exception.
21 | */
22 | public class DistributedLockException extends RuntimeException {
23 |
24 | private static final long serialVersionUID = -4415279469780082174L;
25 |
26 | public DistributedLockException() { super(); }
27 |
28 | public DistributedLockException(final String pMsg) { super(pMsg); }
29 |
30 | public DistributedLockException(final String pMsg, final Throwable pT) { super(pMsg, pT); }
31 |
32 | public DistributedLockException(final Throwable pT) { super(pT); }
33 |
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/DistributedLockOptions.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | /**
20 | * The options/configuration on a per lock basis. If this is not passed when creating the lock,
21 | * the library defaults are used.
22 | */
23 | public class DistributedLockOptions {
24 |
25 | /**
26 | * Set the inactive lock timeout (time in milliseconds). The default is one minute.
27 | * This means that if your lock process dies or is killed without unlocking first,
28 | * the lock will be reset in one minute (120,000 ms).
29 | */
30 | public void setInactiveLockTimeout(final int pV) { _inactiveLockTimeout = pV; }
31 |
32 | /**
33 | * Returns the inactive lock timeout.
34 | */
35 | public int getInactiveLockTimeout() { return _inactiveLockTimeout; }
36 |
37 | private int _inactiveLockTimeout = 120000;
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/DistributedLockSvc.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | /**
20 | * The distributed lock service interface.
21 | */
22 | public interface DistributedLockSvc {
23 |
24 | /**
25 | * Create a new distributed lock. If a lock already exists with this name,
26 | * the lock will be returned (else a new one is created).
27 | */
28 | public DistributedLock create(final String pLockName);
29 |
30 | /**
31 | * Create a new distributed lock. If a lock already exists with this name,
32 | * the lock will be returned (else a new one is created).
33 | */
34 | public DistributedLock create(final String pLockName, final DistributedLockOptions pOptions);
35 |
36 | /**
37 | * Destroy a distributed lock.
38 | */
39 | public void destroy(final DistributedLock pLock);
40 |
41 | /**
42 | * Part of the usage contract. The startup method is called by the factory.
43 | */
44 | public void startup();
45 |
46 | /**
47 | * Part of the usage contract. Must be called when the app stops.
48 | */
49 | public void shutdown();
50 |
51 | /**
52 | * Returns true if the lock service is running.
53 | */
54 | public boolean isRunning();
55 |
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/DistributedLockSvcFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | // Lib
20 | import com.deftlabs.lock.mongo.impl.SvcImpl;
21 |
22 | /**
23 | * The distrbuted lock service factory.
24 | */
25 | public final class DistributedLockSvcFactory {
26 |
27 | public DistributedLockSvcFactory(final DistributedLockSvcOptions pOptions) {
28 | _options = pOptions;
29 | }
30 |
31 | /**
32 | * Returns the global lock service. This method also calls the startup method on the
33 | * lock service returned (when it is created).
34 | */
35 | public DistributedLockSvc getLockSvc() {
36 | if (_lockSvc != null && _lockSvc.isRunning()) return _lockSvc;
37 |
38 | synchronized(_mutex) {
39 | if (_lockSvc != null && _lockSvc.isRunning()) return _lockSvc;
40 |
41 | final SvcImpl svc = new SvcImpl(_options);
42 | svc.startup();
43 | _lockSvc = svc;
44 | return _lockSvc;
45 | }
46 | }
47 |
48 | private static volatile DistributedLockSvc _lockSvc = null;
49 |
50 | private final DistributedLockSvcOptions _options;
51 | private final static Object _mutex = new Object();
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/DistributedLockSvcOptions.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | // Java
20 | import java.net.InetAddress;
21 | import java.net.UnknownHostException;
22 |
23 |
24 | /**
25 | * The global distributed lock service options/config.
26 | */
27 | public class DistributedLockSvcOptions {
28 |
29 | /**
30 | * The basic constructor. This uses the following:
31 | *
32 | *
database name: mongo-distributed-lock
33 | *
collection name: locks
34 | *
35 | *
36 | */
37 | public DistributedLockSvcOptions(final String pMongoUri)
38 | { this(pMongoUri, "mongo-distributed-lock", "locks", null); }
39 |
40 | /**
41 | * Constructor that allows the user to specify database and colleciton name.
42 | */
43 | public DistributedLockSvcOptions( final String pMongoUri,
44 | final String pDbName,
45 | final String pCollectionName)
46 | { this(pMongoUri, pDbName, pCollectionName, null); }
47 |
48 | /**
49 | * Constructor that allows the user to specify database, colleciton and app name.
50 | * The app name should definetly be used if the db/collection names are shared by multiple
51 | * apps/systems (e.g., SomeCoolDataProcessor).
52 | */
53 | public DistributedLockSvcOptions( final String pMongoUri,
54 | final String pDbName,
55 | final String pCollectionName,
56 | final String pAppName)
57 | {
58 | _mongoUri = pMongoUri;
59 | _dbName = pDbName;
60 | _collectionName = pCollectionName;
61 | _appName = pAppName;
62 |
63 | try { _hostAddress = InetAddress.getLocalHost().getHostAddress();
64 | } catch (final UnknownHostException e) { _hostAddress = null; }
65 |
66 | try { _hostname = InetAddress.getLocalHost().getHostName();
67 | } catch (final UnknownHostException e) { _hostname = null; }
68 | }
69 |
70 | public String getMongoUri() { return _mongoUri; }
71 | public String getDbName() { return _dbName; }
72 | public String getCollectionName() { return _collectionName; }
73 | public String getAppName() { return _appName; }
74 |
75 | /**
76 | * True by default. Set to false to disable storing historical data. Lock data
77 | * is tracked when individual locks are locked, unlocked and (possibly) timed out.
78 | */
79 | public void setEnableHistory(final boolean pV) { _enableHistory = pV; }
80 | public boolean getEnableHistory() { return _enableHistory; }
81 |
82 | /**
83 | * True by default. Set to fault to use a regular collection instead of a
84 | * capped collection for lock history. Entries will never be deleted if
85 | * this is not a capped collection.
86 | */
87 | public void setHistoryIsCapped(final boolean pV) { _historyIsCapped = pV; }
88 | public boolean getHistoryIsCapped() { return _historyIsCapped; }
89 |
90 | /**
91 | * Set the size (in bytes) of the historical capped collection. The default
92 | * is 200MB.
93 | */
94 | public void setHistorySize(final long pV) { _historySize = pV; }
95 | public long getHistorySize() { return _historySize; }
96 |
97 | public String getLibVersion() { return LIB_VERSION; }
98 |
99 | public String getHostname() { return _hostname; }
100 | public void setHostname(final String pV) { _hostname = pV; }
101 |
102 | public String getHostAddress() { return _hostAddress; }
103 |
104 | /**
105 | * Milliseconds between heartbeat checks.
106 | */
107 | public long getHeartbeatFrequency() { return _heartbeatFrequency; }
108 | public void setHeartbeatFrequency(final long pHeartbeatFrequency) {
109 | _heartbeatFrequency = pHeartbeatFrequency;
110 | }
111 |
112 | /**
113 | * Milliseconds between lock timeout checks.
114 | */
115 | public long getTimeoutFrequency() { return _timeoutFrequency; }
116 | public void setTimeoutFrequency(final long pTimeoutFrequency) {
117 | _timeoutFrequency = pTimeoutFrequency;
118 | }
119 |
120 | /**
121 | * Milliseconds between lock unlocked checks.
122 | */
123 | public long getLockUnlockedFrequency() { return _lockUnlockedFrequency; }
124 | public void setLockUnlockedFrequency(final long pLockUnlockedFrequency) {
125 | _lockUnlockedFrequency = pLockUnlockedFrequency;
126 | }
127 |
128 | /**
129 | * The default collection name is: lockHistory. Override here.
130 | */
131 | public void setHistoryCollectionName(final String pV) { _historyCollectionName = pV; }
132 | public String getHistoryCollectionName() { return _historyCollectionName; }
133 |
134 | private final String _mongoUri;
135 | private final String _dbName;
136 | private final String _collectionName;
137 | private String _historyCollectionName = "lockHistory";
138 |
139 | private String _hostname;
140 | private String _hostAddress;
141 |
142 | private final String _appName;
143 |
144 | private static final String LIB_VERSION = "@LIB_VERSION@";
145 |
146 | private boolean _enableHistory = true;
147 | private boolean _historyIsCapped = true;
148 | private long _historySize = 209715200;
149 |
150 | private long _heartbeatFrequency = 5000;
151 | private long _timeoutFrequency = 60000;
152 | private long _lockUnlockedFrequency = 1000;
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/BaseDao.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | // Lib
20 | import com.deftlabs.lock.mongo.DistributedLockOptions;
21 | import com.deftlabs.lock.mongo.DistributedLockSvcOptions;
22 |
23 | // Mongo
24 | import com.mongodb.Mongo;
25 | import com.mongodb.DB;
26 | import com.mongodb.DBCollection;
27 | import com.mongodb.BasicDBObject;
28 | import com.mongodb.WriteResult;
29 | import com.mongodb.WriteConcern;
30 | import com.mongodb.CommandResult;
31 | import org.bson.types.ObjectId;
32 |
33 | // Java
34 | import java.util.Date;
35 |
36 | /**
37 | * The base dao.
38 | */
39 | abstract class BaseDao {
40 |
41 | protected final static void requestStart( final Mongo pMongo,
42 | final DistributedLockSvcOptions pSvcOptions)
43 | { getDb(pMongo, pSvcOptions).requestStart(); }
44 |
45 | protected final static void requestDone(final Mongo pMongo,
46 | final DistributedLockSvcOptions pSvcOptions)
47 | { getDb(pMongo, pSvcOptions).requestDone(); }
48 |
49 | /**
50 | * Returns the db.
51 | */
52 | protected final static DB getDb(final Mongo pMongo,
53 | final DistributedLockSvcOptions pSvcOptions)
54 | { return pMongo.getDB(pSvcOptions.getDbName()); }
55 |
56 | /**
57 | * Returns the current server time. This makes a few requests to the server to try and adjust for
58 | * network latency.
59 | */
60 | protected final static long getServerTime( final Mongo pMongo,
61 | final DistributedLockSvcOptions pSvcOptions)
62 | {
63 |
64 | final long [] localTimes = new long[SERVER_TIME_TRIES];
65 | final int [] latency = new int[SERVER_TIME_TRIES];
66 |
67 | long startTime;
68 | BasicDBObject serverStatus;
69 |
70 | for (int idx=0; idx < SERVER_TIME_TRIES; idx++) {
71 | startTime = System.currentTimeMillis();
72 | serverStatus = getDb(pMongo, pSvcOptions).command(_serverStatusCmd);
73 | latency[idx] = (int)(System.currentTimeMillis() - startTime);
74 | localTimes[idx] = ((Date)serverStatus.get(LOCAL_TIME_FIELD)).getTime();
75 | }
76 |
77 | final long serverTime = localTimes[(SERVER_TIME_TRIES -1)];
78 |
79 | // Adjust based on latency.
80 | return (serverTime + getHalfRoundedAvg(latency));
81 | }
82 |
83 | /**
84 | * We assume that latency is 50% each way.
85 | */
86 | protected final static int getHalfRoundedAvg(final int [] pV) {
87 | int total = 0;
88 | for (int idx=0; idx < pV.length; idx++) total += pV[idx];
89 | return Math.round((((float)total / (float)pV.length) / (float)2));
90 | }
91 |
92 | protected static final String INC = "$inc";
93 | protected static final String SET = "$set";
94 |
95 | protected static final String LT = "$lt";
96 |
97 | private static final String LOCAL_TIME_FIELD = "localTime";
98 | private static final int SERVER_TIME_TRIES = 3;
99 |
100 | protected static final int DUPLICATE_KEY_ERROR_CODE = 11000;
101 |
102 | protected static final BasicDBObject _serverStatusCmd = new BasicDBObject("serverStatus", 1);
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/LockDao.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | // Lib
20 | import com.deftlabs.lock.mongo.DistributedLockOptions;
21 | import com.deftlabs.lock.mongo.DistributedLockSvcOptions;
22 |
23 | // Mongo
24 | import com.mongodb.Mongo;
25 | import com.mongodb.DB;
26 | import com.mongodb.DBCursor;
27 | import com.mongodb.DBCollection;
28 | import com.mongodb.BasicDBObject;
29 | import com.mongodb.WriteResult;
30 | import com.mongodb.WriteConcern;
31 | import com.mongodb.CommandResult;
32 | import org.bson.types.ObjectId;
33 |
34 | // Java
35 | import java.util.Date;
36 |
37 | /**
38 | * The distributed lock dao. These are a set of static methods
39 | * that are responsible for data access.
40 | */
41 | final class LockDao extends BaseDao {
42 |
43 | /**
44 | * Increment the lock heartbeat time.
45 | */
46 | static void heartbeat( final Mongo pMongo,
47 | final String pLockName,
48 | final ObjectId pLockId,
49 | final DistributedLockOptions pLockOptions,
50 | final DistributedLockSvcOptions pSvcOptions)
51 |
52 | {
53 | try {
54 | requestStart(pMongo, pSvcOptions);
55 |
56 | final long serverTime = getServerTime(pMongo, pSvcOptions);
57 |
58 | final BasicDBObject query = new BasicDBObject(LockDef.ID.field, pLockName);
59 | query.put(LockDef.LOCK_ID.field, pLockId);
60 | query.put(LockDef.STATE.field, LockState.LOCKED.code());
61 |
62 | final BasicDBObject toSet = new BasicDBObject(LockDef.LAST_HEARTBEAT.field, new Date(serverTime));
63 | toSet.put(LockDef.LOCK_TIMEOUT_TIME.field, new Date(serverTime + pLockOptions.getInactiveLockTimeout()));
64 |
65 | getDbCollection(pMongo, pSvcOptions).update(query, new BasicDBObject(SET, toSet), false, false, WriteConcern.SAFE);
66 |
67 | } finally { requestDone(pMongo, pSvcOptions); }
68 | }
69 |
70 | /**
71 | * Try and get the lock. If unable to do so, this returns false.
72 | */
73 | static synchronized ObjectId lock( final Mongo pMongo,
74 | final String pLockName,
75 | final DistributedLockSvcOptions pSvcOptions,
76 | final DistributedLockOptions pLockOptions)
77 | {
78 | try {
79 | requestStart(pMongo, pSvcOptions);
80 |
81 | // Lookup the lock object.
82 | BasicDBObject lockDoc = findById(pMongo, pLockName, pSvcOptions);
83 |
84 | final long serverTime = getServerTime(pMongo, pSvcOptions);
85 | final long startTime = System.currentTimeMillis();
86 |
87 | // The doc was not there so we are going to try and insert a new doc.
88 | if (lockDoc == null) {
89 | final ObjectId lockId
90 | = tryInsertNew(pMongo, pLockName, pSvcOptions, pLockOptions,serverTime, startTime);
91 | if (lockId != null) return lockId;
92 | }
93 |
94 | if (lockDoc == null) lockDoc = findById(pMongo, pLockName, pSvcOptions);
95 |
96 | // Get the state.
97 | final LockState lockState = LockState.findByCode(lockDoc.getString(LockDef.STATE.field));
98 |
99 | final ObjectId currentLockId = lockDoc.getObjectId(LockDef.LOCK_ID.field);
100 |
101 | // If it is unlocked, then try and lock.
102 | if (lockState.isUnlocked()) {
103 | final ObjectId lockId
104 | = tryLockingExisting( pMongo, pLockName, currentLockId, pSvcOptions, pLockOptions, serverTime, startTime);
105 | if (lockId != null) return lockId;
106 | }
107 |
108 | final ObjectId lockId = (ObjectId)lockDoc.get(LockDef.LOCK_ID.field);
109 |
110 | // Could not get the lock.
111 | incrementLockAttemptCount(pMongo, pLockName, lockId, pSvcOptions);
112 |
113 | return null;
114 |
115 | } finally { requestDone(pMongo, pSvcOptions); }
116 | }
117 |
118 | private static ObjectId tryLockingExisting( final Mongo pMongo,
119 | final String pLockName,
120 | final ObjectId pCurrentLockId,
121 | final DistributedLockSvcOptions pSvcOptions,
122 | final DistributedLockOptions pLockOptions,
123 | final long pServerTime,
124 | final long pStartTime)
125 | {
126 | final long adjustTime = System.currentTimeMillis() - pStartTime;
127 |
128 | final long serverTime = pServerTime + adjustTime;
129 | final Date now = new Date(serverTime);
130 |
131 | final ObjectId lockId = ObjectId.get();
132 |
133 | final BasicDBObject query = new BasicDBObject(LockDef.ID.field, pLockName);
134 | query.put(LockDef.LOCK_ID.field, pCurrentLockId);
135 | query.put(LockDef.STATE.field, LockState.UNLOCKED.code());
136 |
137 | final BasicDBObject toSet = new BasicDBObject();
138 | toSet.put(LockDef.LIBRARY_VERSION.field, pSvcOptions.getLibVersion());
139 | toSet.put(LockDef.UPDATED.field, now);
140 | toSet.put(LockDef.LAST_HEARTBEAT.field, now);
141 | toSet.put(LockDef.LOCK_ACQUIRED_TIME.field, now);
142 | toSet.put(LockDef.LOCK_TIMEOUT_TIME.field, new Date((serverTime + pLockOptions.getInactiveLockTimeout())));
143 | toSet.put(LockDef.LOCK_ID.field, lockId);
144 | toSet.put(LockDef.STATE.field, LockState.LOCKED.code());
145 | toSet.put(LockDef.OWNER_APP_NAME.field, pSvcOptions.getAppName());
146 | toSet.put(LockDef.OWNER_ADDRESS.field, pSvcOptions.getHostAddress());
147 | toSet.put(LockDef.OWNER_HOSTNAME.field, pSvcOptions.getHostname());
148 | toSet.put(LockDef.OWNER_THREAD_ID.field, Thread.currentThread().getId());
149 | toSet.put(LockDef.OWNER_THREAD_NAME.field, Thread.currentThread().getName());
150 | toSet.put(LockDef.OWNER_THREAD_GROUP_NAME.field, Thread.currentThread().getThreadGroup().getName());
151 | toSet.put(LockDef.LOCK_ATTEMPT_COUNT.field, 0);
152 | toSet.put(LockDef.INACTIVE_LOCK_TIMEOUT.field, pLockOptions.getInactiveLockTimeout());
153 |
154 | // Try and modify the existing lock.
155 | final BasicDBObject lockDoc
156 | = (BasicDBObject)getDbCollection(pMongo, pSvcOptions).findAndModify(query, new BasicDBObject(LockDef.LOCK_ID.field, 1), null, false, new BasicDBObject(SET, toSet), true, false);
157 |
158 | if (lockDoc == null) return null;
159 | if (!lockDoc.containsField(LockDef.LOCK_ID.field)) return null;
160 |
161 | final ObjectId returnedLockId = lockDoc.getObjectId(LockDef.LOCK_ID.field);
162 | if (returnedLockId == null) return null;
163 | if (!returnedLockId.equals(lockId)) return null;
164 |
165 | if (pSvcOptions.getEnableHistory())
166 | { LockHistoryDao.insert(pMongo, pLockName, pSvcOptions, pLockOptions, serverTime, LockState.LOCKED, lockId, false); }
167 |
168 | // Yay... we have the lock.
169 | return lockId;
170 | }
171 |
172 | /**
173 | * This will try and create the object. If successful, it will return the lock id.
174 | * Otherwise, it will return null (i.e., no lock).
175 | */
176 | private static ObjectId tryInsertNew( final Mongo pMongo,
177 | final String pLockName,
178 | final DistributedLockSvcOptions pSvcOptions,
179 | final DistributedLockOptions pLockOptions,
180 | final long pServerTime,
181 | final long pStartTime)
182 | {
183 | final long adjustTime = System.currentTimeMillis() - pStartTime;
184 |
185 | final long serverTime = pServerTime + adjustTime;
186 | final Date now = new Date(serverTime);
187 | final ObjectId lockId = ObjectId.get();
188 |
189 | final Thread currentThread = Thread.currentThread();
190 |
191 | final BasicDBObject lockDoc = new BasicDBObject(LockDef.ID.field, pLockName);
192 | lockDoc.put(LockDef.LIBRARY_VERSION.field, pSvcOptions.getLibVersion());
193 | lockDoc.put(LockDef.UPDATED.field, now);
194 | lockDoc.put(LockDef.LAST_HEARTBEAT.field, now);
195 | lockDoc.put(LockDef.LOCK_ACQUIRED_TIME.field, now);
196 | lockDoc.put(LockDef.LOCK_ID.field, lockId);
197 | lockDoc.put(LockDef.STATE.field, LockState.LOCKED.code());
198 | lockDoc.put(LockDef.LOCK_TIMEOUT_TIME.field, new Date((serverTime + pLockOptions.getInactiveLockTimeout())));
199 | lockDoc.put(LockDef.OWNER_APP_NAME.field, pSvcOptions.getAppName());
200 | lockDoc.put(LockDef.OWNER_ADDRESS.field, pSvcOptions.getHostAddress());
201 | lockDoc.put(LockDef.OWNER_HOSTNAME.field, pSvcOptions.getHostname());
202 | lockDoc.put(LockDef.OWNER_THREAD_ID.field, currentThread.getId());
203 | lockDoc.put(LockDef.OWNER_THREAD_NAME.field, currentThread.getName());
204 | lockDoc.put(LockDef.OWNER_THREAD_GROUP_NAME.field, currentThread.getThreadGroup().getName());
205 | lockDoc.put(LockDef.LOCK_ATTEMPT_COUNT.field, 0);
206 | lockDoc.put(LockDef.INACTIVE_LOCK_TIMEOUT.field, pLockOptions.getInactiveLockTimeout());
207 |
208 | // Insert, if successful then get out of here.
209 | final WriteResult result = getDbCollection(pMongo, pSvcOptions).insert(lockDoc, WriteConcern.NORMAL);
210 | final CommandResult cmdResult = result.getLastError(WriteConcern.NORMAL);
211 |
212 | if (!cmdResult.ok() || cmdResult.getException() != null || cmdResult.getErrorMessage() != null) return null;
213 |
214 | if (pSvcOptions.getEnableHistory())
215 | { LockHistoryDao.insert( pMongo, pLockName, pSvcOptions, pLockOptions, serverTime, LockState.LOCKED, lockId, false); }
216 |
217 | return lockId;
218 | }
219 |
220 | /**
221 | * Returns true if the lock is locked.
222 | */
223 | static boolean isLocked(final Mongo pMongo,
224 | final String pLockName,
225 | final DistributedLockSvcOptions pSvcOptions)
226 | {
227 | final BasicDBObject lock
228 | = (BasicDBObject)getDbCollection(pMongo, pSvcOptions).findOne(new BasicDBObject(LockDef.ID.field, pLockName), new BasicDBObject(LockDef.STATE.field, 1));
229 |
230 | if (lock == null) return false;
231 |
232 | final LockState state = LockState.findByCode(lock.getString(LockDef.STATE.field));
233 |
234 | return state.isLocked();
235 | }
236 |
237 | /**
238 | * Find by lock name/id.
239 | */
240 | static BasicDBObject findById( final Mongo pMongo,
241 | final String pLockName,
242 | final DistributedLockSvcOptions pSvcOptions)
243 | { return (BasicDBObject)getDbCollection(pMongo, pSvcOptions).findOne(new BasicDBObject(LockDef.ID.field, pLockName)); }
244 |
245 | /**
246 | * Increment the waiting request count. This can be used by application developers
247 | * to diagnose problems with their applications.
248 | */
249 | static void incrementLockAttemptCount( final Mongo pMongo,
250 | final String pLockName,
251 | final ObjectId pLockId,
252 | final DistributedLockSvcOptions pSvcOptions)
253 | {
254 |
255 | final BasicDBObject query = new BasicDBObject(LockDef.ID.field, pLockName);
256 | query.put(LockDef.LOCK_ID.field, pLockId);
257 |
258 | getDbCollection(pMongo, pSvcOptions)
259 | .update(query, new BasicDBObject(INC, new BasicDBObject(LockDef.LOCK_ATTEMPT_COUNT.field, 1)), false, false, WriteConcern.SAFE);
260 | }
261 |
262 | /**
263 | * Unlock the lock.
264 | */
265 | static synchronized void unlock(final Mongo pMongo,
266 | final String pLockName,
267 | final DistributedLockSvcOptions pSvcOptions,
268 | final DistributedLockOptions pLockOptions,
269 | final ObjectId pLockId)
270 | {
271 | final BasicDBObject toSet = new BasicDBObject();
272 | toSet.put(LockDef.LIBRARY_VERSION.field, null);
273 | toSet.put(LockDef.UPDATED.field, new Date(getServerTime(pMongo, pSvcOptions)));
274 | toSet.put(LockDef.LOCK_ACQUIRED_TIME.field, null);
275 | toSet.put(LockDef.LOCK_TIMEOUT_TIME.field, null);
276 | toSet.put(LockDef.LOCK_ID.field, null);
277 | toSet.put(LockDef.STATE.field, LockState.UNLOCKED.code());
278 | toSet.put(LockDef.OWNER_APP_NAME.field, null);
279 | toSet.put(LockDef.OWNER_ADDRESS.field, null);
280 | toSet.put(LockDef.OWNER_HOSTNAME.field, null);
281 | toSet.put(LockDef.OWNER_THREAD_ID.field, null);
282 | toSet.put(LockDef.OWNER_THREAD_NAME.field, null);
283 | toSet.put(LockDef.OWNER_THREAD_GROUP_NAME.field, null);
284 | toSet.put(LockDef.LOCK_ATTEMPT_COUNT.field, 0);
285 | toSet.put(LockDef.INACTIVE_LOCK_TIMEOUT.field, null);
286 |
287 | final BasicDBObject query = new BasicDBObject(LockDef.ID.field, pLockName);
288 | query.put(LockDef.LOCK_ID.field, pLockId);
289 | query.put(LockDef.STATE.field, LockState.LOCKED.code());
290 |
291 | // Reset the values in the lock.
292 | getDbCollection(pMongo, pSvcOptions).findAndModify(query, null, null, false, new BasicDBObject(SET, toSet), false, false);
293 |
294 | // If history is enabled, log.
295 | if (pSvcOptions.getEnableHistory())
296 | { LockHistoryDao.insert(pMongo, pLockName, pSvcOptions, pLockOptions, 0, LockState.UNLOCKED, pLockId, false); }
297 | }
298 |
299 | /**
300 | * Check for expired/inactive/dead locks and unlock.
301 | */
302 | static void expireInactiveLocks(final Mongo pMongo, final DistributedLockSvcOptions pSvcOptions) {
303 | // Adjust the time buffer to make sure we do not have small time issues.
304 | final long queryServerTime = getServerTime(pMongo, pSvcOptions);
305 |
306 | final BasicDBObject query = new BasicDBObject(LockDef.STATE.field, LockState.LOCKED.code());
307 | query.put(LockDef.LOCK_TIMEOUT_TIME.field, new BasicDBObject(LT, new Date(queryServerTime)));
308 |
309 | final DBCursor cur = getDbCollection(pMongo, pSvcOptions).find(query).batchSize(10);
310 |
311 | try {
312 | while (cur.hasNext()) {
313 |
314 | final BasicDBObject lockDoc = (BasicDBObject)cur.next();
315 |
316 | final ObjectId lockId = (ObjectId)lockDoc.get(LockDef.LOCK_ID.field);
317 | final String lockName = lockDoc.getString(LockDef.ID.field);
318 |
319 | final long serverTime = getServerTime(pMongo, pSvcOptions);
320 |
321 | final BasicDBObject toSet = new BasicDBObject();
322 | toSet.put(LockDef.LIBRARY_VERSION.field, null);
323 | toSet.put(LockDef.UPDATED.field, new Date(serverTime));
324 | toSet.put(LockDef.LOCK_ACQUIRED_TIME.field, null);
325 | toSet.put(LockDef.LOCK_TIMEOUT_TIME.field, null);
326 | toSet.put(LockDef.LOCK_ID.field, null);
327 | toSet.put(LockDef.STATE.field, LockState.UNLOCKED.code());
328 | toSet.put(LockDef.OWNER_APP_NAME.field, null);
329 | toSet.put(LockDef.OWNER_ADDRESS.field, null);
330 | toSet.put(LockDef.OWNER_HOSTNAME.field, null);
331 | toSet.put(LockDef.OWNER_THREAD_ID.field, null);
332 | toSet.put(LockDef.OWNER_THREAD_NAME.field, null);
333 | toSet.put(LockDef.OWNER_THREAD_GROUP_NAME.field, null);
334 | toSet.put(LockDef.LOCK_ATTEMPT_COUNT.field, 0);
335 | toSet.put(LockDef.INACTIVE_LOCK_TIMEOUT.field, null);
336 |
337 | final BasicDBObject timeoutQuery = new BasicDBObject(LockDef.ID.field, lockName);
338 | timeoutQuery.put(LockDef.LOCK_ID.field, lockId);
339 | timeoutQuery.put(LockDef.STATE.field, LockState.LOCKED.code());
340 | timeoutQuery.put(LockDef.LOCK_TIMEOUT_TIME.field, new BasicDBObject(LT, new Date(serverTime)));
341 |
342 | final BasicDBObject previousLockDoc
343 | = (BasicDBObject)getDbCollection(pMongo, pSvcOptions)
344 | .findAndModify(timeoutQuery, new BasicDBObject(LockDef.INACTIVE_LOCK_TIMEOUT.field, 1), null, false, new BasicDBObject(SET, toSet), false, false);
345 |
346 | if (previousLockDoc == null) continue;
347 |
348 | if (!pSvcOptions.getEnableHistory()) continue;
349 |
350 | // Insert the history state.
351 | LockHistoryDao.insert(pMongo, lockName, pSvcOptions, previousLockDoc.getInt(LockDef.INACTIVE_LOCK_TIMEOUT.field), serverTime, LockState.LOCKED, lockId, true);
352 | }
353 | } finally { if (cur != null) cur.close(); }
354 | }
355 |
356 | /**
357 | * Returns the collection.
358 | */
359 | private static DBCollection getDbCollection(final Mongo pMongo,
360 | final DistributedLockSvcOptions pSvcOptions)
361 | { return getDb(pMongo, pSvcOptions).getCollection(pSvcOptions.getCollectionName()); }
362 |
363 | /**
364 | * Ensure the proper indexes are on the collection. This must be called when
365 | * the service sarts.
366 | */
367 | static void setup(final Mongo pMongo, final DistributedLockSvcOptions pSvcOptions) {
368 | getDbCollection(pMongo, pSvcOptions).ensureIndex(new BasicDBObject(LockDef.LAST_HEARTBEAT.field, 1), "lastHeartbeatV1Idx", false);
369 | getDbCollection(pMongo, pSvcOptions).ensureIndex(new BasicDBObject(LockDef.OWNER_APP_NAME.field, 1), "ownerAppNameV1Idx", false);
370 | getDbCollection(pMongo, pSvcOptions).ensureIndex(new BasicDBObject(LockDef.STATE.field, 1), "stateV1Idx", false);
371 | getDbCollection(pMongo, pSvcOptions).ensureIndex(new BasicDBObject(LockDef.LOCK_ID.field, 1), "lockIdV1Idx", false);
372 |
373 | final BasicDBObject idStateIdx = new BasicDBObject(LockDef.ID.field, 1);
374 | idStateIdx.put(LockDef.STATE.field, 1);
375 | getDbCollection(pMongo, pSvcOptions).ensureIndex(idStateIdx, "idStateV1Idx", false);
376 |
377 | final BasicDBObject idLockIdIdx = new BasicDBObject(LockDef.ID.field, 1);
378 | idStateIdx.put(LockDef.LOCK_ID.field, 1);
379 | getDbCollection(pMongo, pSvcOptions).ensureIndex(idLockIdIdx, "idLockIdV1Idx", false);
380 |
381 | final BasicDBObject stateTimeoutIdx = new BasicDBObject(LockDef.STATE.field, 1);
382 | stateTimeoutIdx.put(LockDef.LOCK_TIMEOUT_TIME.field, 1);
383 | getDbCollection(pMongo, pSvcOptions).ensureIndex(stateTimeoutIdx, "stateTimeoutV1Idx", false);
384 |
385 | final BasicDBObject idStateLockIdIdx = new BasicDBObject(LockDef.ID.field, 1);
386 | idStateLockIdIdx.put(LockDef.LOCK_ID.field, 1);
387 | idStateLockIdIdx.put(LockDef.STATE.field, 1);
388 | getDbCollection(pMongo, pSvcOptions).ensureIndex(idStateLockIdIdx, "idStateLockIdV1Idx", false);
389 |
390 |
391 | final BasicDBObject idStateLockIdTimeoutIdx = new BasicDBObject(LockDef.ID.field, 1);
392 | idStateLockIdTimeoutIdx.put(LockDef.LOCK_ID.field, 1);
393 | idStateLockIdTimeoutIdx.put(LockDef.STATE.field, 1);
394 | idStateLockIdTimeoutIdx.put(LockDef.LOCK_TIMEOUT_TIME.field, 1);
395 | getDbCollection(pMongo, pSvcOptions).ensureIndex(idStateLockIdTimeoutIdx, "idStateLockIdTimeoutV1Idx", false);
396 |
397 | }
398 | }
399 |
400 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/LockDef.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | /**
20 | * The distributed lock db data fields.
21 | */
22 | enum LockDef {
23 | ID("_id"), // This is the lock name
24 | LIBRARY_VERSION("libraryVersion"), // A string with the java library version number
25 | UPDATED("lastUpdated"),
26 | LAST_HEARTBEAT("lastHeartbeat"),
27 | LOCK_ACQUIRED_TIME("lockAcquired"), // The time the lock was granted
28 |
29 | STATE("lockState"), // The current state of this lock
30 |
31 | LOCK_ID("lockId"), // The generated id is used to ensure multiple thread/processes don't step on each other
32 |
33 | OWNER_APP_NAME("appName"), // The name of the application who has the lock (optional)
34 |
35 | OWNER_ADDRESS("ownerAddress"),
36 | OWNER_HOSTNAME("ownerHostname"),
37 | OWNER_THREAD_ID("ownerThreadId"),
38 | OWNER_THREAD_NAME("ownerThreadName"),
39 | OWNER_THREAD_GROUP_NAME("ownerThreadGroupName"),
40 |
41 | INACTIVE_LOCK_TIMEOUT("inactiveLockTimeout"), // The number of ms before timeout (since last heartbeat)
42 | LOCK_TIMEOUT_TIME("lockTimeoutTime"),
43 |
44 | LOCK_ATTEMPT_COUNT("lockAttemptCount"); // The number of times another thread/process has requested this lock (since locked)
45 |
46 | private LockDef(final String pField) { field = pField; }
47 | final String field;
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/LockHistoryDao.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | // Lib
20 | import com.deftlabs.lock.mongo.DistributedLockOptions;
21 | import com.deftlabs.lock.mongo.DistributedLockSvcOptions;
22 |
23 | // Mongo
24 | import com.mongodb.Mongo;
25 | import com.mongodb.DB;
26 | import com.mongodb.DBCollection;
27 | import com.mongodb.BasicDBObject;
28 | import com.mongodb.WriteResult;
29 | import com.mongodb.WriteConcern;
30 | import com.mongodb.CommandResult;
31 | import org.bson.types.ObjectId;
32 |
33 | // Java
34 | import java.util.Date;
35 |
36 | /**
37 | * The distributed lock history dao. These are calls enabled by default, but can
38 | * be disabled in DistributedLockSvcOptions.
39 | */
40 | final class LockHistoryDao extends BaseDao {
41 |
42 | /**
43 | * Insert an entry.
44 | */
45 | static void insert( final Mongo pMongo,
46 | final String pLockName,
47 | final DistributedLockSvcOptions pSvcOptions,
48 | final DistributedLockOptions pLockOptions,
49 | long pServerTime,
50 | final LockState pLockState,
51 | final Object pLockId,
52 | final boolean pTimedOut)
53 | {
54 |
55 | insert(pMongo, pLockName, pSvcOptions, pLockOptions.getInactiveLockTimeout(), pServerTime, pLockState, pLockId, pTimedOut);
56 | }
57 |
58 |
59 | /**
60 | * Insert an entry.
61 | */
62 | static void insert( final Mongo pMongo,
63 | final String pLockName,
64 | final DistributedLockSvcOptions pSvcOptions,
65 | final int pInactiveLockTimeout,
66 | long pServerTime,
67 | final LockState pLockState,
68 | final Object pLockId,
69 | final boolean pTimedOut)
70 | {
71 | final Thread currentThread = Thread.currentThread();
72 |
73 | long serverTime = pServerTime;
74 |
75 | if (serverTime == 0) serverTime = getServerTime(pMongo, pSvcOptions);
76 |
77 | final Date now = new Date(serverTime);
78 |
79 | final BasicDBObject lockDoc = new BasicDBObject(LockHistoryDef.LOCK_NAME.field, pLockName);
80 | lockDoc.put(LockHistoryDef.LIBRARY_VERSION.field, pSvcOptions.getLibVersion());
81 | lockDoc.put(LockHistoryDef.CREATED.field, now);
82 | lockDoc.put(LockHistoryDef.LOCK_ID.field, pLockId);
83 | lockDoc.put(LockHistoryDef.STATE.field, pLockState.code());
84 | lockDoc.put(LockHistoryDef.OWNER_APP_NAME.field, pSvcOptions.getAppName());
85 | lockDoc.put(LockHistoryDef.OWNER_ADDRESS.field, pSvcOptions.getHostAddress());
86 | lockDoc.put(LockHistoryDef.OWNER_HOSTNAME.field, pSvcOptions.getHostname());
87 | lockDoc.put(LockHistoryDef.OWNER_THREAD_ID.field, currentThread.getId());
88 | lockDoc.put(LockHistoryDef.OWNER_THREAD_NAME.field, currentThread.getName());
89 | lockDoc.put(LockHistoryDef.OWNER_THREAD_GROUP_NAME.field, currentThread.getThreadGroup().getName());
90 |
91 | lockDoc.put(LockHistoryDef.INACTIVE_LOCK_TIMEOUT.field, pInactiveLockTimeout);
92 |
93 | lockDoc.put(LockHistoryDef.TIMED_OUT.field, pTimedOut);
94 |
95 | getDbCollection(pMongo, pSvcOptions).insert(lockDoc, WriteConcern.SAFE);
96 | }
97 |
98 | /**
99 | * Returns the history collection.
100 | */
101 | private static DBCollection getDbCollection(final Mongo pMongo,
102 | final DistributedLockSvcOptions pSvcOptions)
103 | { return getDb(pMongo, pSvcOptions).getCollection(pSvcOptions.getHistoryCollectionName()); }
104 |
105 | /**
106 | * Ensure the proper indexes are on the collection. This must be called when
107 | * the service sarts.
108 | */
109 | static void setup(final Mongo pMongo, final DistributedLockSvcOptions pSvcOptions) {
110 |
111 | }
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/LockHistoryDef.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | /**
20 | * The distributed lock history fields. See LockDef for more info.
21 | *
22 | * The fields in LockDef need to be represented here.
23 | */
24 | enum LockHistoryDef {
25 |
26 | ID("_id"), // This is an object id in history
27 | LIBRARY_VERSION("libraryVersion"),
28 | STATE("lockState"),
29 | LOCK_ID("lockId"),
30 | OWNER_APP_NAME("appName"),
31 | OWNER_ADDRESS("ownerAddress"),
32 | OWNER_HOSTNAME("ownerHostname"),
33 | OWNER_THREAD_ID("ownerThreadId"),
34 | OWNER_THREAD_NAME("ownerThreadName"),
35 | OWNER_THREAD_GROUP_NAME("ownerThreadGroupName"),
36 | INACTIVE_LOCK_TIMEOUT("inactiveLockTimeout"),
37 |
38 |
39 | // Fields on top of LockDef
40 | CREATED("historyCreated"),
41 | LOCK_NAME("lockName"),
42 | TIMED_OUT("timedOut");
43 |
44 | private LockHistoryDef(final String pField) { field = pField; }
45 | final String field;
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/LockImpl.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | // Lib
20 | import com.deftlabs.lock.mongo.DistributedLock;
21 | import com.deftlabs.lock.mongo.DistributedLockOptions;
22 | import com.deftlabs.lock.mongo.DistributedLockSvcOptions;
23 |
24 | // Mongo
25 | import com.mongodb.Mongo;
26 | import org.bson.types.ObjectId;
27 |
28 | // Java
29 | import java.util.Queue;
30 | import java.util.concurrent.TimeUnit;
31 | import java.util.concurrent.locks.Lock;
32 | import java.util.concurrent.locks.Condition;
33 | import java.util.concurrent.atomic.AtomicBoolean;
34 | import java.util.concurrent.ConcurrentLinkedQueue;
35 | import java.util.concurrent.locks.LockSupport;
36 |
37 | /**
38 | * The distributed lock object.
39 | */
40 | public class LockImpl implements DistributedLock {
41 |
42 | /**
43 | * Construct the object with params.
44 | */
45 | LockImpl( final Mongo pMongo,
46 | final String pName,
47 | final DistributedLockOptions pLockOptions,
48 | final DistributedLockSvcOptions pSvcOptions)
49 | {
50 | _mongo = pMongo;
51 | _name = pName;
52 | _lockOptions = pLockOptions;
53 | _svcOptions = pSvcOptions;
54 | }
55 |
56 | @Override public void lock() {
57 | if (tryDistributedLock()) return;
58 | park();
59 | }
60 |
61 | /**
62 | * Park the current thread. This method will check to see (when allowed) to see
63 | * if it can get the distributed lock.
64 | */
65 | private void park() {
66 |
67 | boolean wasInterrupted = false;
68 | final Thread current = Thread.currentThread();
69 | _waitingThreads.add(current);
70 |
71 | // Block while not first in queue or cannot acquire lock
72 | while (_running.get()) {
73 |
74 | LockSupport.park(this);
75 |
76 | if (Thread.interrupted()) { wasInterrupted = true; break; }
77 |
78 | if (_waitingThreads.peek() == current && !isLocked()) {
79 | // Check to see if this thread can get the distributed lock
80 | if (tryDistributedLock()) break;
81 | }
82 | }
83 |
84 | if (wasInterrupted) current.interrupt();
85 | else _waitingThreads.remove();
86 | }
87 |
88 | /**
89 | * Park the current thread for a max amount of time. This method will check to see
90 | * (when allowed) to see if it can get the distributed lock.
91 | */
92 | private boolean park(final long pNanos) {
93 |
94 | boolean wasInterrupted = false;
95 | final Thread current = Thread.currentThread();
96 | _waitingThreads.add(current);
97 |
98 | boolean locked = false;
99 |
100 | long parkTime = pNanos;
101 |
102 | long startTime = System.nanoTime();
103 |
104 | // Block while not first in queue or cannot acquire lock
105 | while (_running.get()) {
106 |
107 | parkTime = (pNanos - (System.nanoTime() - startTime));
108 |
109 | if (parkTime <= 0) break;
110 |
111 | LockSupport.parkNanos(this, parkTime);
112 |
113 | if (Thread.interrupted()) { wasInterrupted = true; break; }
114 |
115 | if (_waitingThreads.peek() == current && !isLocked()) {
116 | // Check to see if this thread can get the distributed lock
117 | if (tryDistributedLock()) { locked = true; break; }
118 | }
119 |
120 | if ((System.nanoTime() - startTime) >= pNanos) break;
121 | }
122 |
123 | // TODO: There is a problem here... we need to be able to remove
124 | // the actual thread, not just the head. This is causing an issue
125 | // where we are removing other threads.
126 |
127 |
128 | if (wasInterrupted) { current.interrupt(); return locked; }
129 | else _waitingThreads.remove();
130 |
131 | return locked;
132 | }
133 |
134 | /**
135 | * Try and lock the distributed lock.
136 | */
137 | private boolean tryDistributedLock() {
138 | if (isLocked()) return false;
139 |
140 | final ObjectId lockId = LockDao.lock(_mongo, _name, _svcOptions, _lockOptions);
141 |
142 | if (lockId == null) return false;
143 |
144 | _locked.set(true);
145 | _lockId = lockId;
146 | return true;
147 | }
148 |
149 | /**
150 | * This is not supported.
151 | */
152 | @Override public void lockInterruptibly()
153 | { throw new UnsupportedOperationException("not implemented"); }
154 |
155 | /**
156 | * For now, this is not supported.
157 | */
158 | @Override public Condition newCondition()
159 | { throw new UnsupportedOperationException("not implemented"); }
160 |
161 | /**
162 | * Does not block. Returns right away if not able to lock.
163 | */
164 | @Override public boolean tryLock() {
165 | if (isLocked()) return false;
166 |
167 | final ObjectId lockId = LockDao.lock(_mongo, _name, _svcOptions, _lockOptions);
168 |
169 | if (lockId == null) return false;
170 |
171 | _locked.set(true);
172 | _lockId = lockId;
173 | return true;
174 | }
175 |
176 | @Override public boolean tryLock(final long pTime, final TimeUnit pTimeUnit) {
177 | if (tryDistributedLock()) return true;
178 | return park(pTimeUnit.toNanos(pTime));
179 | }
180 |
181 | @Override public void unlock() {
182 | LockDao.unlock(_mongo, _name, _svcOptions, _lockOptions, _lockId);
183 | _locked.set(false);
184 | _lockId = null;
185 | LockSupport.unpark(_waitingThreads.peek());
186 | }
187 |
188 | /**
189 | * Called to initialize the lock.
190 | */
191 | synchronized void init() {
192 | if (_running.get()) throw new IllegalStateException("init already called");
193 | _running.set(true);
194 | }
195 |
196 | /**
197 | * Called to destroy the lock.
198 | */
199 | synchronized void destroy() {
200 | if (!_running.get()) throw new IllegalStateException("destroy already called");
201 | _running.set(false);
202 | for (final Thread t : _waitingThreads) t.interrupt();
203 | _waitingThreads.clear();
204 | }
205 |
206 | /**
207 | * Returns true if the lock is currently locked.
208 | */
209 | @Override public boolean isLocked() { return _locked.get(); }
210 |
211 | /**
212 | * Returns the lock name.
213 | */
214 | @Override public String getName() { return _name; }
215 |
216 | @Override public ObjectId getLockId() { return _lockId; }
217 |
218 | /**
219 | * Returns the options used to configure this lock.
220 | */
221 | @Override public DistributedLockOptions getOptions() { return _lockOptions; }
222 |
223 | /**
224 | * Wakeup any blocked threads. This should ONLY be used by the lock service,
225 | * not the user.
226 | */
227 | @Override public void wakeupBlocked() { LockSupport.unpark(_waitingThreads.peek()); }
228 |
229 | private final String _name;
230 | private final Mongo _mongo;
231 | private final DistributedLockOptions _lockOptions;
232 | private final DistributedLockSvcOptions _svcOptions;
233 |
234 | private volatile ObjectId _lockId;
235 | private final AtomicBoolean _locked = new AtomicBoolean(false);
236 | private final AtomicBoolean _running = new AtomicBoolean(false);
237 | private final Queue _waitingThreads = new ConcurrentLinkedQueue();
238 | }
239 |
240 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/LockState.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | /**
20 | * The lock states.
21 | */
22 | enum LockState {
23 |
24 | LOCKED("locked"),
25 | UNLOCKED("unlocked");
26 |
27 | private LockState(final String pCode) { _code = pCode; }
28 | private final String _code;
29 |
30 | public String code() { return _code; }
31 |
32 | public boolean isLocked() { return this == LOCKED; }
33 | public boolean isUnlocked() { return this == UNLOCKED; }
34 |
35 | public static LockState findByCode(final String pCode) {
36 | if (pCode == null) throw new IllegalArgumentException("Invalid lock state code: " + pCode);
37 | for (final LockState s : values()) if (s._code.equals(pCode)) return s;
38 | throw new IllegalArgumentException("Invalid lock state code: " + pCode);
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/Monitor.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | // Lib
20 | import com.deftlabs.lock.mongo.DistributedLock;
21 | import com.deftlabs.lock.mongo.DistributedLockSvcOptions;
22 |
23 | // Mongo
24 | import com.mongodb.Mongo;
25 | import org.bson.types.ObjectId;
26 |
27 | // Java
28 | import java.util.Map;
29 | import java.util.concurrent.CountDownLatch;
30 | import java.util.concurrent.TimeUnit;
31 | import java.util.logging.Level;
32 | import java.util.logging.Logger;
33 |
34 | /**
35 | * The lock monitors.
36 | */
37 | final class Monitor {
38 |
39 | /**
40 | * The lock heartbeat thread is responsible for sending updates to the lock
41 | * doc every X seconds (when the lock is owned by the current process). This
42 | * library uses missing/stale/old heartbeats to timeout locks that have not been
43 | * closed properly (based on the lock/unlock) contract. This can happen when processes
44 | * die unexpectedly (e.g., out of memory) or when they are not stopped properly (e.g., kill -9).
45 | */
46 | static class LockHeartbeat extends MonitorThread {
47 |
48 | LockHeartbeat(final Mongo pMongo,
49 | final DistributedLockSvcOptions pSvcOptions,
50 | final Map pLocks) {
51 | super("Mongo-Distributed-Lock-LockHeartbeat-" + System.currentTimeMillis(),
52 | pMongo, pSvcOptions, pLocks);
53 | }
54 |
55 | @Override
56 | boolean monitor() throws InterruptedException {
57 | for (final String lockName : _locks.keySet()) {
58 | final DistributedLock lock = _locks.get(lockName);
59 |
60 | final ObjectId lockId = lock.getLockId();
61 |
62 | if (!lock.isLocked() || lockId == null) continue;
63 |
64 | LockDao.heartbeat(_mongo, lockName, lockId, lock.getOptions(), _svcOptions);
65 | }
66 |
67 | return _shutdown.await(_svcOptions.getHeartbeatFrequency(), TimeUnit.MILLISECONDS);
68 | }
69 | }
70 |
71 | /**
72 | * The lock timeout thread impl (see LockHeartbeat docs for more info). One lock
73 | * timeout thread runs in each process this lock lib is running. This thread is
74 | * responsible for cleaning up expired locks (based on time since last heartbeat).
75 | */
76 | static class LockTimeout extends MonitorThread {
77 |
78 | LockTimeout(final Mongo pMongo, final DistributedLockSvcOptions pSvcOptions) {
79 | super("Mongo-Distributed-Lock-LockTimeout-" + System.currentTimeMillis(), pMongo, pSvcOptions);
80 | }
81 |
82 | @Override
83 | boolean monitor() throws InterruptedException {
84 | LockDao.expireInactiveLocks(_mongo, _svcOptions);
85 | return _shutdown.await(_svcOptions.getTimeoutFrequency(), TimeUnit.MILLISECONDS);
86 | }
87 | }
88 |
89 | /**
90 | * The lock unlocked thread is responsible for waking up local
91 | * threads when a lock state changes.
92 | */
93 | static class LockUnlocked extends MonitorThread {
94 |
95 | LockUnlocked(final Mongo pMongo,
96 | final DistributedLockSvcOptions pSvcOptions,
97 | final Map pLocks) {
98 | super("Mongo-Distributed-Lock-LockUnlocked-" + System.currentTimeMillis(),
99 | pMongo, pSvcOptions, pLocks);
100 | }
101 |
102 | @Override
103 | boolean monitor() throws InterruptedException {
104 | for (final String lockName : _locks.keySet()) {
105 | final DistributedLock lock = _locks.get(lockName);
106 |
107 | if (lock.isLocked()) continue;
108 |
109 | // Check to see if this is locked.
110 | if (LockDao.isLocked(_mongo, lockName, _svcOptions)) continue;
111 |
112 | // The lock is not locked, wakeup any blocking threads.
113 | lock.wakeupBlocked();
114 | }
115 |
116 | return _shutdown.await(_svcOptions.getLockUnlockedFrequency(), TimeUnit.MILLISECONDS);
117 | }
118 | }
119 |
120 |
121 | private static abstract class MonitorThread extends Thread {
122 |
123 | MonitorThread(final String pName,
124 | final Mongo pMongo,
125 | final DistributedLockSvcOptions pSvcOptions) {
126 | this(pName, pMongo, pSvcOptions, null);
127 | }
128 |
129 | MonitorThread(final String pName,
130 | final Mongo pMongo,
131 | final DistributedLockSvcOptions pSvcOptions,
132 | final Map pLocks) {
133 | super(pName);
134 | _mongo = pMongo;
135 | _svcOptions = pSvcOptions;
136 | _locks = pLocks;
137 | _shutdown = new CountDownLatch(1);
138 | _exited = new CountDownLatch(1);
139 | setDaemon(true);
140 | }
141 |
142 | @Override public void run() {
143 | boolean shutdown = false;
144 | try {
145 | while (!shutdown) {
146 | try { shutdown = monitor();
147 | } catch (final InterruptedException ie) { break;
148 | } catch (final Throwable t) { LOG.log(Level.SEVERE, t.getMessage(), t); }
149 | }
150 | } finally {
151 | _exited.countDown();
152 | }
153 | }
154 |
155 | /**
156 | * Performs check and awaits shutdown signal for configured amount of milliseconds
157 | * @return true if shutdown() was called, false otherwise.
158 | */
159 | abstract boolean monitor() throws InterruptedException;
160 |
161 | void shutdown() throws InterruptedException {
162 | _shutdown.countDown();
163 | if (!_exited.await(10000, TimeUnit.MILLISECONDS)) {
164 | this.interrupt();
165 | }
166 | }
167 |
168 | final Mongo _mongo;
169 | final DistributedLockSvcOptions _svcOptions;
170 | final Map _locks;
171 | final CountDownLatch _shutdown;
172 | final CountDownLatch _exited;
173 | }
174 |
175 | private static final Logger LOG = Logger.getLogger("com.deftlabs.lock.mongo.Monitor");
176 | }
177 |
178 |
--------------------------------------------------------------------------------
/src/main/com/deftlabs/lock/mongo/impl/SvcImpl.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo.impl;
18 |
19 | // Lib
20 | import com.deftlabs.lock.mongo.DistributedLock;
21 | import com.deftlabs.lock.mongo.DistributedLockSvc;
22 | import com.deftlabs.lock.mongo.DistributedLockOptions;
23 | import com.deftlabs.lock.mongo.DistributedLockSvcOptions;
24 | import com.deftlabs.lock.mongo.DistributedLockException;
25 |
26 | // Mongo
27 | import com.mongodb.Mongo;
28 | import com.mongodb.MongoURI;
29 |
30 | // Java
31 | import java.util.Map;
32 | import java.util.concurrent.ConcurrentHashMap;
33 | import java.util.concurrent.locks.ReentrantLock;
34 | import java.util.concurrent.atomic.AtomicBoolean;
35 |
36 | /**
37 | * The distributed lock server implementation.
38 | */
39 | public final class SvcImpl implements DistributedLockSvc {
40 |
41 | public SvcImpl(final DistributedLockSvcOptions pOptions) {
42 | _options = pOptions;
43 | }
44 |
45 | /**
46 | * Returns a new lock. If the name is already in use and the lock has been
47 | * created, it returns the existing lock.
48 | */
49 | @Override
50 | public DistributedLock create( final String pLockName,
51 | final DistributedLockOptions pLockOptions) {
52 | if ( !isRunning() ) throw new IllegalStateException("cannot create lock when service not running");
53 |
54 | _lock.lock();
55 | try {
56 | if (_locks.containsKey(pLockName)) return _locks.get(pLockName);
57 |
58 | final LockImpl lock = new LockImpl(_mongo, pLockName, pLockOptions, _options);
59 |
60 | lock.init();
61 |
62 | _locks.put(pLockName, lock);
63 |
64 | return lock;
65 |
66 | } finally { _lock.unlock(); }
67 | }
68 |
69 | /**
70 | * Returns a new lock. If the name is already in use and the lock has been
71 | * created, it returns the existing lock.
72 | */
73 | @Override
74 | public DistributedLock create(final String pLockName) {
75 | return create(pLockName, new DistributedLockOptions());
76 | }
77 |
78 | @Override
79 | public void destroy(final DistributedLock pLock) {
80 | _lock.lock();
81 | try {
82 | if (!_locks.containsKey(pLock.getName()))
83 | { throw new DistributedLockException("Lock has already been destroyed: " + pLock.getName()); }
84 |
85 | // Make sure the lock isn't locked.
86 | if (pLock.isLocked())
87 | { throw new IllegalStateException("Lock is currently in use - must unlock before destroying"); }
88 |
89 | try { ((LockImpl)pLock).destroy(); } finally { _locks.remove(pLock.getName()); }
90 |
91 | } finally { _lock.unlock(); }
92 | }
93 |
94 | /**
95 | * Initialize the service.
96 | */
97 | @Override
98 | public void startup() {
99 | if (!_running.compareAndSet(false, true)) throw new IllegalStateException("startup called but already running");
100 |
101 | _lock.lock();
102 | try {
103 | _mongo = new Mongo(new MongoURI(_options.getMongoUri()));
104 |
105 | // Init the db/collection.
106 | LockDao.setup(_mongo, _options);
107 | if (_options.getEnableHistory()) LockHistoryDao.setup(_mongo, _options);
108 |
109 | // Init the monitor threads.
110 | _lockHeartbeat = new Monitor.LockHeartbeat(_mongo, _options, _locks);
111 | _lockHeartbeat.start();
112 |
113 | _lockTimeout = new Monitor.LockTimeout(_mongo, _options);
114 | _lockTimeout.start();
115 |
116 | _lockUnlocked = new Monitor.LockUnlocked(_mongo, _options, _locks);
117 | _lockUnlocked.start();
118 |
119 |
120 | } catch (final Throwable t) { throw new DistributedLockException(t);
121 | } finally { _lock.unlock(); }
122 | }
123 |
124 | /**
125 | * Initialize the service.
126 | */
127 | @Override
128 | public void shutdown() {
129 | if (!_running.compareAndSet(true, false)) throw new IllegalStateException("shutdown called but not running");
130 |
131 | _lock.lock();
132 | try {
133 | // Interrupt the locks.
134 | for (final String lockName : _locks.keySet()) {
135 | final DistributedLock lock = _locks.get(lockName);
136 | if (lock == null) continue;
137 | ((LockImpl)lock).destroy();
138 | }
139 |
140 | _lockTimeout.shutdown();
141 | _lockHeartbeat.shutdown();
142 | _lockUnlocked.shutdown();
143 |
144 | _locks.clear();
145 | _mongo.close();
146 | } catch (final Throwable t) { throw new DistributedLockException(t);
147 | } finally { _lock.unlock(); }
148 | }
149 |
150 | @Override
151 | public boolean isRunning() { return _running.get(); }
152 |
153 | private Mongo _mongo;
154 |
155 | private final ReentrantLock _lock = new ReentrantLock(true);
156 | private final DistributedLockSvcOptions _options;
157 |
158 | private final AtomicBoolean _running = new AtomicBoolean(false);
159 |
160 | private Monitor.LockHeartbeat _lockHeartbeat;
161 | private Monitor.LockTimeout _lockTimeout;
162 | private Monitor.LockUnlocked _lockUnlocked;
163 |
164 | private final Map _locks = new ConcurrentHashMap();
165 |
166 | }
167 |
168 |
--------------------------------------------------------------------------------
/src/test/com/deftlabs/lock/mongo/DistributedLockIntTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | // Mongo
20 | import com.mongodb.Mongo;
21 | import com.mongodb.MongoURI;
22 | import com.mongodb.BasicDBObject;
23 | import com.mongodb.DBCollection;
24 |
25 | // JUnit
26 | import org.junit.Test;
27 | import org.junit.Before;
28 | import org.junit.After;
29 | import static org.junit.Assert.*;
30 |
31 | // Java
32 | import java.util.concurrent.TimeUnit;
33 |
34 | /**
35 | * Test the distributed lock. You must be running mongo on localhost:27017 for this
36 | * test to work.
37 | */
38 | public final class DistributedLockIntTests {
39 |
40 | @Test
41 | public void testSimpleCreate() throws Exception {
42 | assertNotNull(createSimpleLockSvc());
43 | }
44 |
45 | @Test
46 | public void testSimpleLockCreateDestroy() throws Exception {
47 | final DistributedLockSvc lockSvc = createSimpleLockSvc();
48 | try {
49 | DistributedLock lock = null;
50 | try { lock = lockSvc.create("testLock");
51 | } finally { if (lock != null) lockSvc.destroy(lock); }
52 | } finally { lockSvc.shutdown(); }
53 | }
54 |
55 | @Test
56 | public void testSimpleLockCreateLockUnlockDestroy() throws Exception {
57 | final DistributedLockSvc lockSvc = createSimpleLockSvc();
58 | try {
59 | DistributedLock lock = null;
60 | try {
61 | lock = lockSvc.create("testLock");
62 | try { lock.lock();
63 | } finally { lock.unlock(); }
64 |
65 |
66 |
67 | } finally { if (lock != null) lockSvc.destroy(lock); }
68 | } finally { lockSvc.shutdown(); }
69 | assertEquals(2, getHistoryCollection().count());
70 | }
71 |
72 | @Test
73 | public void testSimpleLockWithNestedTryLock() throws Exception {
74 | final DistributedLockSvc lockSvc = createSimpleLockSvc();
75 | try {
76 | DistributedLock lock = null;
77 | try {
78 | lock = lockSvc.create("testLock");
79 | try {
80 | lock.lock();
81 | assertEquals(false, lock.tryLock());
82 | } finally { lock.unlock(); }
83 | } finally { if (lock != null) lockSvc.destroy(lock); }
84 | } finally { lockSvc.shutdown(); }
85 | assertEquals(2, getHistoryCollection().count());
86 | }
87 |
88 | @Test
89 | public void testSimpleTryLock() throws Exception {
90 | final DistributedLockSvc lockSvc = createSimpleLockSvc();
91 | try {
92 | DistributedLock lock = null;
93 | try {
94 | lock = lockSvc.create("testLock");
95 | try { lock.tryLock();
96 | } finally { lock.unlock(); }
97 | } finally { if (lock != null) lockSvc.destroy(lock); }
98 | } finally { lockSvc.shutdown(); }
99 | assertEquals(2, getHistoryCollection().count());
100 | }
101 |
102 | @Test
103 | public void testSimpleTimedTryLock() throws Exception {
104 | final DistributedLockSvc lockSvc = createSimpleLockSvc();
105 | try {
106 | DistributedLock lock = null;
107 | try {
108 | lock = lockSvc.create("testLock");
109 | try { assertEquals(true, lock.tryLock(0, TimeUnit.SECONDS));
110 | } finally { lock.unlock(); }
111 | } finally { if (lock != null) lockSvc.destroy(lock); }
112 | } finally { lockSvc.shutdown(); }
113 |
114 | assertEquals(2, getHistoryCollection().count());
115 | }
116 |
117 | @Test
118 | public void testSimpleTimedTryLock2() throws Exception {
119 | final DistributedLockSvc lockSvc = createSimpleLockSvc();
120 | try {
121 | DistributedLock lock = null;
122 | try {
123 | lock = lockSvc.create("testLock");
124 | try {
125 | lock.lock();
126 | assertEquals(false, lock.tryLock(0, TimeUnit.SECONDS));
127 | } finally { lock.unlock(); }
128 | } finally { if (lock != null) lockSvc.destroy(lock); }
129 | } finally { lockSvc.shutdown(); }
130 |
131 | assertEquals(2, getHistoryCollection().count());
132 | }
133 |
134 | @Test
135 | public void testSimpleTimedTryLock3() throws Exception {
136 | final DistributedLockSvc lockSvc = createSimpleLockSvc();
137 | try {
138 | DistributedLock lock = null;
139 | try {
140 | lock = lockSvc.create("testLock");
141 | try {
142 | lock.lock();
143 |
144 | final long startTime = System.currentTimeMillis();
145 | assertEquals(false, lock.tryLock(1, TimeUnit.SECONDS));
146 |
147 | final long blockTime = System.currentTimeMillis() - startTime;
148 | final long diff = blockTime - 1000;
149 |
150 | assertEquals(true, (diff <= 5));
151 | } finally { lock.unlock(); }
152 | } finally { if (lock != null) lockSvc.destroy(lock); }
153 | } finally { lockSvc.shutdown(); }
154 |
155 | // In a over utilized env, it can take a second for the history entries to be persisted.
156 | Thread.sleep(1000);
157 |
158 | assertEquals(2, getHistoryCollection().count());
159 | }
160 |
161 | private DistributedLockSvc createSimpleLockSvc() {
162 | final DistributedLockSvcOptions options
163 | = new DistributedLockSvcOptions("mongodb://127.0.0.1:27017");
164 |
165 | final DistributedLockSvcFactory factory = new DistributedLockSvcFactory(options);
166 | return factory.getLockSvc();
167 | }
168 |
169 | @Before
170 | public void init() throws Exception {
171 | // Cleanup the test database
172 | getCollection().remove(new BasicDBObject());
173 | getHistoryCollection().remove(new BasicDBObject());
174 | }
175 |
176 | @After
177 | public void cleanup() {
178 | getCollection().remove(new BasicDBObject());
179 | }
180 |
181 | private DBCollection getCollection()
182 | { return _mongo.getDB("mongo-distributed-lock").getCollection("locks"); }
183 |
184 | private DBCollection getHistoryCollection()
185 | { return _mongo.getDB("mongo-distributed-lock").getCollection("lockHistory"); }
186 |
187 | public DistributedLockIntTests() throws Exception {
188 | _mongo = new Mongo(new MongoURI("mongodb://127.0.0.1:27017"));
189 | }
190 |
191 | private final Mongo _mongo;
192 |
193 | }
194 |
195 |
--------------------------------------------------------------------------------
/src/test/com/deftlabs/lock/mongo/DriverIntTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | // Mongo
20 | import com.mongodb.DB;
21 | import com.mongodb.Mongo;
22 | import com.mongodb.MongoURI;
23 | import com.mongodb.BasicDBObject;
24 | import com.mongodb.DBCollection;
25 | import com.mongodb.WriteConcern;
26 | import com.mongodb.WriteResult;
27 | import com.mongodb.CommandResult;
28 |
29 | // JUnit
30 | import org.junit.Test;
31 | import org.junit.Before;
32 | import org.junit.After;
33 | import static org.junit.Assert.*;
34 |
35 | // Java
36 | import java.util.concurrent.TimeUnit;
37 |
38 | /**
39 | * Test assumptions about the java driver for mongo.
40 | */
41 | public final class DriverIntTests {
42 |
43 | @Test public void testWriteConcerns() throws Exception {
44 | final BasicDBObject doc = new BasicDBObject("_id", "test");
45 |
46 | getDb().requestStart();
47 | getCollection().insert(doc, WriteConcern.NORMAL);
48 |
49 | final WriteResult result = getCollection().insert(doc, WriteConcern.NORMAL);
50 | final CommandResult cmdResult = result.getLastError(WriteConcern.NORMAL);
51 |
52 | assertTrue(cmdResult.getException() != null);
53 |
54 | //System.out.println(cmdResult);
55 |
56 | getDb().requestDone();
57 | }
58 |
59 | @Test public void testFindAndModify() throws Exception {
60 |
61 | final BasicDBObject query = new BasicDBObject("_id", "test");
62 |
63 | final BasicDBObject toSet = new BasicDBObject("locked", true);
64 |
65 | final BasicDBObject lockDoc
66 | = (BasicDBObject)getCollection().findAndModify(query, new BasicDBObject("_id", 1), null, false, new BasicDBObject("$set", toSet), false, false);
67 |
68 | assertNull(lockDoc);
69 | }
70 |
71 | @Test public void testFindAndModifyWithDoc() throws Exception {
72 | final BasicDBObject insert = new BasicDBObject("_id", "test");
73 | insert.put("locked", false);
74 | insert.put("lockId", "10");
75 | getCollection().insert(insert, WriteConcern.SAFE);
76 |
77 |
78 | final BasicDBObject query = new BasicDBObject("_id", "test");
79 | query.put("locked", false);
80 | query.put("lockId", "10");
81 |
82 | final BasicDBObject toSet = new BasicDBObject("locked", true);
83 | toSet.put("lockId", "20");
84 |
85 | final BasicDBObject lockDoc
86 | = (BasicDBObject)getCollection().findAndModify(query, new BasicDBObject("lockId", 1), null, false, new BasicDBObject("$set", toSet), true, false);
87 |
88 | assertNotNull(lockDoc);
89 |
90 | assertEquals("20", lockDoc.getString("lockId"));
91 | }
92 |
93 | @Test public void testFindAndModifyWithDocMiss() throws Exception {
94 | final BasicDBObject insert = new BasicDBObject("_id", "test");
95 | insert.put("locked", false);
96 | insert.put("lockId", "10");
97 | getCollection().insert(insert, WriteConcern.SAFE);
98 |
99 | final BasicDBObject query = new BasicDBObject("_id", "test");
100 | query.put("locked", false);
101 | query.put("lockId", "20");
102 |
103 | final BasicDBObject toSet = new BasicDBObject("locked", true);
104 | toSet.put("lockId", "20");
105 |
106 | final BasicDBObject lockDoc
107 | = (BasicDBObject)getCollection().findAndModify(query, new BasicDBObject("lockId", 1), null, false, new BasicDBObject("$set", toSet), true, false);
108 |
109 | assertNull(lockDoc);
110 |
111 | }
112 |
113 | @Before public void init() throws Exception { getDb().dropDatabase(); }
114 |
115 | @After public void cleanup() { getDb().dropDatabase(); }
116 |
117 | private DBCollection getCollection() { return getDb().getCollection("test"); }
118 |
119 | private DB getDb() { return _mongo.getDB("mongo-distributed-lock-test"); }
120 |
121 | public DriverIntTests() throws Exception {
122 | _mongo = new Mongo(new MongoURI("mongodb://127.0.0.1:27017"));
123 | }
124 |
125 | private final Mongo _mongo;
126 |
127 | }
128 |
129 |
--------------------------------------------------------------------------------
/src/test/com/deftlabs/lock/mongo/StandaloneThreadLockTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | // Mongo
20 | import com.mongodb.Mongo;
21 | import com.mongodb.MongoURI;
22 | import com.mongodb.BasicDBObject;
23 | import com.mongodb.DBCollection;
24 |
25 | // JUnit
26 | import org.junit.Test;
27 | import org.junit.Before;
28 | import org.junit.After;
29 | import static org.junit.Assert.*;
30 |
31 | // Java
32 | import java.util.concurrent.TimeUnit;
33 | import java.util.concurrent.CountDownLatch;
34 | import java.util.concurrent.atomic.AtomicBoolean;
35 |
36 |
37 | /**
38 | * The standalone threaded lock tests.
39 | */
40 | public final class StandaloneThreadLockTests {
41 |
42 | private void test() throws Exception {
43 | final CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
44 |
45 | final AtomicBoolean locked = new AtomicBoolean(false);
46 | final DistributedLockSvc lockSvc = createSimpleLockSvc();
47 |
48 | try {
49 |
50 | for (int idx=0; idx < THREAD_COUNT; idx++)
51 | { new Thread(new LockTest(countDownLatch, lockSvc, locked)).start(); }
52 |
53 | countDownLatch.await();
54 |
55 | } finally { lockSvc.shutdown(); }
56 | }
57 |
58 | private DistributedLockSvc createSimpleLockSvc() {
59 | final DistributedLockSvcOptions options
60 | = new DistributedLockSvcOptions("mongodb://127.0.0.1:27017");
61 |
62 | options.setEnableHistory(false);
63 |
64 | final DistributedLockSvcFactory factory = new DistributedLockSvcFactory(options);
65 | return factory.getLockSvc();
66 | }
67 |
68 | private static class LockTest implements Runnable {
69 |
70 | private LockTest( final CountDownLatch pCountDownLatch,
71 | final DistributedLockSvc pLockSvc,
72 | final AtomicBoolean pLocked)
73 | {
74 | _countDownLatch = pCountDownLatch;
75 | _lockSvc = pLockSvc;
76 | _locked = pLocked;
77 | }
78 |
79 | @Override public void run() {
80 |
81 | final DistributedLock lock = _lockSvc.create("com.deftlabs.lock.mongo.testLock");
82 |
83 | final long startTime = System.currentTimeMillis();
84 |
85 | for (int idx = 0; idx < LOCK_TEST_COUNT; idx++) {
86 |
87 | try {
88 | lock.lock();
89 |
90 | if (!_locked.compareAndSet(false, true)) throw new IllegalStateException("Already locked");
91 |
92 | } finally {
93 | if (!_locked.compareAndSet(true, false)) throw new IllegalStateException("Already unlocked");
94 | lock.unlock();
95 | }
96 | }
97 |
98 | final long execTime = System.currentTimeMillis() - startTime;
99 |
100 | _countDownLatch.countDown();
101 | }
102 |
103 | private final DistributedLockSvc _lockSvc;
104 | private final CountDownLatch _countDownLatch;
105 | private final AtomicBoolean _locked;
106 | }
107 |
108 | private DBCollection getCollection()
109 | { return _mongo.getDB("mongo-distributed-lock").getCollection("locks"); }
110 |
111 | private DBCollection getHistoryCollection()
112 | { return _mongo.getDB("mongo-distributed-lock").getCollection("lockHistory"); }
113 |
114 | private StandaloneThreadLockTests() throws Exception
115 | { _mongo = new Mongo(new MongoURI("mongodb://127.0.0.1:27017")); }
116 |
117 | private static final int THREAD_COUNT = 200;
118 |
119 | private static final int LOCK_TEST_COUNT = 50;
120 |
121 | public static void main(final String [] pArgs) throws Exception {
122 | final StandaloneThreadLockTests tests = new StandaloneThreadLockTests();
123 | tests.test();
124 |
125 | }
126 |
127 | private final Mongo _mongo;
128 |
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/src/unit/com/deftlabs/lock/mongo/DistributedLockUnitTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2011, Deft Labs.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.deftlabs.lock.mongo;
18 |
19 | // JUnit
20 | import org.junit.Test;
21 | import org.junit.Before;
22 | import org.junit.After;
23 | import org.junit.BeforeClass;
24 | import static org.junit.Assert.*;
25 |
26 | // Java
27 | import java.util.logging.Level;
28 | import java.util.logging.Logger;
29 | import java.util.logging.LogManager;
30 |
31 | /**
32 | * Test the distributed lock.
33 | */
34 | public final class DistributedLockUnitTests {
35 |
36 | @Test
37 | public void testPlaceholder() throws Exception {
38 |
39 | }
40 |
41 |
42 | private static final Logger LOG = Logger.getLogger(DistributedLockUnitTests.class.getName());
43 | }
44 |
45 |
--------------------------------------------------------------------------------