void inputSucceeded(Input input) { /* cleanup... */ }
void noInputSucceeded() { /* cleanup... */ }
void runTask(Input input, SettableFuture<Void> failover) {
// Calls either failover.set(null) or inputSucceeded(input).
}
final AtomicReference<ListenableFuture<Void>> failovers =
new AtomicReference<>(immediateFuture(null));
void addInput(final Input input) {
final SettableFuture<Void> failover =
SettableFuture.create();
failovers.getAndSet(failover)
.addListener(() -> runTask(input, failover));
}
void noMoreInputs() {
failovers.getAndSet(null)
.addListener(() -> noInputSucceeded());
}
addInput
s to be run concurrently, but ensures that everything in runTask
(up to the failover.set
call, at least) is fully synchronized. Inputs are handled on a first-come-first-served basis. If noMoreInputs
is called after all inputs are added, then exactly one of inputSucceeded
or noInputSucceeded
will run to perform whatever cleanup is necessary.I call this the "failover trigger" pattern because it provides each task with a trigger it can use to optionally start the next task.