package com.ldbc.driver; import com.ldbc.driver.runtime.ConcurrentErrorReporter; import com.ldbc.driver.runtime.coordination.LocalCompletionTimeWriter; import com.ldbc.driver.runtime.metrics.MetricsCollectionException; import com.ldbc.driver.runtime.metrics.MetricsService; import com.ldbc.driver.runtime.scheduling.Spinner; import com.ldbc.driver.runtime.scheduling.SpinnerCheck; import com.ldbc.driver.temporal.TimeSource; import stormpot.Poolable; import stormpot.Slot; import static java.lang.String.format; public class OperationHandlerRunnableContext implements Runnable, Poolable { // set by OperationHandlerRunnerFactory private Slot slot = null; // set by Db private DbConnectionState dbConnectionState = null; private OperationHandler operationHandler = null; // set by DependencyAndNonDependencyHandlersRetriever private TimeSource timeSource = null; private Spinner spinner = null; private Operation operation = null; private LocalCompletionTimeWriter localCompletionTimeWriter = null; private ConcurrentErrorReporter errorReporter = null; private MetricsService.MetricsServiceWriter metricsServiceWriter = null; // set by DependencyAndNonDependencyHandlersRetriever private SpinnerCheck beforeExecuteCheck = null; private boolean initialized = false; private ResultReporter.SimpleResultReporter resultReporter = null; public final void setSlot( Slot slot ) { this.slot = slot; } public final void init( TimeSource timeSource, Spinner spinner, Operation operation, LocalCompletionTimeWriter localCompletionTimeWriter, ConcurrentErrorReporter errorReporter, MetricsService metricsService ) throws OperationException { if ( initialized ) { throw new OperationException( format( "%s can not be initialized twice", getClass().getSimpleName() ) ); } if ( null == this.timeSource ) { this.timeSource = timeSource; this.spinner = spinner; this.errorReporter = errorReporter; if ( null == resultReporter ) { this.resultReporter = new ResultReporter.SimpleResultReporter( errorReporter ); } try { this.metricsServiceWriter = metricsService.getWriter(); } catch ( MetricsCollectionException e ) { throw new OperationException( "Error while retrieving metrics writer", e ); } } this.operation = operation; this.localCompletionTimeWriter = localCompletionTimeWriter; this.beforeExecuteCheck = Spinner.TRUE_CHECK; this.initialized = true; } public final void setOperationHandler( OperationHandler operationHandler ) { this.operationHandler = operationHandler; } public final void setDbConnectionState( DbConnectionState dbConnectionState ) { this.dbConnectionState = dbConnectionState; } public final void setBeforeExecuteCheck( SpinnerCheck check ) { beforeExecuteCheck = check; } public final Operation operation() { return operation; } public final OperationHandler operationHandler() { return operationHandler; } public final LocalCompletionTimeWriter localCompletionTimeWriter() { return localCompletionTimeWriter; } public final DbConnectionState dbConnectionState() { return dbConnectionState; } public final ResultReporter resultReporter() { return resultReporter; } /** * Internally calls the method executeOperation(operation) * and returns the associated OperationResultReport if execution was successful. * If execution is successful OperationResultReport metrics are also written to ConcurrentMetricsService. * If execution is unsuccessful the result is null, an error is written to ConcurrentErrorReporter, * and no metrics are written. * * @return an OperationResultReport if Operation execution was successful, otherwise null */ @Override public void run() { if ( false == initialized ) { errorReporter.reportError( this, "Handler was executed before being initialized" ); return; } try { if ( false == spinner.waitForScheduledStartTime( operation, beforeExecuteCheck ) ) { // TODO something more elaborate here? see comments in Spinner // TODO should probably report failed operation // Spinner result indicates operation should not be processed return; } resultReporter.setActualStartTimeAsMilli( timeSource.nowAsMilli() ); long startOfLatencyMeasurementAsNano = timeSource.nanoSnapshot(); operationHandler.executeOperation( operation, dbConnectionState, resultReporter ); long endOfLatencyMeasurementAsNano = timeSource.nanoSnapshot(); resultReporter.setRunDurationAsNano( endOfLatencyMeasurementAsNano - startOfLatencyMeasurementAsNano ); if ( null == resultReporter().result() ) { errorReporter.reportError( this, format( "Operation result is null\nOperation: %s", operation ) ); } else { localCompletionTimeWriter.submitLocalCompletedTime( operation.timeStamp() ); metricsServiceWriter.submitOperationResult( operation.type(), operation.scheduledStartTimeAsMilli(), resultReporter.actualStartTimeAsMilli(), resultReporter.runDurationAsNano(), resultReporter.resultCode(), operation.timeStamp() ); } } catch ( Throwable e ) { String errMsg = format( "Error encountered\n%s\n%s", operation, ConcurrentErrorReporter.stackTraceToString( e ) ); errorReporter.reportError( this, errMsg ); } } @Override public String toString() { return "OperationHandlerRunner\n" + " -> resultReporter=" + resultReporter + "\n" + " -> slot=" + slot + "\n" + " -> operation=" + operation + "\n" + " -> beforeExecuteCheck=" + beforeExecuteCheck + "\n" + " -> operationHandler=" + operationHandler + "\n" + " -> initialized=" + initialized; } public final void cleanup() { release(); } // Note, this should not really be public API, it is from the StormPot Poolable interface @Override public final void release() { initialized = false; if ( null != slot ) { slot.release( this ); } } }