Skip to content

Commit e709b59

Browse files
committed
Rework IoPolicy
Make IoPolicy an enum that just communicates the intented locking behaviour (STRICT, PERMISSIVE, UNSAFE). How exactly this is implemented is up to the backend. For the FileSystemKeyValueAccess, the IoPolicy is simply to the FileKeyLockManager, then on to the FileLockState, and finally the ChannelLock, which actually implements the behaviour using FileLock (or not).
1 parent 5cfda39 commit e709b59

6 files changed

Lines changed: 285 additions & 290 deletions

File tree

src/main/java/org/janelia/saalfeldlab/n5/ChannelLock.java

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,22 @@ class ChannelLock implements Closeable {
1818

1919
private final FileChannel channel;
2020

21-
//Used to ensure a hard reference exists until we close
21+
/**
22+
* Hold a hard reference to the {@code FileLock} to make sure it is not
23+
* prematurely released.
24+
* <p>
25+
* NB: We do not call {@code lock.release()} in {@link #close}, because at
26+
* this point the channel might be already closed (by an external writer).
27+
* {@code lock.release()} will throw an exception if the channel is already
28+
* closed. Instead, we just close the channel which will automatically
29+
* release the lock.
30+
*/
2231
@SuppressWarnings({"unused", "FieldCanBeLocal"})
23-
private final FileLock unusedHardRef;
32+
private final FileLock lock;
2433

2534
private ChannelLock(final FileChannel channel, final FileLock lock) {
2635
this.channel = channel;
27-
this.unusedHardRef = lock;
36+
this.lock = lock;
2837
}
2938

3039
public void close() throws IOException {
@@ -56,9 +65,12 @@ FileChannel getChannel() {
5665
* @throws IOException if an error occurs while opening the channel, or if
5766
* the calling thread is interrupted while waiting for the {@code FileLock}.
5867
*/
59-
static ChannelLock lock(final Path path, final boolean forWriting) throws IOException {
68+
static ChannelLock lock(final Path path, final boolean forWriting, final IoPolicy policy) throws IOException {
6069

6170
final FileChannel channel = openFileChannel(path, forWriting);
71+
if (policy == IoPolicy.UNSAFE) {
72+
return new ChannelLock(channel, null);
73+
}
6274
try {
6375
while (true) {
6476
try {
@@ -74,34 +86,12 @@ static ChannelLock lock(final Path path, final boolean forWriting) throws IOExce
7486
}
7587
}
7688
} catch (Exception e) {
77-
closeQuietly(channel);
78-
throw e;
79-
}
80-
}
81-
82-
/**
83-
* Create a {@link FileChannel} on the given {@code path} and try to lock it
84-
* with a system-level {@link FileLock}. If the channel cannot be locked,
85-
* {@code null} is returned.
86-
* <p>
87-
* The {@code FileLock} is exclusive if the {@code path} is locked {@code
88-
* forWriting}, and shared otherwise.
89-
* <p>
90-
* If the {@code path} is locked {@code forWriting} non-existing file and
91-
* the parent directories are created as needed.
92-
*
93-
* @throws IOException if an error occurs while opening the channel.
94-
*/
95-
static ChannelLock tryLock(final Path path, final boolean forWriting) throws IOException {
96-
97-
FileChannel channel = null;
98-
try {
99-
channel = openFileChannel(path, forWriting);
100-
final FileLock lock = channel.tryLock(0, Long.MAX_VALUE, !forWriting);
101-
return lock == null ? null : new ChannelLock(channel, lock);
102-
} catch (Exception e) {
103-
closeQuietly(channel);
104-
throw e;
89+
if (policy == IoPolicy.STRICT) {
90+
closeQuietly(channel);
91+
throw e;
92+
} else {
93+
return new ChannelLock(channel, null);
94+
}
10595
}
10696
}
10797

src/main/java/org/janelia/saalfeldlab/n5/FileKeyLockManager.java

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.io.IOException;
3232
import java.lang.ref.ReferenceQueue;
3333
import java.lang.ref.WeakReference;
34+
import java.nio.channels.FileLock;
3435
import java.nio.file.Path;
3536
import java.util.concurrent.ConcurrentHashMap;
3637

@@ -79,25 +80,26 @@ private void cleanUp()
7980
}
8081
}
8182

82-
private KeyLockState keyLockState(final Path path) {
83+
private KeyLockState keyLockState(final Path path, final IoPolicy policy) throws IOException {
8384

8485
final String key = path.toAbsolutePath().toString();
8586

8687
cleanUp();
8788

8889
final WeakValue existingRef = locks.get(key);
8990
KeyLockState state = existingRef == null ? null : existingRef.get();
90-
if (state != null) {
91-
return state;
91+
if (state == null) {
92+
final KeyLockState newState = new KeyLockState(path, policy);
93+
while (state == null) {
94+
final WeakValue ref = locks.compute(key,
95+
(k, v) -> (v != null && v.get() != null)
96+
? v
97+
: new WeakValue(k, newState, refQueue));
98+
state = ref.get();
99+
}
92100
}
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();
101+
if (state.policy() != policy) {
102+
throw new IOException("Trying to lock \"" + path + "\" with policy " + policy + ", but it is already used with " + state.policy());
101103
}
102104
return state;
103105
}
@@ -108,31 +110,73 @@ private KeyLockState keyLockState(final Path path) {
108110
* <p>
109111
* The first reader will acquire a shared file lock. Subsequent readers
110112
* only acquire the thread-level lock.
113+
* <p>
114+
* The given locking {@link IoPolicy policy} applies to OS-level locking.
115+
* For both the {@code STRICT} and {@code PERMISSIVE} policy, a {@link
116+
* FileLock} is obtained. If this fails, {@code STRICT} will throw an {@code
117+
* IOException}. {@code PERMISSIVE} will proceed without locking. {@code
118+
* UNSAFE} will not attempt OS-level locking, however will still manage
119+
* mutual exclusion of readers and writers in the same JVM. Trying to lock
120+
* the same path with different locking policies will throw an {@code
121+
* IOException}.
111122
*
112123
* @param path
113-
* the key (file path) to lock for reading
124+
* the key (file path) to lock for reading
125+
* @param policy
126+
* the locking policy
127+
*
114128
* @return a {@link LockedChannel} that must be closed when done
129+
*
115130
* @throws IOException
116-
* if acquiring the file lock fails
131+
* if acquiring the file lock fails
132+
*/
133+
public LockedFileChannel lockForReading(final Path path, final IoPolicy policy) throws IOException {
134+
135+
return keyLockState(path, policy).acquireRead();
136+
}
137+
138+
/**
139+
* Acquires a read lock for the specified key with the {@link IoPolicy#STRICT} locking policy.
117140
*/
118141
public LockedFileChannel lockForReading(final Path path) throws IOException {
119142

120-
return keyLockState(path).acquireRead();
143+
return lockForReading(path, IoPolicy.STRICT);
121144
}
122145

123146
/**
124147
* Acquires a write lock for the specified key. Only one thread can hold a
125148
* write lock for a key at a time, and no readers can hold locks.
149+
* <p>
150+
* The given locking {@link IoPolicy policy} applies to OS-level locking.
151+
* For both the {@code STRICT} and {@code PERMISSIVE} policy, a {@link
152+
* FileLock} is obtained. If this fails, {@code STRICT} will throw an {@code
153+
* IOException}. {@code PERMISSIVE} will proceed without locking. {@code
154+
* UNSAFE} will not attempt OS-level locking, however will still manage
155+
* mutual exclusion of readers and writers in the same JVM. Trying to lock
156+
* the same path with different locking policies will throw an {@code
157+
* IOException}.
126158
*
127159
* @param path
128-
* the file path to lock for writing
160+
* the file path to lock for writing
161+
* @param policy
162+
* the locking policy
163+
*
129164
* @return a {@link LockedChannel} that must be closed when done
165+
*
130166
* @throws IOException
131-
* if acquiring the file lock fails
167+
* if acquiring the file lock fails
168+
*/
169+
public LockedFileChannel lockForWriting(final Path path, final IoPolicy policy) throws IOException {
170+
171+
return keyLockState(path, policy).acquireWrite();
172+
}
173+
174+
/**
175+
* Acquires a write lock for the specified key with the {@link IoPolicy#STRICT} locking policy.
132176
*/
133177
public LockedFileChannel lockForWriting(final Path path) throws IOException {
134178

135-
return keyLockState(path).acquireWrite();
179+
return lockForWriting(path, IoPolicy.STRICT);
136180
}
137181

138182
/**
@@ -144,14 +188,4 @@ int size() {
144188

145189
return locks.size();
146190
}
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-
}
157191
}

0 commit comments

Comments
 (0)