3131import java .io .IOException ;
3232import java .lang .ref .ReferenceQueue ;
3333import java .lang .ref .WeakReference ;
34+ import java .nio .channels .FileLock ;
3435import java .nio .file .Path ;
3536import java .util .concurrent .ConcurrentHashMap ;
3637
38+ import static org .janelia .saalfeldlab .n5 .LockingPolicy .STRICT ;
39+
3740/**
3841 * Provides thread-safe and process-safe read/write locking for filesystem paths.
3942 * Uses thread locks for JVM coordination and file locks for inter-process coordination.
4043 */
4144class FileKeyLockManager {
4245
43- static final FileKeyLockManager FILE_LOCK_MANAGER = new FileKeyLockManager ();
46+ /**
47+ * @deprecated use {@link FileKeyLockManager} instance per KVA instead of global one.
48+ */
49+ @ Deprecated
50+ static final FileKeyLockManager FILE_LOCK_MANAGER = new FileKeyLockManager (STRICT );
4451
45- private FileKeyLockManager () {
46- // singleton
47- }
52+ private final LockingPolicy policy ;
4853
54+ /**
55+ * Create a new {@link FileKeyLockManager} with the specified locking policy.
56+ * <p>
57+ * The given locking {@link LockingPolicy policy} applies to OS-level locking.
58+ * For both the {@code STRICT} and {@code PERMISSIVE} policy, a {@link
59+ * FileLock} is obtained. If this fails, {@code STRICT} will throw an {@code
60+ * IOException}. {@code PERMISSIVE} will proceed without locking. {@code
61+ * UNSAFE} will not attempt OS-level locking, however will still manage
62+ * mutual exclusion of readers and writers in the same JVM. Trying to lock
63+ * the same path with different locking policies will throw an {@code
64+ * IOException}.
65+ *
66+ * @param policy
67+ * the locking policy
68+ */
69+ FileKeyLockManager (final LockingPolicy policy ) {
70+ this .policy = policy ;
71+ }
4972
5073 private final ConcurrentHashMap <String , WeakValue > locks = new ConcurrentHashMap <>();
5174
@@ -79,25 +102,26 @@ private void cleanUp()
79102 }
80103 }
81104
82- private KeyLockState keyLockState (final Path path ) {
105+ private KeyLockState keyLockState (final Path path , final LockingPolicy policy ) throws IOException {
83106
84107 final String key = path .toAbsolutePath ().toString ();
85108
86109 cleanUp ();
87110
88111 final WeakValue existingRef = locks .get (key );
89112 KeyLockState state = existingRef == null ? null : existingRef .get ();
90- if (state != null ) {
91- return state ;
113+ if (state == null ) {
114+ final KeyLockState newState = new KeyLockState (path , policy );
115+ while (state == null ) {
116+ final WeakValue ref = locks .compute (key ,
117+ (k , v ) -> (v != null && v .get () != null )
118+ ? v
119+ : new WeakValue (k , newState , refQueue ));
120+ state = ref .get ();
121+ }
92122 }
93-
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 ();
123+ if (state .policy () != policy ) {
124+ throw new IOException ("Trying to lock \" " + path + "\" with policy " + policy + ", but it is already used with " + state .policy ());
101125 }
102126 return state ;
103127 }
@@ -110,29 +134,33 @@ private KeyLockState keyLockState(final Path path) {
110134 * only acquire the thread-level lock.
111135 *
112136 * @param path
113- * the key (file path) to lock for reading
137+ * the key (file path) to lock for reading
138+ *
114139 * @return a {@link LockedChannel} that must be closed when done
140+ *
115141 * @throws IOException
116- * if acquiring the file lock fails
142+ * if acquiring the file lock fails
117143 */
118144 public LockedFileChannel lockForReading (final Path path ) throws IOException {
119145
120- return keyLockState (path ).acquireRead ();
146+ return keyLockState (path , policy ).acquireRead ();
121147 }
122148
123149 /**
124150 * Acquires a write lock for the specified key. Only one thread can hold a
125151 * write lock for a key at a time, and no readers can hold locks.
126152 *
127153 * @param path
128- * the file path to lock for writing
154+ * the file path to lock for writing
155+ *
129156 * @return a {@link LockedChannel} that must be closed when done
157+ *
130158 * @throws IOException
131- * if acquiring the file lock fails
159+ * if acquiring the file lock fails
132160 */
133161 public LockedFileChannel lockForWriting (final Path path ) throws IOException {
134162
135- return keyLockState (path ).acquireWrite ();
163+ return keyLockState (path , policy ).acquireWrite ();
136164 }
137165
138166 /**
@@ -144,14 +172,4 @@ int size() {
144172
145173 return locks .size ();
146174 }
147-
148- /**
149- * Removes the lock state for a key if no locks are held.
150- *
151- * @param key the key whose lock state should be removed
152- * @return true if removed, false if currently in use or not found
153- */
154- public boolean removeLockIfUnused (final String key ) {
155- throw new UnsupportedOperationException ("TODO. REMOVE" );
156- }
157175}
0 commit comments