blob: a3fbc0d86de60a9ac6e5cd531239bba53e82342e [file] [log] [blame]
Madan Jampani12390c12014-11-12 00:35:56 -08001package org.onlab.onos.store.service.impl;
2
Madan Jampania88d1f52014-11-14 16:45:24 -08003import static org.slf4j.LoggerFactory.getLogger;
4
Madan Jampani12390c12014-11-12 00:35:56 -08005import java.util.UUID;
6import java.util.concurrent.CompletableFuture;
7import java.util.concurrent.ExecutionException;
8import java.util.concurrent.TimeUnit;
9import java.util.concurrent.TimeoutException;
10import java.util.concurrent.atomic.AtomicBoolean;
11
12import org.joda.time.DateTime;
13import org.onlab.onos.cluster.ClusterService;
14import org.onlab.onos.store.service.DatabaseService;
15import org.onlab.onos.store.service.Lock;
Madan Jampania88d1f52014-11-14 16:45:24 -080016import org.slf4j.Logger;
Madan Jampani12390c12014-11-12 00:35:56 -080017
18/**
19 * A distributed lock implementation.
20 */
21public class DistributedLock implements Lock {
22
Madan Jampania88d1f52014-11-14 16:45:24 -080023 private final Logger log = getLogger(getClass());
24
25 private static final long MAX_WAIT_TIME_MS = 100000000L;
26
Madan Jampani12390c12014-11-12 00:35:56 -080027 private final DistributedLockManager lockManager;
28 private final DatabaseService databaseService;
29 private final String path;
30 private DateTime lockExpirationTime;
31 private AtomicBoolean isLocked = new AtomicBoolean(false);
32 private byte[] lockId;
33
34 public DistributedLock(
35 String path,
36 DatabaseService databaseService,
37 ClusterService clusterService,
38 DistributedLockManager lockManager) {
39
40 this.path = path;
41 this.databaseService = databaseService;
42 this.lockManager = lockManager;
43 this.lockId =
44 (UUID.randomUUID().toString() + "::" + clusterService.getLocalNode().id().toString()).getBytes();
45 }
46
47 @Override
48 public String path() {
49 return path;
50 }
51
52 @Override
53 public void lock(int leaseDurationMillis) {
Madan Jampani12390c12014-11-12 00:35:56 -080054 if (isLocked() && lockExpirationTime.isAfter(DateTime.now().plusMillis(leaseDurationMillis))) {
55 // Nothing to do.
56 // Current expiration time is beyond what is requested.
57 return;
58 } else {
Madan Jampania88d1f52014-11-14 16:45:24 -080059 tryLock(MAX_WAIT_TIME_MS, leaseDurationMillis);
Madan Jampani12390c12014-11-12 00:35:56 -080060 }
61 }
62
63 @Override
64 public boolean tryLock(int leaseDurationMillis) {
Madan Jampania88d1f52014-11-14 16:45:24 -080065 if (databaseService.putIfAbsent(
Madan Jampanic22123d2014-11-12 02:12:19 -080066 DistributedLockManager.ONOS_LOCK_TABLE_NAME,
67 path,
Madan Jampania88d1f52014-11-14 16:45:24 -080068 lockId)) {
69 isLocked.set(true);
70 lockExpirationTime = DateTime.now().plusMillis(leaseDurationMillis);
71 return true;
72 }
73 return false;
Madan Jampani12390c12014-11-12 00:35:56 -080074 }
75
76 @Override
77 public boolean tryLock(
78 long waitTimeMillis,
79 int leaseDurationMillis) {
Madan Jampania88d1f52014-11-14 16:45:24 -080080 if (tryLock(leaseDurationMillis)) {
81 return true;
Madan Jampani12390c12014-11-12 00:35:56 -080082 }
Madan Jampania88d1f52014-11-14 16:45:24 -080083
84 CompletableFuture<DateTime> future =
85 lockManager.lockIfAvailable(this, waitTimeMillis, leaseDurationMillis);
86 try {
87 lockExpirationTime = future.get(waitTimeMillis, TimeUnit.MILLISECONDS);
88 return true;
89 } catch (ExecutionException | InterruptedException e) {
90 log.error("Encountered an exception trying to acquire lock for " + path, e);
91 // TODO: ExecutionException could indicate something
92 // wrong with the backing database.
93 // Throw an exception?
94 return false;
95 } catch (TimeoutException e) {
96 log.debug("Timed out waiting to acquire lock for {}", path);
97 return false;
98 }
Madan Jampani12390c12014-11-12 00:35:56 -080099 }
100
101 @Override
102 public boolean isLocked() {
103 if (isLocked.get()) {
104 // We rely on local information to check
Madan Jampania88d1f52014-11-14 16:45:24 -0800105 // if the lock expired.
Madan Jampani12390c12014-11-12 00:35:56 -0800106 // This should should make this call
Madan Jampania88d1f52014-11-14 16:45:24 -0800107 // light weight, while still retaining the
Madan Jampani12390c12014-11-12 00:35:56 -0800108 // safety guarantees.
109 if (DateTime.now().isAfter(lockExpirationTime)) {
110 isLocked.set(false);
111 return false;
Madan Jampanif5d263b2014-11-13 10:04:40 -0800112 } else {
113 return true;
Madan Jampani12390c12014-11-12 00:35:56 -0800114 }
115 }
Madan Jampanif5d263b2014-11-13 10:04:40 -0800116 return false;
Madan Jampani12390c12014-11-12 00:35:56 -0800117 }
118
119 @Override
120 public void unlock() {
121 if (!isLocked()) {
122 return;
123 } else {
Madan Jampania88d1f52014-11-14 16:45:24 -0800124 if (databaseService.removeIfValueMatches(DistributedLockManager.ONOS_LOCK_TABLE_NAME, path, lockId)) {
125 isLocked.set(false);
126 }
Madan Jampani12390c12014-11-12 00:35:56 -0800127 }
128 }
129
130 @Override
131 public boolean extendExpiration(int leaseDurationMillis) {
Madan Jampania88d1f52014-11-14 16:45:24 -0800132 if (!isLocked()) {
133 log.warn("Ignoring request to extend expiration for lock {}."
134 + " ExtendExpiration must be called for locks that are already acquired.", path);
135 }
136
137 if (databaseService.putIfValueMatches(
138 DistributedLockManager.ONOS_LOCK_TABLE_NAME,
139 path,
140 lockId,
141 lockId)) {
142 lockExpirationTime = DateTime.now().plusMillis(leaseDurationMillis);
143 log.debug("Succeeded in extending lock {} expiration time to {}", lockExpirationTime);
Madan Jampani12390c12014-11-12 00:35:56 -0800144 return true;
145 } else {
Madan Jampania88d1f52014-11-14 16:45:24 -0800146 log.info("Failed to extend expiration for {}", path);
147 return false;
Madan Jampani12390c12014-11-12 00:35:56 -0800148 }
149 }
Madan Jampania88d1f52014-11-14 16:45:24 -0800150}