1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.named.support;
20
21 import java.io.IOException;
22 import java.io.UncheckedIOException;
23 import java.nio.channels.FileChannel;
24 import java.nio.channels.FileLock;
25 import java.nio.channels.OverlappingFileLockException;
26 import java.util.ArrayDeque;
27 import java.util.Deque;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.atomic.AtomicReference;
32 import java.util.concurrent.locks.ReentrantLock;
33
34 import org.eclipse.aether.named.NamedLockKey;
35
36 import static org.eclipse.aether.named.support.Retry.retry;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 public final class FileLockNamedLock extends NamedLockSupport {
52 private static final long RETRY_SLEEP_MILLIS = 100L;
53
54 private static final long LOCK_POSITION = 0L;
55
56 private static final long LOCK_SIZE = 1L;
57
58
59
60
61 private final Map<Thread, Deque<Boolean>> threadSteps;
62
63
64
65
66 private final FileChannel fileChannel;
67
68
69
70
71 private final AtomicReference<FileLock> fileLockRef;
72
73
74
75
76
77 private final ReentrantLock criticalRegion;
78
79 public FileLockNamedLock(
80 final NamedLockKey key, final FileChannel fileChannel, final NamedLockFactorySupport factory) {
81 super(key, factory);
82 this.threadSteps = new HashMap<>();
83 this.fileChannel = fileChannel;
84 this.fileLockRef = new AtomicReference<>(null);
85 this.criticalRegion = new ReentrantLock();
86 }
87
88 @Override
89 protected boolean doLockShared(final long time, final TimeUnit unit) throws InterruptedException {
90 return retry(time, unit, RETRY_SLEEP_MILLIS, () -> doLockSharedPerform(time, unit), null, false);
91 }
92
93 @Override
94 protected boolean doLockExclusively(final long time, final TimeUnit unit) throws InterruptedException {
95 return retry(time, unit, RETRY_SLEEP_MILLIS, () -> doLockExclusivelyPerform(time, unit), null, false);
96 }
97
98 private Boolean doLockSharedPerform(final long time, final TimeUnit unit) throws InterruptedException {
99 if (criticalRegion.tryLock(time, unit)) {
100 try {
101 Deque<Boolean> steps = threadSteps.computeIfAbsent(Thread.currentThread(), k -> new ArrayDeque<>());
102 FileLock obtainedLock = fileLockRef.get();
103 if (obtainedLock != null) {
104 if (obtainedLock.isShared()) {
105 steps.push(Boolean.TRUE);
106 return true;
107 } else {
108
109 boolean weOwnExclusive = steps.contains(Boolean.FALSE);
110 if (weOwnExclusive) {
111 steps.push(Boolean.TRUE);
112 return true;
113 } else {
114
115 return null;
116 }
117 }
118 }
119
120 FileLock fileLock = obtainFileLock(true);
121 if (fileLock != null) {
122 fileLockRef.set(fileLock);
123 steps.push(Boolean.TRUE);
124 return true;
125 }
126 } finally {
127 criticalRegion.unlock();
128 }
129 }
130 return null;
131 }
132
133 private Boolean doLockExclusivelyPerform(final long time, final TimeUnit unit) throws InterruptedException {
134 if (criticalRegion.tryLock(time, unit)) {
135 try {
136 Deque<Boolean> steps = threadSteps.computeIfAbsent(Thread.currentThread(), k -> new ArrayDeque<>());
137 FileLock obtainedLock = fileLockRef.get();
138 if (obtainedLock != null) {
139 if (obtainedLock.isShared()) {
140
141 boolean weOwnShared = steps.contains(Boolean.TRUE);
142 if (weOwnShared) {
143 throw new LockUpgradeNotSupportedException(this);
144 } else {
145
146 return null;
147 }
148 } else {
149
150 boolean weOwnExclusive = steps.contains(Boolean.FALSE);
151 if (weOwnExclusive) {
152 steps.push(Boolean.FALSE);
153 return true;
154 } else {
155
156 return null;
157 }
158 }
159 }
160
161 FileLock fileLock = obtainFileLock(false);
162 if (fileLock != null) {
163 fileLockRef.set(fileLock);
164 steps.push(Boolean.FALSE);
165 return true;
166 }
167 } finally {
168 criticalRegion.unlock();
169 }
170 }
171 return null;
172 }
173
174 @Override
175 protected void doUnlock() {
176 criticalRegion.lock();
177 try {
178 Deque<Boolean> steps = threadSteps.computeIfAbsent(Thread.currentThread(), k -> new ArrayDeque<>());
179 if (steps.isEmpty()) {
180 throw new IllegalStateException("Wrong API usage: unlock without lock");
181 }
182 steps.pop();
183 if (steps.isEmpty() && !anyOtherThreadHasSteps()) {
184 try {
185 fileLockRef.getAndSet(null).release();
186 } catch (IOException e) {
187 throw new UncheckedIOException(e);
188 }
189 }
190 } finally {
191 criticalRegion.unlock();
192 }
193 }
194
195
196
197
198 private boolean anyOtherThreadHasSteps() {
199 return threadSteps.entrySet().stream()
200 .filter(e -> !Thread.currentThread().equals(e.getKey()))
201 .map(Map.Entry::getValue)
202 .anyMatch(d -> !d.isEmpty());
203 }
204
205
206
207
208 private FileLock obtainFileLock(final boolean shared) {
209 FileLock result;
210 try {
211 result = fileChannel.tryLock(LOCK_POSITION, LOCK_SIZE, shared);
212 } catch (OverlappingFileLockException e) {
213 logger.trace("File lock overlap on '{}'", key(), e);
214 return null;
215 } catch (IOException e) {
216 logger.trace("Failure on acquire of file lock for '{}'", key(), e);
217 throw new UncheckedIOException("Failed to acquire lock file channel for '" + key() + "'", e);
218 }
219 return result;
220 }
221 }