nebula.plugin.metrics.dispatcher.AbstractQueuedExecutionThreadService.java Source code

Java tutorial

Introduction

Here is the source code for nebula.plugin.metrics.dispatcher.AbstractQueuedExecutionThreadService.java

Source

/*
 *  Copyright 2015-2016 Netflix, Inc.
 *
 *  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 nebula.plugin.metrics.dispatcher;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AbstractExecutionThreadService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage;

/**
 * An {@link AbstractQueuedExecutionThreadService} that allows actions of type <pre>E</pre> to be queued and executed in
 * the order received.
 *
 * @author Danny Thomas
 */
public abstract class AbstractQueuedExecutionThreadService<E> extends AbstractExecutionThreadService {
    private static final Set<State> QUEUE_AVAILABLE_STATES = Sets.newHashSet(State.STARTING, State.RUNNING,
            State.STOPPING);
    // We can't use the MetricsLoggerFactory here, or we'll get a feedback loop from the debug statements in the indexing critical paths
    private final Logger logger = LoggerFactory.getLogger(AbstractExecutionThreadService.class);
    private final BlockingQueue<E> queue;
    private final boolean failOnError;
    private final AtomicBoolean failed = new AtomicBoolean();
    private final boolean verboseErrorOuput;

    public AbstractQueuedExecutionThreadService(boolean failOnError, boolean verboseErrorOuput) {
        this(new LinkedBlockingQueue<E>(), failOnError, verboseErrorOuput);
    }

    @VisibleForTesting
    AbstractQueuedExecutionThreadService(BlockingQueue<E> queue, boolean failOnError, boolean verboseErrorOutput) {
        this.queue = checkNotNull(queue);
        this.failOnError = failOnError;
        this.verboseErrorOuput = verboseErrorOutput;
    }

    protected abstract void execute(E action) throws Exception;

    @Override
    protected final void run() throws Exception {
        while (isRunning() || !queue.isEmpty()) {
            E action = queue.poll(100, TimeUnit.MILLISECONDS);
            doExecute(action);
        }
        logger.debug("Service is not running and queue is empty, returning from run()");
    }

    private void doExecute(@Nullable E action) {
        try {
            if (action != null) {
                logger.debug("Executing {}", action);
                execute(action);
            }
        } catch (Exception e) {
            logger.debug("Error executing metrics action {}: {}", action, getRootCauseMessage(e));
            if (failOnError) {
                logger.debug("Shutting down {} due to previous failure", this);
                queue.clear();
                failed.set(true);
                if (verboseErrorOuput)
                    throw Throwables.propagate(e);
            }
        }
    }

    protected final boolean hasFailed() {
        return failed.get();
    }

    @Override
    protected final void shutDown() throws Exception {
        try {
            beforeShutDown(); // We want any problems with the before shutdown hook to prevent queue draining, so we handle that inside this try

            logger.debug("Shutting down queued execution service {}. Draining queue...", this);
            List<E> remaining = Lists.newArrayListWithCapacity(queue.size());
            queue.drainTo(remaining);
            for (E e : remaining) {
                execute(e);
            }
            checkState(queue.isEmpty(), "The queue should have been drained before shutdown");
        } catch (Exception e) {
            logger.error("An error occurred during shutdown (error message: )", getRootCauseMessage(e));
        }
        postShutDown();
    }

    protected void beforeShutDown() throws Exception {
    }

    protected void postShutDown() throws Exception {
    }

    protected final void queue(E action) {
        checkNotNull(action);
        if (!QUEUE_AVAILABLE_STATES.contains(state())) {
            logger.debug("Dispatcher is not running, dropping action {}", action);
        } else if (isAsync()) {
            logger.debug("Queueing {}", action);
            queue.add(action);
        } else {
            doExecute(action);
        }
    }

    protected final void executeSynchronously(E action) {
        checkNotNull(action);
        if (!QUEUE_AVAILABLE_STATES.contains(state())) {
            logger.debug("Dispatcher is not running, dropping action {}", action);
        } else {
            doExecute(action);
        }
    }

    /**
     * Allow service to run non-asynchronously to allow unit testing of concrete implementations, without needing to
     * deal with timing issues.
     *
     * @return true if the service is asynchronous
     */
    protected boolean isAsync() {
        return true;
    }
}