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 steps.push( Boolean.TRUE );
116 return true;
117 }
118 else
119 {
120
121 boolean weOwnExclusive = steps.contains( Boolean.FALSE );
122 if ( weOwnExclusive )
123 {
124 steps.push( Boolean.TRUE );
125 return true;
126 }
127 else
128 {
129
130 return null;
131 }
132 }
133 }
134
135 FileLock fileLock = obtainFileLock( true );
136 if ( fileLock != null )
137 {
138 fileLockRef.set( fileLock );
139 steps.push( Boolean.TRUE );
140 return true;
141 }
142 }
143 finally
144 {
145 criticalRegion.unlock();
146 }
147 }
148 return null;
149 }
150
151 private Boolean doLockExclusively()
152 {
153 if ( criticalRegion.tryLock() )
154 {
155 try
156 {
157 Deque<Boolean> steps = threadSteps.computeIfAbsent( Thread.currentThread(), k -> new ArrayDeque<>() );
158 FileLock obtainedLock = fileLockRef.get();
159 if ( obtainedLock != null )
160 {
161 if ( obtainedLock.isShared() )
162 {
163
164 boolean weOwnShared = steps.contains( Boolean.TRUE );
165 if ( weOwnShared )
166 {
167 return false;
168 }
169 else
170 {
171
172 return null;
173 }
174 }
175 else
176 {
177
178 boolean weOwnExclusive = steps.contains( Boolean.FALSE );
179 if ( weOwnExclusive )
180 {
181 steps.push( Boolean.FALSE );
182 return true;
183 }
184 else
185 {
186
187 return null;
188 }
189 }
190 }
191
192 FileLock fileLock = obtainFileLock( false );
193 if ( fileLock != null )
194 {
195 fileLockRef.set( fileLock );
196 steps.push( Boolean.FALSE );
197 return true;
198 }
199 }
200 finally
201 {
202 criticalRegion.unlock();
203 }
204 }
205 return null;
206 }
207
208 @Override
209 public void unlock()
210 {
211 criticalRegion.lock();
212 try
213 {
214 Deque<Boolean> steps = threadSteps.computeIfAbsent( Thread.currentThread(), k -> new ArrayDeque<>() );
215 if ( steps.isEmpty() )
216 {
217 throw new IllegalStateException( "Wrong API usage: unlock without lock" );
218 }
219 steps.pop();
220 if ( steps.isEmpty() && !anyOtherThreadHasSteps() )
221 {
222 try
223 {
224 fileLockRef.getAndSet( null ).release();
225 }
226 catch ( IOException e )
227 {
228 throw new UncheckedIOException( e );
229 }
230 }
231 }
232 finally
233 {
234 criticalRegion.unlock();
235 }
236 }
237
238
239
240
241 private boolean anyOtherThreadHasSteps()
242 {
243 return threadSteps.entrySet().stream()
244 .filter( e -> !Thread.currentThread().equals( e.getKey() ) )
245 .map( Map.Entry::getValue )
246 .anyMatch( d -> !d.isEmpty() );
247 }
248
249
250
251
252 private FileLock obtainFileLock( final boolean shared )
253 {
254 FileLock result;
255 try
256 {
257 result = fileChannel.tryLock( LOCK_POSITION, LOCK_SIZE, shared );
258 }
259 catch ( OverlappingFileLockException e )
260 {
261 logger.trace( "File lock overlap on '{}'", name(), e );
262 return null;
263 }
264 catch ( IOException e )
265 {
266 logger.trace( "Failure on acquire of file lock for '{}'", name(), e );
267 throw new UncheckedIOException( "Failed to acquire lock file channel for '" + name() + "'", e );
268 }
269 return result;
270 }
271 }