/**
* Copyright 2014 Benjamin Lerer
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.horizondb.db.commitlog;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.common.util.concurrent.ExecutionList;
import com.google.common.util.concurrent.ListenableFuture;
/**
* <code>FutureTask</code> that is used to ensure that the future will be in the completed state only
* once the data have been written and flushed to the disk.
*
* @author Benjamin
*
*/
final class CommitLogWriteFutureTask<V> extends FutureTask<V> implements ListenableFuture<V> {
/**
* The execution list used to hold the listeners.
*/
private final ExecutionList executionList = new ExecutionList();
/**
* The latch that will make thread await until the written data has been flushed to the disk.
*/
private CountDownLatch flushSignal = new CountDownLatch(1);
/**
* Creates a <code>FutureTask</code> that is used to ensure that the future will be in the completed
* state only once the data have been written and flushed to the disk.
*
* @param writeFuture the write <code>Future</code>.
*/
public CommitLogWriteFutureTask(Callable<V> callable) {
super(callable);
}
/**
* Signal that Sets the flush future associated to this write.
*/
void flushed() {
this.flushSignal.countDown();
notifyListeners();
}
/**
* {@inheritDoc}
*/
@Override
public V get() throws InterruptedException, ExecutionException {
V result = super.get();
waitForFlush();
return result;
}
/**
* {@inheritDoc}
*/
@Override
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
long timeInNanos = System.nanoTime();
V result = super.get(timeout, unit);
long usedTime = System.nanoTime() - timeInNanos;
long remainingTime = unit.toNanos(timeout) - usedTime;
waitForFlush(remainingTime, TimeUnit.NANOSECONDS);
return result;
}
/**
* {@inheritDoc}
*/
@Override
public void addListener(Runnable runnable, Executor executor) {
this.executionList.add(runnable, executor);
}
/**
* Notify the listeners.
*/
private void notifyListeners() {
this.executionList.execute();
}
/**
* Wait for the data to be flushed on the disk.
*
* @throws InterruptedException if the thread is interrupted
* @throws ExecutionException if a problem occurs while flushing the data
*/
private void waitForFlush() throws InterruptedException, ExecutionException {
this.flushSignal.await();
}
/**
* Waits if necessary for at most the given time for the data to be flushed on the disk.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
* @throws InterruptedException if the thread is interrupted
* @throws ExecutionException if a problem occurs while flushing the data.
* @throws TimeoutException if the wait timed out
*/
private void waitForFlush(long timeout, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
this.flushSignal.await(timeout, timeUnit);
}
}