net.java.truevfs.kernel.impl.LockController Maven / Gradle / Ivy
/*
* Copyright © 2005 - 2021 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package net.java.truevfs.kernel.impl;
import net.java.truecommons.cio.*;
import net.java.truecommons.io.DecoratingInputStream;
import net.java.truecommons.io.DecoratingOutputStream;
import net.java.truecommons.io.DecoratingSeekableChannel;
import net.java.truecommons.shed.BitField;
import net.java.truevfs.kernel.spec.*;
import javax.annotation.concurrent.ThreadSafe;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
import java.util.Map;
import java.util.Optional;
import static net.java.truevfs.kernel.impl.LockingStrategy.*;
/**
* Provides read/write locking for multi-threaded access by its clients.
*
* This controller is a barrier for {@link net.java.truevfs.kernel.impl.NeedsWriteLockException}s:
* Whenever the decorated controller chain throws a {@code NeedsWriteLockException}, the read lock gets released before
* the write lock gets acquired and the operation gets retried.
*
* This controller is also an emitter of and a barrier for
* {@link net.java.truevfs.kernel.impl.NeedsLockRetryException}s:
* If a lock can't get immediately acquired, then a {@code NeedsLockRetryException} gets thrown.
* This will unwind the stack of federated file systems until the {@code LockController} for the first visited file
* system is found.
* This controller will then pause the current thread for a small random amount of milliseconds before retrying the
* operation.
*
* @author Christian Schlichtherle
* @see LockingStrategy
*/
@ThreadSafe
abstract class LockController implements DelegatingArchiveController {
@Override
public Optional extends FsNode> node(BitField options, FsNodeName name) throws IOException {
return timedReadOrWriteLocked(new Op, IOException>() {
@Override
public Optional extends FsNode> call() throws IOException {
return getController().node(options, name);
}
});
}
@Override
public void checkAccess(BitField options, FsNodeName name, BitField types) throws IOException {
timedReadOrWriteLocked(new Op() {
@Override
public Void call() throws IOException {
getController().checkAccess(options, name, types);
return null;
}
});
}
@Override
public void setReadOnly(BitField options, FsNodeName name) throws IOException {
timedLocked.using(writeLock()).call(new Op() {
@Override
public Void call() throws IOException {
getController().setReadOnly(options, name);
return null;
}
});
}
@Override
public boolean setTime(BitField options, FsNodeName name, Map times) throws IOException {
return timedLocked.using(writeLock()).call(new Op() {
@Override
public Boolean call() throws IOException {
return getController().setTime(options, name, times);
}
});
}
@Override
public boolean setTime(BitField options, FsNodeName name, BitField types, long time) throws IOException {
return timedLocked.using(writeLock()).call(new Op() {
@Override
public Boolean call() throws IOException {
return getController().setTime(options, name, types, time);
}
});
}
@Override
public InputSocket extends Entry> input(BitField options, FsNodeName name) {
return new AbstractInputSocket() {
final InputSocket extends Entry> socket = getController().input(options, name);
@Override
public Entry target() throws IOException {
return fastLocked.using(writeLock()).call(new Op() {
@Override
public Entry call() throws IOException {
return socket.target();
}
});
}
@Override
public InputStream stream(OutputSocket extends Entry> peer) throws IOException {
return timedLocked.using(writeLock()).call(new Op() {
@Override
public InputStream call() throws IOException {
return new LockInputStream(socket.stream(peer));
}
});
}
@Override
public SeekableByteChannel channel(OutputSocket extends Entry> peer) throws IOException {
return timedLocked.using(writeLock()).call(new Op() {
@Override
public SeekableByteChannel call() throws IOException {
return new LockSeekableChannel(socket.channel(peer));
}
});
}
};
}
@Override
public OutputSocket extends Entry> output(BitField options, FsNodeName name, Optional template) {
return new AbstractOutputSocket() {
final OutputSocket extends Entry> socket = getController().output(options, name, template);
@Override
public Entry target() throws IOException {
return fastLocked.using(writeLock()).call(new Op() {
@Override
public Entry call() throws IOException {
return socket.target();
}
});
}
@Override
public OutputStream stream(InputSocket extends Entry> peer) throws IOException {
return timedLocked.using(writeLock()).call(new Op() {
@Override
public OutputStream call() throws IOException {
return new LockOutputStream(socket.stream(peer));
}
});
}
@Override
public SeekableByteChannel channel(InputSocket extends Entry> peer) throws IOException {
return timedLocked.using(writeLock()).call(new Op() {
@Override
public SeekableByteChannel call() throws IOException {
return new LockSeekableChannel(socket.channel(peer));
}
});
}
};
}
@Override
public void make(BitField options, FsNodeName name, Entry.Type type, Optional template) throws IOException {
timedLocked.using(writeLock()).call(new Op() {
@Override
public Void call() throws IOException {
getController().make(options, name, type, template);
return null;
}
});
}
@Override
public void unlink(BitField options, FsNodeName name) throws IOException {
timedLocked.using(writeLock()).call(new Op() {
@Override
public Void call() throws IOException {
getController().unlink(options, name);
return null;
}
});
}
@Override
public void sync(BitField options) throws FsSyncException {
timedLocked.using(writeLock()).call(new Op() {
@Override
public Void call() throws FsSyncException {
getController().sync(options);
return null;
}
});
}
private T timedReadOrWriteLocked(final Op op) throws IOException {
try {
return timedLocked.using(readLock()).call(op);
} catch (NeedsWriteLockException e) {
if (readLockedByCurrentThread()) {
throw e;
}
return timedLocked.using(writeLock()).call(op);
}
}
private final class LockInputStream extends DecoratingInputStream {
LockInputStream(InputStream in) {
super(in);
}
@Override
public void close() throws IOException {
deadLocked.using(writeLock()).call(new Op() {
@Override
public Void call() throws IOException {
in.close();
return null;
}
});
}
}
private final class LockOutputStream extends DecoratingOutputStream {
LockOutputStream(OutputStream out) {
super(out);
}
@Override
public void close() throws IOException {
deadLocked.using(writeLock()).call(new Op() {
@Override
public Void call() throws IOException {
out.close();
return null;
}
});
}
}
private final class LockSeekableChannel extends DecoratingSeekableChannel {
LockSeekableChannel(SeekableByteChannel channel) {
super(channel);
}
@Override
public void close() throws IOException {
deadLocked.using(writeLock()).call(new Op() {
@Override
public Void call() throws IOException {
channel.close();
return null;
}
});
}
}
}