com.getsentry.raven.connection.AsyncConnection Maven / Gradle / Ivy
Show all versions of raven Show documentation
package com.getsentry.raven.connection;
import com.getsentry.raven.Raven;
import com.getsentry.raven.environment.RavenEnvironment;
import com.getsentry.raven.event.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Asynchronous usage of a connection.
*
* Instead of synchronously sending each event to a connection, use a ThreadPool to establish the connection
* and submit the event.
*/
public class AsyncConnection implements Connection {
private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class);
// CHECKSTYLE.OFF: ConstantName
private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class.getName() + ".lockdown");
// CHECKSTYLE.ON: ConstantName
/**
* Timeout of the {@link #executorService}, in milliseconds.
*/
private final long shutdownTimeout;
/**
* Connection used to actually send the events.
*/
private final Connection actualConnection;
/**
* Executor service in charge of running the connection in separate threads.
*/
private final ExecutorService executorService;
/**
* Shutdown hook used to stop the async connection properly when the JVM quits.
*/
private final ShutDownHook shutDownHook = new ShutDownHook();
/**
* Boolean that represents if graceful shutdown is enabled.
*/
private boolean gracefulShutdown;
/**
* Boolean used to check whether the connection is still open or not.
*/
private volatile boolean closed;
/**
* Creates a connection which will rely on an executor to send events.
*
* Will propagate the {@link #close()} operation.
*
* @param actualConnection connection used to send the events.
* @param executorService executorService used to process events, if null, the executorService will automatically
* be set to {@code Executors.newSingleThreadExecutor()}
* @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook.
* @param shutdownTimeout timeout for graceful shutdown of the executor, in milliseconds.
*/
public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown,
long shutdownTimeout) {
this.actualConnection = actualConnection;
if (executorService == null) {
this.executorService = Executors.newSingleThreadExecutor();
} else {
this.executorService = executorService;
}
if (gracefulShutdown) {
this.gracefulShutdown = gracefulShutdown;
addShutdownHook();
}
this.shutdownTimeout = shutdownTimeout;
}
/**
* Adds a hook to shutdown the {@link #executorService} gracefully when the JVM shuts down.
*/
private void addShutdownHook() {
// JUL loggers are shutdown by an other shutdown hook, it's possible that nothing will get actually logged.
Runtime.getRuntime().addShutdownHook(shutDownHook);
}
/**
* {@inheritDoc}
*
* The event will be added to a queue and will be handled by a separate {@code Thread} later on.
*/
@Override
public void send(Event event) {
if (!closed) {
executorService.execute(new EventSubmitter(event));
}
}
@Override
public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) {
actualConnection.addEventSendFailureCallback(eventSendFailureCallback);
}
/**
* {@inheritDoc}.
*
* Closing the {@link AsyncConnection} will attempt a graceful shutdown of the {@link #executorService} with a
* timeout of {@link #shutdownTimeout}, allowing the current events to be submitted while new events will
* be rejected.
* If the shutdown times out, the {@code executorService} will be forced to shutdown.
*/
@Override
public void close() throws IOException {
if (gracefulShutdown) {
shutDownHook.enabled = false;
}
doClose();
}
/**
* Close the connection whether it's from the shutdown hook or not.
*
* @see #close()
*/
@SuppressWarnings("checkstyle:magicnumber")
private void doClose() throws IOException {
logger.info("Gracefully shutdown sentry threads.");
closed = true;
executorService.shutdown();
try {
if (shutdownTimeout == -1L) {
// Block until the executor terminates, but log periodically.
long waitBetweenLoggingMs = 5000L;
while (true) {
if (executorService.awaitTermination(waitBetweenLoggingMs, TimeUnit.MILLISECONDS)) {
break;
}
logger.info("Still waiting on async executor to terminate.");
}
} else if (!executorService.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS)) {
logger.warn("Graceful shutdown took too much time, forcing the shutdown.");
List tasks = executorService.shutdownNow();
logger.info("{} tasks failed to execute before the shutdown.", tasks.size());
}
logger.info("Shutdown finished.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("Graceful shutdown interrupted, forcing the shutdown.");
List tasks = executorService.shutdownNow();
logger.info("{} tasks failed to execute before the shutdown.", tasks.size());
} finally {
actualConnection.close();
}
}
/**
* Simple runnable using the {@link #send(com.getsentry.raven.event.Event)} method of the
* {@link #actualConnection}.
*/
private final class EventSubmitter implements Runnable {
private final Event event;
private EventSubmitter(Event event) {
this.event = event;
}
@Override
public void run() {
RavenEnvironment.startManagingThread();
try {
// The current thread is managed by raven
actualConnection.send(event);
} catch (LockedDownException e) {
lockdownLogger.warn("The connection to Sentry is currently locked down.", e);
} catch (Exception e) {
logger.error("An exception occurred while sending the event to Sentry.", e);
} finally {
RavenEnvironment.stopManagingThread();
}
}
}
private final class ShutDownHook extends Thread {
/**
* Whether or not this ShutDownHook instance will do anything when run.
*/
private volatile boolean enabled = true;
@Override
public void run() {
if (!enabled) {
return;
}
RavenEnvironment.startManagingThread();
try {
// The current thread is managed by raven
logger.info("Automatic shutdown of the async connection");
AsyncConnection.this.doClose();
} catch (Exception e) {
logger.error("An exception occurred while closing the connection.", e);
} finally {
RavenEnvironment.stopManagingThread();
}
}
}
}