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