package net.kencochrane.raven.connection;

import net.kencochrane.raven.event.LoggedEvent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Asynchronous usage of a connection.
 * <p>
 * Instead of synchronously sending each event to a connection, use a ThreadPool to establish the connection
 * and submit the event.
 * </p>
 */
public class AsyncConnection implements Connection {
    private static final Logger logger = Logger.getLogger(AsyncConnection.class.getCanonicalName());
    /**
     * Connection used to actually send the events.
     */
    private final Connection actualConnection;
    private final ExecutorService executorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new DaemonThreadFactory());

    /**
     * Create a connection which will rely on an executor to send events.
     *
     * @param actualConnection connection used to send the events.
     */
    public AsyncConnection(Connection actualConnection) {
        this.actualConnection = actualConnection;
    }

    @Override
    public void send(LoggedEvent event) {
        // TODO: Consider adding an option to wait when it's full?
        executorService.execute(new LoggedEventSubmitter(event));
    }

    private static final class DaemonThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        private DaemonThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
            if (!t.isDaemon())
                t.setDaemon(true);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

    /**
     * Simple runnable using the {@link #send(net.kencochrane.raven.event.LoggedEvent)} method of the
     * {@link #actualConnection}.
     */
    private final class LoggedEventSubmitter implements Runnable {
        private final LoggedEvent event;

        private LoggedEventSubmitter(LoggedEvent event) {
            this.event = event;
        }

        @Override
        public void run() {
            try {
                actualConnection.send(event);
            } catch (Exception e) {
                logger.log(Level.SEVERE, "An exception occurred while sending the event to Sentry.", e);
            }
        }
    }
}

@@ -85,16 +85,39 @@
 Event;%0A%0A
+import java.util.List;%0A
 import j
@@ -153,16 +153,16 @@
 ervice;%0A
-
 import j
@@ -235,16 +235,54 @@
 actory;%0A
+import java.util.concurrent.TimeUnit;%0A
 import j
@@ -1238,24 +1238,24 @@
 nnection) %7B%0A
-
         this
@@ -1288,24 +1288,1106 @@
 Connection;%0A
+        addShutdownHook();%0A    %7D%0A%0A    private void addShutdownHook() %7B%0A        //TODO: JUL loggers are shutdown by an other shutdown hook, it's possible that nothing will get actually logged.%0A        Runtime.getRuntime().addShutdownHook(new Thread() %7B%0A            public void run() %7B%0A                logger.log(Level.INFO, %22Gracefully shutdown sentry threads.%22);%0A                executorService.shutdown();%0A                try %7B%0A                    if (!executorService.awaitTermination(1000, TimeUnit.MILLISECONDS)) %7B%0A                        logger.log(Level.WARNING, %22Graceful shutdown took too much time, forcing the shutdown.%22);%0A                        List%3CRunnable%3E tasks = executorService.shutdownNow();%0A                        logger.log(Level.INFO, tasks.size() + %22 tasks failed to execute before the shutdown.%22);%0A                    %7D%0A                    logger.log(Level.SEVERE, %22Shutdown interrupted.%22);%0A                %7D catch (InterruptedException e) %7B%0A                    logger.log(Level.SEVERE, %22Shutdown interrupted.%22);%0A                %7D%0A            %7D%0A        %7D);%0A
     %7D%0A%0A    @

Add a shutdown hook to terminate AsyncConnection