nebula.plugin.metrics.dispatcher.AbstractQueuedExecutionThreadService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-metrics-plugin Show documentation
Show all versions of gradle-metrics-plugin Show documentation
Gradle Metrics plugin: Collects gradle metrics and publishes to ElasticSearch HTTP/Splunk/REST endpoints
/*
* 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 E
to be queued and executed in
* the order received.
*
* @author Danny Thomas
*/
public abstract class AbstractQueuedExecutionThreadService extends AbstractExecutionThreadService {
private static final Set 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 queue;
private final boolean failOnError;
private final AtomicBoolean failed = new AtomicBoolean();
private final boolean verboseErrorOuput;
public AbstractQueuedExecutionThreadService(boolean failOnError, boolean verboseErrorOuput) {
this(new LinkedBlockingQueue(), failOnError, verboseErrorOuput);
}
@VisibleForTesting
AbstractQueuedExecutionThreadService(BlockingQueue 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 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;
}
}