/** * 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 io.horizondb.db.Configuration; import io.horizondb.db.commitlog.CommitLog.FlushTask; import io.horizondb.db.commitlog.CommitLog.WriteTask; import io.horizondb.db.metrics.PrefixFilter; import io.horizondb.db.metrics.ThreadPoolExecutorMetrics; import io.horizondb.db.util.concurrent.ExecutorsUtils; import io.horizondb.db.util.concurrent.ForwardingRunnableScheduledFuture; import io.horizondb.db.util.concurrent.NamedThreadFactory; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.RunnableScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.MetricRegistry; import com.google.common.util.concurrent.ListenableFuture; import static com.codahale.metrics.MetricRegistry.name; /** * Base class for the <code>WriteExecutor</code>s implementations. * * @author Benjamin */ abstract class AbstractWriteExecutor implements WriteExecutor { /** * The database configuration. */ private final Configuration configuration; /** * The executor used to flush the data changes. */ private final WriteThreadPoolExecutor executor; /** * @param configuration */ public AbstractWriteExecutor(Configuration configuration, FlushTask flushTask) { this.configuration = configuration; this.executor = createWriteThreadPoolExecutor(configuration, name(getName(), "executor"), flushTask); } /** * {@inheritDoc} */ @Override public final void register(MetricRegistry registry) { registry.registerAll(new ThreadPoolExecutorMetrics(name(getName(), "executor"), this.executor)); } /** * {@inheritDoc} */ @Override public final void unregister(MetricRegistry registry) { registry.removeMatching(new PrefixFilter(getName())); } /** * {@inheritDoc} */ @Override public final String getName() { return MetricRegistry.name(this.getClass()); } /** * {@inheritDoc} */ @Override public final ListenableFuture<ReplayPosition> executeWrite(WriteTask writeTask) { CommitLogWriteFutureTask<ReplayPosition> futureTask = new CommitLogWriteFutureTask<>(writeTask); this.executor.submit(futureTask); return futureTask; } /** * {@inheritDoc} */ @Override public final void shutdown() throws InterruptedException { this.executor.flush(); ExecutorsUtils.shutdownAndAwaitForTermination(this.executor, this.configuration.getShutdownWaitingTimeInSeconds()); } /** * Creates the <code>WriteThreadPoolExecutor</code> that will be used by this <code>WriteExecutor</code> * to perform the writes. * * @param configuration the database configuration * @param flushTask the flush task * @return the <code>WriteThreadPoolExecutor</code> that will be used by <code>WriteExecutor</code> to perform * the writes */ protected abstract WriteThreadPoolExecutor createWriteThreadPoolExecutor(Configuration configuration, String name, FlushTask flushTask); /** * <code>ScheduledThreadPoolExecutor</code> that flush the data to disk. * */ protected static class WriteThreadPoolExecutor extends ScheduledThreadPoolExecutor { /** * The class logger. */ private final Logger logger = LoggerFactory.getLogger(getClass()); /** * The flush task. */ private final FlushTask flushTask; /** * The <code>CommitLogWriteFutureTask</code>s waiting for the flush signal. */ private final Queue<CommitLogWriteFutureTask<?>> waitingFutures = new LinkedList<>(); public WriteThreadPoolExecutor(String name, FlushTask flushTask) { super(1, new NamedThreadFactory(name)); this.flushTask = flushTask; } /** * Executes a flush task as soon as possible. */ final void flush() { execute(this.flushTask); } @Override protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) { if (runnable instanceof CommitLogWriteFutureTask) { return new RunnableAwareTask<>(task, runnable); } return task; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override protected final void beforeExecute(Thread thread, Runnable runnable) { if (runnable instanceof RunnableAwareTask) { Runnable futureTask = ((RunnableAwareTask<?>) runnable).getRunnable(); beforeWrite(); addToWaitingFutures((CommitLogWriteFutureTask<ReplayPosition>) futureTask); this.logger.debug("Executing write with {}", futureTask); } else { this.logger.debug("Flushing writes to the disk."); } } /** * {@inheritDoc} */ @Override protected final void afterExecute(Runnable runnable, Throwable throwable) { if (runnable instanceof RunnableAwareTask<?> || throwable != null) { this.logger.debug("Write {} is now waiting for flush to disk.", ((RunnableAwareTask<?>) runnable).getRunnable()); return; } afterFlush(); notifyWaitingFutures(); } /** * Returns the flush task. * * @return the flush task. */ protected final FlushTask getFlushTask() { return this.flushTask; } /** * Extension point. */ protected void beforeWrite() { } /** * Extension point. */ protected void afterFlush() { } /** * Notifies all the <code>Future</code>s that were waiting for a flush that it has been done. */ private void notifyWaitingFutures() { int count = 0; synchronized (this.waitingFutures) { CommitLogWriteFutureTask<?> future; while ((future = this.waitingFutures.poll()) != null) { future.flushed(); count++; } } this.logger.debug("Flushed {} writes to the disk.", Integer.valueOf(count)); } /** * Adds the specified <code>Future</code> to the list of <code>Future</code> that will be waiting for the flush. * @param future the <code>Future</code> to add */ private void addToWaitingFutures(CommitLogWriteFutureTask<ReplayPosition> future) { synchronized (this.waitingFutures) { this.waitingFutures.offer(future); } } } /** * <code>RunnableScheduledFuture</code> that provide access to the decorated <code>Runnable</code>. * * @param <V> */ private static final class RunnableAwareTask<V> extends ForwardingRunnableScheduledFuture<V> { /** * The task. */ private final RunnableScheduledFuture<V> task; /** * The original runnable. */ private final Runnable runnable; /** * Creates a task that is aware of the decorated <code>Runnable</code>. * * @param task the task decorating the runnable * @param runnable the runnable */ public RunnableAwareTask(RunnableScheduledFuture<V> task, Runnable runnable) { this.task = task; this.runnable = runnable; } /** * {@inheritDoc} */ @Override protected RunnableScheduledFuture<V> delegate() { return this.task; } /** * Returns the runnable. * * @return the runnable * @return */ public Runnable getRunnable() { return this.runnable; } } }