2929package org .janelia .saalfeldlab .n5 ;
3030
3131import java .io .IOException ;
32- import java .lang .ref .PhantomReference ;
33- import java .lang .ref .Reference ;
3432import java .lang .ref .ReferenceQueue ;
35- import java .nio . channels . FileChannel ;
33+ import java .lang . ref . WeakReference ;
3634import java .nio .file .Path ;
37- import java .nio .file .StandardOpenOption ;
38- import java .util .Set ;
3935import java .util .concurrent .ConcurrentHashMap ;
4036
4137/**
@@ -46,70 +42,64 @@ class FileKeyLockManager {
4642
4743 static final FileKeyLockManager FILE_LOCK_MANAGER = new FileKeyLockManager ();
4844
49- private static final ReferenceQueue < LockedFileChannel > queue = new ReferenceQueue <>();
50- private static final Set < LockedChannelReference > phantomRefs = ConcurrentHashMap . newKeySet ();
51- private static final ConcurrentHashMap < String , KeyLockState > locks = new ConcurrentHashMap <>();
45+ private FileKeyLockManager () {
46+ // singleton
47+ }
5248
53- /**
54- * PhantomReference for LockedFileChannel that releases the lock if the
55- * channel is garbage collected without being closed.
56- */
57- private static class LockedChannelReference extends PhantomReference <LockedFileChannel > {
5849
59- private final String key ;
60- private final KeyLockState state ;
50+ private final ConcurrentHashMap <String , WeakValue > locks = new ConcurrentHashMap <>();
6151
62- LockedChannelReference (final LockedFileChannel referent , final String key , final KeyLockState state ) {
63- super (referent , queue );
64- this .key = key ;
65- this .state = state ;
66- }
52+ private final ReferenceQueue <KeyLockState > refQueue = new ReferenceQueue <>();
6753
68- void cleanup () {
69- state .releaseFileLockForCleanup ();
70- phantomRefs .remove (this );
71- /* always remove from map - if the LockedFileChannel was GC'd, the entry is stale */
72- locks .remove (key , state );
54+ private static class WeakValue extends WeakReference <KeyLockState > {
55+
56+ final String key ;
57+
58+ WeakValue (
59+ final String key ,
60+ final KeyLockState value ,
61+ final ReferenceQueue <KeyLockState > queue ) {
62+
63+ super (value , queue );
64+ this .key = key ;
7365 }
7466 }
7567
76- private void clearQueue () {
77- Reference <? extends LockedFileChannel > ref ;
78- while ((ref = queue .poll ()) != null ) {
79- ((LockedChannelReference )ref ).cleanup ();
68+ /**
69+ * Remove entries from the cache whose references have been
70+ * garbage-collected.
71+ */
72+ private void cleanUp ()
73+ {
74+ while (true ) {
75+ final WeakValue ref = (WeakValue ) refQueue .poll ();
76+ if (ref == null )
77+ break ;
78+ locks .remove (ref .key , ref );
8079 }
8180 }
8281
83- private class ManagedLockedFileChannel extends LockedFileChannel {
82+ private KeyLockState keyLockState ( final Path path ) {
8483
85- private final String key ;
86- private final KeyLockState state ;
84+ final String key = path .toAbsolutePath ().toString ();
8785
88- ManagedLockedFileChannel (final KeyLockState state , final FileChannel channel , final String key ) {
89- super (state , channel );
90- this .key = key ;
91- this .state = state ;
92- }
86+ cleanUp ();
9387
94- @ Override
95- public void close () throws IOException {
96- super .close ();
97- synchronized (state ) {
98- if (!state .isLocked ()) {
99- locks .remove (key , state );
100- }
101- }
102- clearQueue ();
88+ final WeakValue existingRef = locks .get (key );
89+ KeyLockState state = existingRef == null ? null : existingRef .get ();
90+ if (state != null ) {
91+ return state ;
10392 }
104- }
10593
106- private FileKeyLockManager () {
107- }
108-
109- private LockedFileChannel registerLockedFileChannel (final FileChannel channel , final String key , final KeyLockState state ) {
110- final LockedFileChannel lockedChannel = new ManagedLockedFileChannel (state , channel , key );
111- phantomRefs .add (new LockedChannelReference (lockedChannel , key , state ));
112- return lockedChannel ;
94+ final KeyLockState newState = new KeyLockState (path );
95+ while (state == null ) {
96+ final WeakValue ref = locks .compute (key ,
97+ (k , v ) -> (v != null && v .get () != null )
98+ ? v
99+ : new WeakValue (k , newState , refQueue ));
100+ state = ref .get ();
101+ }
102+ return state ;
113103 }
114104
115105 /**
@@ -126,22 +116,8 @@ private LockedFileChannel registerLockedFileChannel(final FileChannel channel, f
126116 * if acquiring the file lock fails
127117 */
128118 public LockedFileChannel lockForReading (final Path path ) throws IOException {
129- /* if there are stale entries in the queue, clean them before we try to lock */
130- clearQueue ();
131-
132- final String key = path .toAbsolutePath ().toString ();
133- final KeyLockState state = locks .computeIfAbsent (key , k -> new KeyLockState (path ));
134-
135- state .lockForReading ();
136119
137- final FileChannel channel ;
138- try {
139- channel = FileChannel .open (path , StandardOpenOption .READ );
140- } catch (final IOException e ) {
141- state .releaseLock ();
142- throw e ;
143- }
144- return registerLockedFileChannel (channel , key , state );
120+ return keyLockState (path ).acquireRead ();
145121 }
146122
147123 /**
@@ -155,23 +131,8 @@ public LockedFileChannel lockForReading(final Path path) throws IOException {
155131 * if acquiring the file lock fails
156132 */
157133 public LockedFileChannel lockForWriting (final Path path ) throws IOException {
158- /* if there are stale entries in the queue, clean them before we try to lock */
159- clearQueue ();
160-
161- final String key = path .toAbsolutePath ().toString ();
162- final KeyLockState state = locks .computeIfAbsent (key , k -> new KeyLockState (path ));
163-
164- state .lockForWriting ();
165134
166- final FileChannel channel ;
167- try {
168- channel = FileChannel .open (path , StandardOpenOption .READ , StandardOpenOption .WRITE , StandardOpenOption .CREATE );
169- } catch (final IOException e ) {
170- state .releaseLock ();
171- throw e ;
172- }
173-
174- return registerLockedFileChannel (channel , key , state );
135+ return keyLockState (path ).acquireWrite ();
175136 }
176137
177138 /**
@@ -182,24 +143,8 @@ public LockedFileChannel lockForWriting(final Path path) throws IOException {
182143 * @return a {@link LockedChannel} if the lock was acquired, null otherwise
183144 */
184145 public LockedFileChannel tryLockForReading (final Path path ) {
185- /* if there are stale entries in the queue, clean them before we try to lock */
186- clearQueue ();
187-
188- final String key = path .toAbsolutePath ().toString ();
189- final KeyLockState state = locks .computeIfAbsent (key , k -> new KeyLockState (path ));
190-
191- if (!state .tryLockForReading ())
192- return null ;
193146
194- final FileChannel channel ;
195- try {
196- channel = FileChannel .open (path , StandardOpenOption .READ );
197- } catch (final IOException e ) {
198- state .releaseLock ();
199- return null ;
200- }
201-
202- return registerLockedFileChannel (channel , key , state );
147+ return keyLockState (path ).tryAcquireRead ();
203148 }
204149
205150 /**
@@ -210,32 +155,16 @@ public LockedFileChannel tryLockForReading(final Path path) {
210155 * @return a {@link LockedChannel} if the lock was acquired, null otherwise
211156 */
212157 public LockedFileChannel tryLockForWriting (final Path path ) {
213- /* if there are stale entries in the queue, clean them before we try to lock */
214- clearQueue ();
215-
216- final String key = path .toAbsolutePath ().toString ();
217- final KeyLockState state = locks .computeIfAbsent (key , k -> new KeyLockState (path ));
218-
219- if (!state .tryLockForWriting ())
220- return null ;
221158
222- final FileChannel channel ;
223- try {
224- channel = FileChannel .open (path , StandardOpenOption .READ , StandardOpenOption .WRITE , StandardOpenOption .CREATE );
225- } catch (final IOException e ) {
226- state .releaseLock ();
227- return null ;
228- }
229-
230- return registerLockedFileChannel (channel , key , state );
159+ return keyLockState (path ).tryAcquireWrite ();
231160 }
232161
233162 /**
234163 * Returns the number of keys currently being tracked.
235164 *
236165 * @return the number of keys with associated locks
237166 */
238- public int size () {
167+ int size () {
239168
240169 return locks .size ();
241170 }
@@ -247,15 +176,6 @@ public int size() {
247176 * @return true if removed, false if currently in use or not found
248177 */
249178 public boolean removeLockIfUnused (final String key ) {
250-
251- final KeyLockState state = locks .get (key );
252- if (state == null )
253- return false ;
254-
255- synchronized (state ) {
256- if (!state .isLocked ())
257- return locks .remove (key , state );
258- }
259- return false ;
179+ throw new UnsupportedOperationException ("TODO. REMOVE" );
260180 }
261181}
0 commit comments