package org.infinispan.distribution;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import org.infinispan.commands.VisitableCommand;
import org.infinispan.context.InvocationContext;
import org.infinispan.interceptors.DDAsyncInterceptor;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* Interceptor that allows for waiting for a command to be invoked, blocking that command and subsequently
* allowing that command to be released.
*
* @author William Burns
* @since 6.0
*/
public class BlockingInterceptor<T extends VisitableCommand> extends DDAsyncInterceptor {
private static final Log log = LogFactory.getLog(BlockingInterceptor.class);
private final CyclicBarrier barrier;
private final Class<T> commandClass;
private final boolean blockAfter;
private final boolean originLocalOnly;
private final AtomicBoolean suspended = new AtomicBoolean();
private final Predicate<T> acceptCommand;
public BlockingInterceptor(CyclicBarrier barrier, Class<T> commandClass,
boolean blockAfter, boolean originLocalOnly) {
this(barrier, commandClass, blockAfter, originLocalOnly, t -> true);
}
public BlockingInterceptor(CyclicBarrier barrier, Class<T> commandClass,
boolean blockAfter, boolean originLocalOnly, Predicate<T> acceptCommand) {
this.barrier = barrier;
this.commandClass = commandClass;
this.blockAfter = blockAfter;
this.originLocalOnly = originLocalOnly;
this.acceptCommand = acceptCommand;
}
public void suspend(boolean s) {
this.suspended.set(s);
}
private void blockIfNeeded(InvocationContext ctx, VisitableCommand command) throws BrokenBarrierException, InterruptedException {
if (suspended.get()) {
log.tracef("Suspended, not blocking command %s", command);
return;
}
if (commandClass.equals(command.getClass()) && (!originLocalOnly || ctx.isOriginLocal()) && acceptCommand.test(commandClass.cast(command))) {
log.tracef("Command blocking %s completion of %s", blockAfter ? "after" : "before", command);
// The first arrive and await is to sync with main thread
barrier.await();
// Now we actually block until main thread lets us go
barrier.await();
log.tracef("Command completed blocking completion of %s", command);
} else {
log.trace("Command arrived but already found a blocker");
}
}
@Override
protected Object handleDefault(InvocationContext ctx, VisitableCommand command) throws Throwable {
if (!blockAfter) {
blockIfNeeded(ctx, command);
}
return invokeNextAndFinally(ctx, command, (rCtx, rCommand, rv, t) -> {
if (blockAfter) {
blockIfNeeded(rCtx, rCommand);
}
});
}
}