blob: 629c50ca5336c0ad307db2e7ac0e1de7606dc04d [file] [log] [blame]
Madan Jampani12390c12014-11-12 00:35:56 -08001package org.onlab.onos.store.service.impl;
2
Madan Jampani1ee91782014-11-20 20:24:24 -08003import static com.google.common.base.Verify.verify;
Madan Jampania88d1f52014-11-14 16:45:24 -08004import static org.slf4j.LoggerFactory.getLogger;
5
Ray Milkey241b96a2014-11-17 13:08:20 -08006import java.nio.charset.StandardCharsets;
Madan Jampani1ee91782014-11-20 20:24:24 -08007import java.util.Arrays;
Madan Jampani12390c12014-11-12 00:35:56 -08008import java.util.UUID;
9import java.util.concurrent.CompletableFuture;
10import java.util.concurrent.ExecutionException;
11import java.util.concurrent.TimeUnit;
12import java.util.concurrent.TimeoutException;
13import java.util.concurrent.atomic.AtomicBoolean;
14
15import org.joda.time.DateTime;
16import org.onlab.onos.cluster.ClusterService;
Madan Jampani71582ed2014-11-18 10:06:01 -080017import org.onlab.onos.store.service.DatabaseException;
Madan Jampani12390c12014-11-12 00:35:56 -080018import org.onlab.onos.store.service.DatabaseService;
19import org.onlab.onos.store.service.Lock;
Madan Jampani1ee91782014-11-20 20:24:24 -080020import org.onlab.onos.store.service.VersionedValue;
Madan Jampania88d1f52014-11-14 16:45:24 -080021import org.slf4j.Logger;
Madan Jampani12390c12014-11-12 00:35:56 -080022
23/**
24 * A distributed lock implementation.
25 */
26public class DistributedLock implements Lock {
27
Madan Jampania88d1f52014-11-14 16:45:24 -080028 private final Logger log = getLogger(getClass());
29
Madan Jampani12390c12014-11-12 00:35:56 -080030 private final DistributedLockManager lockManager;
31 private final DatabaseService databaseService;
32 private final String path;
33 private DateTime lockExpirationTime;
34 private AtomicBoolean isLocked = new AtomicBoolean(false);
Madan Jampani1ee91782014-11-20 20:24:24 -080035 private volatile long epoch = 0;
Madan Jampani12390c12014-11-12 00:35:56 -080036 private byte[] lockId;
37
38 public DistributedLock(
39 String path,
40 DatabaseService databaseService,
41 ClusterService clusterService,
42 DistributedLockManager lockManager) {
43
44 this.path = path;
45 this.databaseService = databaseService;
46 this.lockManager = lockManager;
47 this.lockId =
Ray Milkey241b96a2014-11-17 13:08:20 -080048 (UUID.randomUUID().toString() + "::" +
49 clusterService.getLocalNode().id().toString()).
50 getBytes(StandardCharsets.UTF_8);
Madan Jampani12390c12014-11-12 00:35:56 -080051 }
52
53 @Override
54 public String path() {
55 return path;
56 }
57
58 @Override
Madan Jampani71582ed2014-11-18 10:06:01 -080059 public void lock(int leaseDurationMillis) throws InterruptedException {
Madan Jampani1769a1a2014-11-19 21:51:44 -080060 try {
61 lockAsync(leaseDurationMillis).get();
62 } catch (ExecutionException e) {
63 throw new DatabaseException(e);
Madan Jampani12390c12014-11-12 00:35:56 -080064 }
65 }
66
67 @Override
Madan Jampani1d3494e2014-11-20 11:24:22 -080068 public CompletableFuture<Void> lockAsync(int leaseDurationMillis) {
Madan Jampaniddaffd02014-11-21 13:12:09 -080069 try {
70 if (isLocked() || tryLock(leaseDurationMillis)) {
71 return CompletableFuture.<Void>completedFuture(null);
72 }
73 return lockManager.lockIfAvailable(this, leaseDurationMillis);
74 } catch (DatabaseException e) {
75 CompletableFuture<Void> lockFuture = new CompletableFuture<>();
76 lockFuture.completeExceptionally(e);
77 return lockFuture;
Madan Jampani1769a1a2014-11-19 21:51:44 -080078 }
Madan Jampani1769a1a2014-11-19 21:51:44 -080079 }
80
81 @Override
Madan Jampani12390c12014-11-12 00:35:56 -080082 public boolean tryLock(int leaseDurationMillis) {
Madan Jampania88d1f52014-11-14 16:45:24 -080083 if (databaseService.putIfAbsent(
Madan Jampanic22123d2014-11-12 02:12:19 -080084 DistributedLockManager.ONOS_LOCK_TABLE_NAME,
85 path,
Madan Jampania88d1f52014-11-14 16:45:24 -080086 lockId)) {
Madan Jampani1ee91782014-11-20 20:24:24 -080087 VersionedValue vv =
88 databaseService.get(DistributedLockManager.ONOS_LOCK_TABLE_NAME, path);
89 verify(Arrays.equals(vv.value(), lockId));
90 epoch = vv.version();
Madan Jampania88d1f52014-11-14 16:45:24 -080091 isLocked.set(true);
92 lockExpirationTime = DateTime.now().plusMillis(leaseDurationMillis);
93 return true;
94 }
95 return false;
Madan Jampani12390c12014-11-12 00:35:56 -080096 }
97
98 @Override
99 public boolean tryLock(
Madan Jampani1769a1a2014-11-19 21:51:44 -0800100 int waitTimeMillis,
Madan Jampani71582ed2014-11-18 10:06:01 -0800101 int leaseDurationMillis) throws InterruptedException {
Madan Jampani1769a1a2014-11-19 21:51:44 -0800102 if (isLocked() || tryLock(leaseDurationMillis)) {
Madan Jampania88d1f52014-11-14 16:45:24 -0800103 return true;
Madan Jampani12390c12014-11-12 00:35:56 -0800104 }
Madan Jampani1769a1a2014-11-19 21:51:44 -0800105
106 CompletableFuture<Void> future =
Madan Jampania88d1f52014-11-14 16:45:24 -0800107 lockManager.lockIfAvailable(this, waitTimeMillis, leaseDurationMillis);
108 try {
Madan Jampani1769a1a2014-11-19 21:51:44 -0800109 future.get(waitTimeMillis, TimeUnit.MILLISECONDS);
Madan Jampania88d1f52014-11-14 16:45:24 -0800110 return true;
Madan Jampani71582ed2014-11-18 10:06:01 -0800111 } catch (ExecutionException e) {
112 throw new DatabaseException(e);
Madan Jampania88d1f52014-11-14 16:45:24 -0800113 } catch (TimeoutException e) {
114 log.debug("Timed out waiting to acquire lock for {}", path);
115 return false;
116 }
Madan Jampani12390c12014-11-12 00:35:56 -0800117 }
118
119 @Override
120 public boolean isLocked() {
121 if (isLocked.get()) {
122 // We rely on local information to check
Madan Jampania88d1f52014-11-14 16:45:24 -0800123 // if the lock expired.
Madan Jampani12390c12014-11-12 00:35:56 -0800124 // This should should make this call
Madan Jampania88d1f52014-11-14 16:45:24 -0800125 // light weight, while still retaining the
Madan Jampani12390c12014-11-12 00:35:56 -0800126 // safety guarantees.
127 if (DateTime.now().isAfter(lockExpirationTime)) {
128 isLocked.set(false);
129 return false;
Madan Jampanif5d263b2014-11-13 10:04:40 -0800130 } else {
131 return true;
Madan Jampani12390c12014-11-12 00:35:56 -0800132 }
133 }
Madan Jampanif5d263b2014-11-13 10:04:40 -0800134 return false;
Madan Jampani12390c12014-11-12 00:35:56 -0800135 }
136
137 @Override
Madan Jampani1ee91782014-11-20 20:24:24 -0800138 public long epoch() {
139 return epoch;
140 }
141
142 @Override
Madan Jampani12390c12014-11-12 00:35:56 -0800143 public void unlock() {
144 if (!isLocked()) {
145 return;
146 } else {
Madan Jampania88d1f52014-11-14 16:45:24 -0800147 if (databaseService.removeIfValueMatches(DistributedLockManager.ONOS_LOCK_TABLE_NAME, path, lockId)) {
148 isLocked.set(false);
149 }
Madan Jampani12390c12014-11-12 00:35:56 -0800150 }
151 }
152
153 @Override
154 public boolean extendExpiration(int leaseDurationMillis) {
Madan Jampania88d1f52014-11-14 16:45:24 -0800155 if (!isLocked()) {
156 log.warn("Ignoring request to extend expiration for lock {}."
157 + " ExtendExpiration must be called for locks that are already acquired.", path);
Pavlin Radoslavov20ded692014-11-17 16:03:15 -0800158 return false;
Madan Jampania88d1f52014-11-14 16:45:24 -0800159 }
160
161 if (databaseService.putIfValueMatches(
162 DistributedLockManager.ONOS_LOCK_TABLE_NAME,
163 path,
164 lockId,
165 lockId)) {
166 lockExpirationTime = DateTime.now().plusMillis(leaseDurationMillis);
167 log.debug("Succeeded in extending lock {} expiration time to {}", lockExpirationTime);
Madan Jampani12390c12014-11-12 00:35:56 -0800168 return true;
169 } else {
Madan Jampania88d1f52014-11-14 16:45:24 -0800170 log.info("Failed to extend expiration for {}", path);
171 return false;
Madan Jampani12390c12014-11-12 00:35:56 -0800172 }
173 }
Madan Jampania88d1f52014-11-14 16:45:24 -0800174}