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