package org.bitsofinfo.s3.worker; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.log4j.Logger; import org.bitsofinfo.s3.S3Util; import org.bitsofinfo.s3.control.CCMode; import org.bitsofinfo.s3.control.CCPayload; import org.bitsofinfo.s3.control.CCPayloadHandler; import org.bitsofinfo.s3.control.CCPayloadType; import org.bitsofinfo.s3.control.ControlChannel; import org.bitsofinfo.s3.master.ShutdownInfo; import org.bitsofinfo.s3.toc.FileCopyTOCPayloadHandler; import org.bitsofinfo.s3.toc.S3KeyCopyingTOCPayloadHandler; import org.bitsofinfo.s3.toc.TOCPayload; import org.bitsofinfo.s3.toc.TOCPayload.MODE; import org.bitsofinfo.s3.toc.TOCPayloadHandler; import org.bitsofinfo.s3.toc.TOCPayloadValidator; import org.bitsofinfo.s3.toc.TOCQueue; import org.bitsofinfo.s3.toc.ValidatingTOCPayloadHandler; import org.bitsofinfo.s3.util.CompressUtil; import org.bitsofinfo.s3.yas3fs.Yas3fsS3UploadMonitor; import org.springframework.util.StringUtils; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.StorageClass; import com.google.common.base.Splitter; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class Worker implements TOCPayloadHandler, CCPayloadHandler, Runnable { private static final Logger logger = Logger.getLogger(Worker.class); private List<TOCQueue> tocQueueConsumers = new ArrayList<TOCQueue>(); private ControlChannel controlChannel = null; private Thread selfMonitorThread = null; private Map<MODE,TOCPayloadHandler> mode2TOCHandlerMap = null; // if no messages received on TOCQueue in 5m declare as idle private long declareWorkerIdleAtMinLastMsgReceivedMS = 60000; private String sqsQueueName = null; private Integer totalConsumerThreads = null; private String awsAccessKey = null; private String awsSecretKey = null; private WorkerState myWorkerState = null; private Gson gson = new Gson(); private WriteMonitor writeMonitor = null; private WriteErrorMonitor writeErrorMonitor = null; private WriteBackoffMonitor writeBackoffMonitor = null; private Properties properties = null; private long initializedLastSentAtMS = -1; private long sendCurrentSummariesEveryMS = 60000; private long currentSummaryLastSentAtMS = -1; private boolean tocQueueConsumersArePaused = false; private int consumerThreadMinRequestsBeforeIdle = 60; private ShutdownInfo shutdownInfo = null; private S3Util s3util = null; private AmazonS3Client s3Client = null; public Worker(Properties props) { try { this.s3util = new S3Util(); this.properties = props; String snsControlTopicName = props.getProperty("aws.sns.control.topic.name"); this.awsAccessKey = props.getProperty("aws.access.key"); this.awsSecretKey = props.getProperty("aws.secret.key"); String userAccountPrincipalId = props.getProperty("aws.account.principal.id"); String userARN = props.getProperty("aws.user.arn"); this.sqsQueueName = props.getProperty("aws.sqs.queue.name"); this.totalConsumerThreads = Integer.valueOf(props.getProperty("worker.toc.consumer.threads.num")); this.consumerThreadMinRequestsBeforeIdle = Integer.valueOf(props.getProperty("worker.toc.consumer.threads.min.requests.before.idle")); this.s3Client = new AmazonS3Client(new BasicAWSCredentials(this.awsAccessKey, this.awsSecretKey)); mode2TOCHandlerMap = initTOCPayloadHandlers(props); // handle init command runInitOrDestroyCommand("initialize",props); // write monitor (optional) initWriteMonitor(props); // backoff monitor (optional) initWriteBackoffMonitor(props); // error monitor (optional) initWriteErrorMonitor(props); // spawn control channel controlChannel = new ControlChannel(false, awsAccessKey, awsSecretKey, snsControlTopicName, userAccountPrincipalId, userARN, this); // for tracking my info this.myWorkerState = new WorkerState( controlChannel.getMySourceIdentifier(), controlChannel.getMySourceIp()); // let master know we are initialized myWorkerState.setCurrentMode(CCMode.INITIALIZED); sendInitializedState(); // monitors our state... selfMonitorThread = new Thread(this); selfMonitorThread.start(); } catch(Exception e) { logger.error("Worker() unexpected error: " + e.getMessage(),e); destroy(); } } private void sendInitializedState() throws Exception{ this.initializedLastSentAtMS = System.currentTimeMillis(); this.controlChannel.send(false, CCPayloadType.WORKER_CURRENT_MODE, CCMode.INITIALIZED); } private void runInitOrDestroyCommand(String mode, Properties props) throws Exception { // Initialization command and environment vars String initCmd = props.getProperty("worker."+mode+".cmd"); String initCmdEnv = props.getProperty("worker."+mode+".cmd.env"); if (initCmd != null) { Map<String,String> env = null; if (initCmdEnv != null) { env = Splitter.on(",").withKeyValueSeparator("=").split(initCmdEnv); } // execute it! logger.debug("Running "+mode+" command: " + initCmd); CommandLine cmdLine = CommandLine.parse(initCmd); DefaultExecutor executor = new DefaultExecutor(); executor.execute(cmdLine, env); } } private void runPreValidateModeCommands(Properties props) throws Exception { // pre-validate command and environment vars String preValCmd = props.getProperty("worker.pre.validate.cmd"); String preValCmdEnv = props.getProperty("worker.pre.validate.cmd.env"); // note that either of these can reference %worker.initialize.cmd% and/or %worker.initialize.cmd.env% // so we will replace them if present preValCmd = StringUtils.replace(preValCmd, "%worker.initialize.cmd%", props.getProperty("worker.initialize.cmd")); preValCmdEnv = StringUtils.replace(preValCmdEnv, "%worker.initialize.cmd.env%", props.getProperty("worker.initialize.cmd.env")); if (preValCmd != null) { Map<String,String> env = null; if (preValCmdEnv != null) { env = Splitter.on(",").withKeyValueSeparator("=").split(preValCmdEnv); } // preValCmd can have multiple delimited by ; List<String> cmdsToRun = new ArrayList<String>(); if (preValCmd.indexOf(";") != -1) { cmdsToRun.addAll(Arrays.asList(preValCmd.split(";"))); } for (String cmd : cmdsToRun) { // execute it! logger.debug("Running pre.validate command: " + cmd); CommandLine cmdLine = CommandLine.parse(cmd); DefaultExecutor executor = new DefaultExecutor(); executor.execute(cmdLine, env); } } } private void initWriteMonitor(Properties props) throws Exception { String writeMonitorClass = props.getProperty("worker.write.complete.monitor.class"); if (writeMonitorClass != null) { logger.debug("Attempting to create WriteMonitor: " + writeMonitorClass); this.writeMonitor = (WriteMonitor)Class.forName(writeMonitorClass).newInstance(); if (writeMonitor instanceof Yas3fsS3UploadMonitor) { Yas3fsS3UploadMonitor m = (Yas3fsS3UploadMonitor)writeMonitor; m.setIsIdleWhenNZeroUploads(10); // the monitor must state 10 consecutive cycles of no s3 uploads to be "idle" m.setCheckEveryMS(Long.valueOf(props.getProperty("worker.write.complete.monitor.yas3fs.checkEveryMS"))); m.setPathToLogFile(props.getProperty("worker.write.complete.monitor.yas3fs.logFilePath")); } } } private void initWriteBackoffMonitor(Properties props) throws Exception { String writeBackoffMonitorClass = props.getProperty("worker.write.backoff.monitor.class"); if (writeBackoffMonitorClass != null) { logger.debug("Attempting to create WriteBackoffMonitor: " + writeBackoffMonitorClass); this.writeBackoffMonitor = (WriteBackoffMonitor)Class.forName(writeBackoffMonitorClass).newInstance(); if (writeBackoffMonitor instanceof Yas3fsS3UploadMonitor) { Yas3fsS3UploadMonitor m = (Yas3fsS3UploadMonitor)writeBackoffMonitor; m.setBackoffWhenMultipartUploads(Integer.valueOf(props.getProperty("worker.write.backoff.monitor.yas3fs.backoffWhenMultipartUploads"))); m.setBackoffWhenTotalHTTPSConns(Integer.valueOf(props.getProperty("worker.write.backoff.monitor.yas3fs.backoffWhenTotalHTTPSConns"))); m.setBackoffWhenTotalS3Uploads(Integer.valueOf(props.getProperty("worker.write.backoff.monitor.yas3fs.backoffWhenTotalS3Uploads"))); m.setCheckEveryMS(Long.valueOf(props.getProperty("worker.write.backoff.monitor.yas3fs.checkEveryMS"))); m.setPathToLogFile(props.getProperty("worker.write.backoff.monitor.yas3fs.logFilePath")); } } } private void initWriteErrorMonitor(Properties props) throws Exception { String writeErrorMonitorClass = props.getProperty("worker.write.error.monitor.class"); if (writeErrorMonitorClass != null) { logger.debug("Attempting to create WriteErrorMonitor: " + writeErrorMonitorClass); this.writeErrorMonitor = (WriteErrorMonitor)Class.forName(writeErrorMonitorClass).newInstance(); if (writeErrorMonitor instanceof Yas3fsS3UploadMonitor) { Yas3fsS3UploadMonitor m = (Yas3fsS3UploadMonitor)writeErrorMonitor; m.setCheckEveryMS(Long.valueOf(props.getProperty("worker.write.error.monitor.yas3fs.checkEveryMS"))); m.setPathToLogFile(props.getProperty("worker.write.error.monitor.yas3fs.logFilePath")); } } } public void startConsuming() { tocQueueConsumersArePaused = false; for (TOCQueue consumer : tocQueueConsumers) { consumer.start(); } } public void pauseConsuming() { tocQueueConsumersArePaused = true; for (TOCQueue consumer : tocQueueConsumers) { consumer.pauseConsuming(); } } public void resumeConsuming() { tocQueueConsumersArePaused = false; for (TOCQueue consumer : tocQueueConsumers) { consumer.resumeConsuming(); } } public void destroy() { // kill payload handlers for (TOCPayloadHandler handler : mode2TOCHandlerMap.values()) { try { handler.destroy(); } catch(Exception ignore){} } // upload logs try { this.s3util.uploadToS3(this.s3Client, this.shutdownInfo.s3LogBucketName, this.shutdownInfo.s3LogBucketFolderRoot, this.myWorkerState.getWorkerIP(), this.shutdownInfo.workerLogFilesToUpload); } catch(Exception ignore){} try { writeMonitor.destroy(); } catch(Exception ignore){} try { writeBackoffMonitor.destroy(); } catch(Exception ignore){} try { writeErrorMonitor.destroy(); } catch(Exception ignore){} try { controlChannel.destroy(); } catch(Exception ignore){} for (TOCQueue consumer : tocQueueConsumers) { consumer.stopConsuming(); } try { Thread.currentThread().sleep(30000); for (TOCQueue consumer : tocQueueConsumers) { consumer.destroy(); } } catch(Exception ignore){} try { runInitOrDestroyCommand("destroy",this.properties); } catch(Exception ignore){} } public void handlePayload(CCPayload payload) throws Exception { // we only care about master payloads and stuff from other than us if (!payload.fromMaster) { return; } // ignore messages targeted for someone other than ourself if (payload.onlyForHostIdOrIP != null && !payload.onlyForHostIdOrIP.equalsIgnoreCase(this.myWorkerState.getWorkerHostSourceId()) && !payload.onlyForHostIdOrIP.equalsIgnoreCase(this.myWorkerState.getWorkerIP())) { return; } logger.info("handlePayload() received CCPayload: fromMaster: " + payload.fromMaster + " sourceHostId:" + payload.sourceHostId + " sourceHostIp:" + payload.sourceHostIP + " onlyForHost:" + payload.onlyForHostIdOrIP + " type:" + payload.type + " value:" + payload.value); // the mode that the master reports we should switch to if (payload.type == CCPayloadType.MASTER_CURRENT_MODE) { CCMode masterMode = CCMode.valueOf(payload.value.toString()); // do we need to change our mode? if (myWorkerState.getCurrentMode() != masterMode) { // set it myWorkerState.setCurrentMode(masterMode); // if we are now WRITE/VALIDATE mode ensure we spawn our threads if (myWorkerState.getCurrentMode() == CCMode.WRITE || myWorkerState.getCurrentMode() == CCMode.VALIDATE) { if (this.tocQueueConsumers.size() == 0) { logger.debug("CCMode switched to mode "+myWorkerState.getCurrentMode()+ ": Worker spawing " + totalConsumerThreads + " separate TOCQueue consumer threads..."); for (int i=0; i<totalConsumerThreads; i++) { tocQueueConsumers.add(new TOCQueue(true, awsAccessKey, awsSecretKey, sqsQueueName, this)); } // start the queue threads this.startConsuming(); if (myWorkerState.getCurrentMode() == CCMode.WRITE) { // if we have an additional monitors configured start those too if (this.writeMonitor != null) { this.writeMonitor.start(); } if (this.writeBackoffMonitor != null) { this.writeBackoffMonitor.start(); } if (this.writeErrorMonitor != null) { this.writeErrorMonitor.start(); } } // we have existing threads, resume consumption } else { if (myWorkerState.getCurrentMode() == CCMode.VALIDATE) { // handle pre-validate commands runPreValidateModeCommands(this.properties); } // resume! this.resumeConsuming(); } } // if now in validate mode, destroy the write monitors... if (myWorkerState.getCurrentMode() == CCMode.VALIDATE) { if (this.writeMonitor != null) { this.writeMonitor.destroy(); } if (this.writeBackoffMonitor != null) { this.writeBackoffMonitor.destroy(); } if (this.writeErrorMonitor != null) { this.writeErrorMonitor.destroy(); } } // if now in REPORT_ERRORS mode... if (myWorkerState.getCurrentMode() == CCMode.REPORT_ERRORS) { // ensure we are paused consuming.. this.pauseConsuming(); // build report... ErrorReport errorReport = new ErrorReport(); errorReport.ip = myWorkerState.getWorkerIP(); errorReport.id = myWorkerState.getWorkerHostSourceId(); errorReport.failedValidates = myWorkerState.getTocPathValidateFailures(); errorReport.failedWrites = myWorkerState.getTocPathsWriteFailures(); errorReport.errorsTolerated = myWorkerState.getTocPathsErrorsTolerated(); errorReport.writeMonitorErrors = myWorkerState.getWriteMonitorErrors(); errorReport.failedPostWriteLocalValidates = myWorkerState.getTocPathsPostWriteLocalValidateFailures(); // convert to json String errorReportJson = new GsonBuilder().setPrettyPrinting().create().toJson(errorReport); // compress... String compressedPayload = new String(CompressUtil.compressAndB64EncodeUTF8Bytes(errorReportJson.getBytes("UTF-8"))); // send to control channel this.controlChannel.send(false, CCPayloadType.WORKER_ERROR_REPORT_DETAILS, compressedPayload); } } } // if the master tells us to shutdown if (payload.type == CCPayloadType.CMD_WORKER_SHUTDOWN) { try { this.shutdownInfo = gson.fromJson(payload.value.toString(), ShutdownInfo.class); } catch(Exception e) { logger.error("CMD_WORKER_SHUTDOWN recieved error attempting to parse payload value into ShutdownInfo: " + e.getMessage(),e); } System.exit(0); // this will trigger shutdown hook } } public void handlePayload(TOCPayload payload) throws Exception { logger.info("handlePayload() received TOCPayload: mode: "+payload.mode + " filePath:" + payload.tocInfo.getPath()); TOCPayloadHandler handler = this.mode2TOCHandlerMap.get(payload.mode); if (handler == null) { throw new Exception("Cannot handle payload: " + payload.mode + " no TOCPayloadHandler configured for this MODE!"); } handler.handlePayload(payload,this.myWorkerState); } private Map<MODE,TOCPayloadHandler> initTOCPayloadHandlers(Properties props) throws Exception { String writeClazz = props.getProperty("tocPayloadHandler.write.class"); String validateClazz = props.getProperty("tocPayloadHandler.validate.class"); Map<MODE, TOCPayloadHandler> map = new HashMap<MODE,TOCPayloadHandler>(); map.put(MODE.WRITE, initTOCPayloadHandler(writeClazz,props)); map.put(MODE.VALIDATE, initTOCPayloadHandler(validateClazz,props)); return map; } private TOCPayloadHandler initTOCPayloadHandler(String className, Properties props) throws Exception { TOCPayloadHandler handler = (TOCPayloadHandler)Class.forName(className).newInstance(); /** * FileCopyTOCPayloadHandler */ if (handler instanceof FileCopyTOCPayloadHandler) { FileCopyTOCPayloadHandler fcHandler = (FileCopyTOCPayloadHandler)handler; fcHandler.setSourceDirectoryRootPath(props.getProperty("tocPayloadHandler.source.dir.root")); fcHandler.setTargetDirectoryRootPath(props.getProperty("tocPayloadHandler.target.dir.root")); fcHandler.setUseRsync(Boolean.valueOf(props.getProperty("tocPayloadHandler.write.use.rsync"))); fcHandler.setRetries(Integer.valueOf(props.getProperty("tocPayloadHandler.write.retries"))); fcHandler.setRetriesSleepMS(Long.valueOf(props.getProperty("tocPayloadHandler.write.retries.sleep.ms"))); if (props.getProperty("tocPayloadHandler.write.post.success.validate.local.dir") != null) { fcHandler.setPostWriteLocalValidateRootDir(((String)props.getProperty("tocPayloadHandler.write.post.success.validate.local.dir"))); if (props.getProperty("tocPayloadHandler.write.post.success.validate.logfile") == null) { throw new Exception("tocPayloadHandler.write.post.success.validate.logfile must be specified if tocPayloadHandler.write.post.success.validate.local.dir is enabled"); } fcHandler.setPostWriteLocalValidateLogFile( ((String)props.getProperty("tocPayloadHandler.write.post.success.validate.logfile"))); fcHandler.setPostWriteLocalValidateSkipDirectories( (Boolean.valueOf(props.getProperty("tocPayloadHandler.write.post.success.validate.skipDirectories")))); // set validator (note its not configured for S3! only local checks) fcHandler.setTocPayloadValidator(new TOCPayloadValidator()); } if (props.getProperty("tocPayloadHandler.write.rsync.tolerable.error.regex") != null) { fcHandler.setRsyncTolerableErrorsRegex((String)props.getProperty("tocPayloadHandler.write.rsync.tolerable.error.regex")); } if (fcHandler.isUseRsync()) { fcHandler.setRsyncOptions(props.getProperty("tocPayloadHandler.write.rsync.options")); } String chmod = props.getProperty("tocPayloadHandler.write.chmod"); if (chmod != null) { boolean dirsOnly = Boolean.valueOf(props.getProperty("tocPayloadHandler.write.chmod.dirsOnly")); fcHandler.setChmod(chmod); fcHandler.setChmodDirsOnly(dirsOnly); } String chown = props.getProperty("tocPayloadHandler.write.chown"); if (chown != null) { boolean dirsOnly = Boolean.valueOf(props.getProperty("tocPayloadHandler.write.chown.dirsOnly")); fcHandler.setChown(chown); fcHandler.setChownDirsOnly(dirsOnly); } return handler; } /** * ValidatingTOCPayloadHandler */ if (handler instanceof ValidatingTOCPayloadHandler) { ValidatingTOCPayloadHandler vhandler = (ValidatingTOCPayloadHandler)handler; vhandler.setTargetDirectoryRootPath(props.getProperty("tocPayloadHandler.target.dir.root")); vhandler.setValidateMode( org.bitsofinfo.s3.toc.ValidatingTOCPayloadHandler.MODE.valueOf( props.getProperty("tocPayloadHandler.validate.mode"))); vhandler.setS3BucketName(props.getProperty("tocPayloadHandler.validate.s3.bucketName")); vhandler.setS3Client(new AmazonS3Client(new BasicAWSCredentials(this.awsAccessKey, this.awsSecretKey))); return vhandler; } /** * S3KeyCopyingTOCPayloadHandler */ if (handler instanceof S3KeyCopyingTOCPayloadHandler) { S3KeyCopyingTOCPayloadHandler fcHandler = (S3KeyCopyingTOCPayloadHandler)handler; fcHandler.setS3Client(this.s3Client); fcHandler.setSourceS3BucketName(props.getProperty("tocPayloadHandler.write.s3keyCopy.sourceS3BucketName").toString()); fcHandler.setTargetS3BucketName(props.getProperty("tocPayloadHandler.write.s3keyCopy.targetS3BucketName").toString()); fcHandler.setEnableServerSideEncryption(Boolean.valueOf(props.getProperty("tocPayloadHandler.write.s3keyCopy.enableServerSideEncryption"))); fcHandler.setStorageClass(StorageClass.valueOf(props.getProperty("tocPayloadHandler.write.s3keyCopy.storageClass"))); return fcHandler; } throw new Exception("initTOCPayloadHandler() invalid tocPayloadHandler.class " + className); } private String getResultsSummaryAsJSON(MODE mode) { if (mode == MODE.WRITE) { ResultSummary writeSummary = new ResultSummary(this.tocQueueConsumersArePaused, myWorkerState.getTotalWritesOK(), myWorkerState.getTotalWritesFailed(), myWorkerState.getTotalErrorsTolerated(), myWorkerState.getTotalWriteMonitorErrors(), myWorkerState.getTotalPostWriteLocalValidateFailures(), myWorkerState.getTotalWritesProcessed()); return gson.toJson(writeSummary); } else if (mode == MODE.VALIDATE) { ResultSummary validateSummary = new ResultSummary(this.tocQueueConsumersArePaused, myWorkerState.getTotalValidatesOK(), myWorkerState.getTotalValidatesFailed(), myWorkerState.getTotalErrorsTolerated(), myWorkerState.getTotalWriteMonitorErrors(), myWorkerState.getTotalPostWriteLocalValidateFailures(), myWorkerState.getTotalValidationsProcessed()); return gson.toJson(validateSummary); } throw new RuntimeException("getResultsSummaryAsJSON() called with invalid MODE: " + mode); } public void run() { boolean running = true; // Here we monitor our WorkerState // to determine where we are at while (running) { try { Thread.currentThread().sleep(20000); // if just in Initialized/Idle state do nothing. if (this.myWorkerState.getCurrentMode() == CCMode.INITIALIZED || this.myWorkerState.getCurrentMode() == CCMode.IDLE ) { // if we are just in INITIALIZED state, and still are after 30s, resend it // as we have seen worker initializd messages not get delivered... long now = System.currentTimeMillis(); if (this.myWorkerState.getCurrentMode() == CCMode.INITIALIZED && this.initializedLastSentAtMS > 0 && (now - this.initializedLastSentAtMS > 60000)) { logger.debug("Resending INITIALIZED state to master......"); this.sendInitializedState(); } continue; } /** * WRITES DONE? */ if (this.myWorkerState.getCurrentMode() == CCMode.WRITE) { // determine TOCQueue threads who have been idle long enough to proceed to a state change assumption int threadsThatQualify = getIdleTOCQueueThreads(); // ok all threads are created AND idle, send our summary...as we can only assume we are done if (this.tocQueueConsumers.size() == this.totalConsumerThreads && threadsThatQualify >= this.tocQueueConsumers.size()) { // if the write monitor IS configured and states we CANNOT proceed... // then just exit/continue... if (writeMonitor != null && !writeMonitor.writesAreComplete()) { continue; } // final of update write error monitor if exists if (this.writeErrorMonitor != null) { this.myWorkerState.addWriteMonitorErrors( this.writeErrorMonitor.getWriteErrors()); } String asJson = getResultsSummaryAsJSON(MODE.WRITE); // pause this.pauseConsuming(); // send out our summary this.controlChannel.send(false, CCPayloadType.WORKER_WRITES_FINISHED_SUMMARY, asJson); // state we are IDLE this.myWorkerState.setCurrentMode(CCMode.IDLE); this.controlChannel.send(false, CCPayloadType.WORKER_CURRENT_MODE, CCMode.IDLE); // not finished but lets send a CURRENT_SUMMARY, if necessary } else { // update write error monitor if exists if (this.writeErrorMonitor != null) { this.myWorkerState.addWriteMonitorErrors( this.writeErrorMonitor.getWriteErrors()); } // build/send the summary long now = System.currentTimeMillis();; if ((now - this.currentSummaryLastSentAtMS) > this.sendCurrentSummariesEveryMS) { // send out our summary this.currentSummaryLastSentAtMS = now; String asJson = getResultsSummaryAsJSON(MODE.WRITE); this.controlChannel.send(false, CCPayloadType.WORKER_WRITES_CURRENT_SUMMARY, asJson); } // should the TOCQueue thread consumers backoff? if (this.writeBackoffMonitor != null) { if (this.writeBackoffMonitor.writesShouldBackoff()) { if (!this.tocQueueConsumersArePaused) { logger.debug("WriteBackoffMonitor states we should BACKOFF... pausing TOCQueue consumers"); this.pauseConsuming(); } } else { if (this.tocQueueConsumersArePaused) { logger.debug("WriteBackoffMonitor states we can RESUME... resuming TOCQueue consumers"); this.resumeConsuming(); } } } } } /** * VALIDATES DONE? */ if (this.myWorkerState.getCurrentMode() == CCMode.VALIDATE) { // determine TOCQueue threads who have been idle long enough to proceed to a state change assumption int threadsThatQualify = getIdleTOCQueueThreads(); // ok all threads are created AND idle, send our summary...as we can only assume we are done if (this.tocQueueConsumers.size() == this.totalConsumerThreads && threadsThatQualify >= this.tocQueueConsumers.size()) { String asJson = getResultsSummaryAsJSON(MODE.VALIDATE); // pause this.pauseConsuming(); // send out our summary this.controlChannel.send(false, CCPayloadType.WORKER_VALIDATIONS_FINISHED_SUMMARY, asJson); // state we are IDLE this.myWorkerState.setCurrentMode(CCMode.IDLE); this.controlChannel.send(false, CCPayloadType.WORKER_CURRENT_MODE, CCMode.IDLE); // not finished but lets send a CURRENT_SUMMARY } else { long now = System.currentTimeMillis();; if ((now - this.currentSummaryLastSentAtMS) > this.sendCurrentSummariesEveryMS) { // send out our summary this.currentSummaryLastSentAtMS = now; String asJson = getResultsSummaryAsJSON(MODE.VALIDATE); this.controlChannel.send(false, CCPayloadType.WORKER_VALIDATIONS_CURRENT_SUMMARY, asJson); } } } } catch(Exception e) { logger.error("run() unexpected error: " + e.getMessage(),e); } } } private int getIdleTOCQueueThreads() { int threadsThatQualify = 0; for (TOCQueue tocQueue : this.tocQueueConsumers) { // not even connected/ready yet, it has not even // made at least 'consumerThreadMinRequestsBeforeIdle' requests to get messages if (tocQueue.getTotalMessageRequestsMade() < this.consumerThreadMinRequestsBeforeIdle) { continue; } long lastMsgReceivedAtMS = (System.currentTimeMillis() - tocQueue.getLastMsgReceivedTimeMS()); if (!tocQueue.isPaused() && !tocQueue.isCurrentlyProcessingMessage() && ( // has processed at least one message and has not done another past our allowed idle time (tocQueue.getTotalMessagesProcessed() > 0 && lastMsgReceivedAtMS >= this.declareWorkerIdleAtMinLastMsgReceivedMS) || // or just has never processed a message, this can happen if too many consumer threads // some won't process any messages (tocQueue.getTotalMessagesProcessed() == 0) )) { threadsThatQualify++; } } return threadsThatQualify; } public void handlePayload(TOCPayload payload,WorkerState workerState) throws Exception { this.handlePayload(payload); } }