package com.ldbc.driver.runtime.executor; import com.ldbc.driver.ChildOperationGenerator; import com.ldbc.driver.Db; import com.ldbc.driver.Operation; import com.ldbc.driver.OperationHandlerRunnableContext; import com.ldbc.driver.WorkloadStreams; import com.ldbc.driver.runtime.ConcurrentErrorReporter; import com.ldbc.driver.runtime.DefaultQueues; import com.ldbc.driver.runtime.coordination.GlobalCompletionTimeReader; import com.ldbc.driver.runtime.coordination.LocalCompletionTimeWriter; import com.ldbc.driver.runtime.metrics.MetricsService; import com.ldbc.driver.runtime.scheduling.Spinner; import com.ldbc.driver.temporal.TimeSource; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import static java.lang.String.format; public class ThreadPoolOperationExecutor implements OperationExecutor { private final ExecutorService threadPoolExecutorService; private final AtomicLong uncompletedHandlers = new AtomicLong( 0 ); private final AtomicBoolean shutdown = new AtomicBoolean( false ); private final OperationHandlerRunnableContextRetriever operationHandlerRunnableContextRetriever; public ThreadPoolOperationExecutor( int threadCount, int boundedQueueSize, Db db, WorkloadStreams.WorkloadStreamDefinition streamDefinition, LocalCompletionTimeWriter localCompletionTimeWriter, GlobalCompletionTimeReader globalCompletionTimeReader, Spinner spinner, TimeSource timeSource, ConcurrentErrorReporter errorReporter, MetricsService metricsService, ChildOperationGenerator childOperationGenerator ) { this.operationHandlerRunnableContextRetriever = new OperationHandlerRunnableContextRetriever( streamDefinition, db, localCompletionTimeWriter, globalCompletionTimeReader, spinner, timeSource, errorReporter, metricsService ); ThreadFactory threadFactory = new ThreadFactory() { private final long factoryTimeStampId = System.currentTimeMillis(); int count = 0; @Override public Thread newThread( Runnable runnable ) { Thread newThread = new Thread( runnable, ThreadPoolOperationExecutor.class.getSimpleName() + "-id(" + factoryTimeStampId + ")" + "-thread(" + count++ + ")" ); return newThread; } }; this.threadPoolExecutorService = ThreadPoolExecutorWithAfterExecute.newFixedThreadPool( threadCount, threadFactory, uncompletedHandlers, boundedQueueSize, childOperationGenerator, operationHandlerRunnableContextRetriever, errorReporter ); } @Override public final void execute( Operation operation ) throws OperationExecutorException { uncompletedHandlers.incrementAndGet(); try { OperationHandlerRunnableContext operationHandlerRunnableContext = operationHandlerRunnableContextRetriever.getInitializedHandlerFor( operation ); threadPoolExecutorService.execute( operationHandlerRunnableContext ); } catch ( Throwable e ) { throw new OperationExecutorException( format( "Error retrieving handler\nOperation: %s\n%s", operation, ConcurrentErrorReporter.stackTraceToString( e ) ), e ); } } @Override synchronized public final void shutdown( long waitAsMilli ) throws OperationExecutorException { if ( shutdown.get() ) { throw new OperationExecutorException( "Executor has already been shutdown" ); } try { threadPoolExecutorService.shutdown(); boolean allHandlersCompleted = threadPoolExecutorService.awaitTermination( waitAsMilli, TimeUnit.MILLISECONDS ); if ( false == allHandlersCompleted ) { List<Runnable> stillRunningThreads = threadPoolExecutorService.shutdownNow(); if ( false == stillRunningThreads.isEmpty() ) { String errMsg = format( "%s shutdown before all handlers could complete\n%s handlers were queued for execution " + "but not yet started\n%s handlers were mid-execution", getClass().getSimpleName(), stillRunningThreads.size(), uncompletedHandlers.get() - stillRunningThreads.size() ); throw new OperationExecutorException( errMsg ); } } } catch ( Throwable e ) { throw new OperationExecutorException( "Error encountered while trying to shutdown", e ); } finally { shutdown.set( true ); } } @Override public long uncompletedOperationHandlerCount() { return uncompletedHandlers.get(); } private static class ThreadPoolExecutorWithAfterExecute extends ThreadPoolExecutor { private final ChildOperationGenerator childOperationGenerator; private final ChildOperationExecutor childOperationExecutor; private final OperationHandlerRunnableContextRetriever operationHandlerRunnableContextRetriever; private final ConcurrentErrorReporter errorReporter; public static ThreadPoolExecutorWithAfterExecute newFixedThreadPool( int threadCount, ThreadFactory threadFactory, AtomicLong uncompletedHandlers, int boundedQueueSize, ChildOperationGenerator childOperationGenerator, OperationHandlerRunnableContextRetriever operationHandlerRunnableContextInitializer, ConcurrentErrorReporter errorReporter ) { int corePoolSize = threadCount; int maximumPoolSize = threadCount; long keepAliveTime = 0; TimeUnit unit = TimeUnit.MILLISECONDS; BlockingQueue<Runnable> workQueue = DefaultQueues.newAlwaysBlockingBounded( boundedQueueSize ); return new ThreadPoolExecutorWithAfterExecute( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, uncompletedHandlers, childOperationGenerator, operationHandlerRunnableContextInitializer, errorReporter ); } private final AtomicLong uncompletedHandlers; private ThreadPoolExecutorWithAfterExecute( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, AtomicLong uncompletedHandlers, ChildOperationGenerator childOperationGenerator, OperationHandlerRunnableContextRetriever operationHandlerRunnableContextRetriever, ConcurrentErrorReporter errorReporter ) { super( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory ); this.childOperationExecutor = new ChildOperationExecutor(); this.uncompletedHandlers = uncompletedHandlers; this.childOperationGenerator = childOperationGenerator; this.operationHandlerRunnableContextRetriever = operationHandlerRunnableContextRetriever; this.errorReporter = errorReporter; } // Note, this occurs in same worker thread as beforeExecute() and run() @Override protected void afterExecute( Runnable runnable, Throwable throwable ) { super.afterExecute( runnable, throwable ); OperationHandlerRunnableContext operationHandlerRunnableContext = (OperationHandlerRunnableContext) runnable; try { childOperationExecutor.execute( childOperationGenerator, operationHandlerRunnableContext.operation(), operationHandlerRunnableContext.resultReporter().result(), operationHandlerRunnableContext.resultReporter().actualStartTimeAsMilli(), operationHandlerRunnableContext.resultReporter().runDurationAsNano(), operationHandlerRunnableContextRetriever ); } catch ( Throwable e ) { errorReporter.reportError( this, format( "Error retrieving handler\n%s", ConcurrentErrorReporter.stackTraceToString( e ) ) ); } finally { uncompletedHandlers.decrementAndGet(); operationHandlerRunnableContext.cleanup(); } } } }