All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jabb.txsdp.DefaultTransactionalStreamDataBatchProcessing Maven / Gradle / Ivy

/**
 * 
 */
package net.sf.jabb.txsdp;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import net.sf.jabb.dstream.ReceiveStatus;
import net.sf.jabb.dstream.StreamDataSupplier;
import net.sf.jabb.dstream.StreamDataSupplierWithIdAndPositionRange;
import net.sf.jabb.dstream.StreamDataSupplierWithIdAndRange;
import net.sf.jabb.dstream.ex.DataStreamInfrastructureException;
import net.sf.jabb.seqtx.ReadOnlySequentialTransaction;
import net.sf.jabb.seqtx.SequentialTransaction;
import net.sf.jabb.seqtx.SequentialTransactionsCoordinator;
import net.sf.jabb.seqtx.SequentialTransactionsCoordinator.TransactionCounts;
import net.sf.jabb.seqtx.ex.DuplicatedTransactionIdException;
import net.sf.jabb.seqtx.ex.TransactionStorageInfrastructureException;
import net.sf.jabb.util.parallel.WaitStrategy;
import net.sf.jabb.util.text.DurationFormatter;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Transactional batch processing of stream data.
 * 
 * @author James Hu
 * 
 * @param  type of the data/message/event
 *
 */
public class DefaultTransactionalStreamDataBatchProcessing implements TransactionalStreamDataBatchProcessing {
	static final Logger logger = LoggerFactory.getLogger(DefaultTransactionalStreamDataBatchProcessing.class);
	
	protected String id;
	protected FlexibleBatchProcessor batchProcessor;
	protected List> suppliers;
	protected SequentialTransactionsCoordinator txCoordinator;
	
	protected Options processorOptions;
	
	protected Map processors = new ConcurrentHashMap<>();
	
	public List> getSuppliers() {
		return suppliers;
	}

	@Override
	public void setSuppliers(List> suppliers) {
		this.suppliers = suppliers;
	}

	/**
	 * Get the transaction coordinator
	 * @return the txCoordinator
	 */
	public SequentialTransactionsCoordinator getTransactionCoordinator() {
		return txCoordinator;
	}

	/**
	 * Constructor
	 * @param id				ID of this processing
	 * @param processorOptions	options
	 * @param txCoordinator		transactions coordinator
	 * @param processor			batch processor
	 * @param suppliers			stream data suppliers
	 */
	public DefaultTransactionalStreamDataBatchProcessing(String id, Options processorOptions, SequentialTransactionsCoordinator txCoordinator, 
			FlexibleBatchProcessor processor, List> suppliers){
		this.id = id;
		this.processorOptions = new Options(processorOptions);
		this.txCoordinator = txCoordinator;
		this.batchProcessor = processor;
		this.suppliers = new ArrayList<>();
		this.suppliers.addAll(suppliers);
		
	}
	
	/**
	 * Constructor
	 * @param id				ID of this processing
	 * @param processorOptions	options
	 * @param txCoordinator		transactions coordinator
	 * @param processor			batch processor
	 * @param suppliers			stream data suppliers
	 */
	@SafeVarargs
	public DefaultTransactionalStreamDataBatchProcessing(String id, Options processorOptions, SequentialTransactionsCoordinator txCoordinator, 
			FlexibleBatchProcessor processor, StreamDataSupplierWithIdAndRange... suppliers){
		this(id, processorOptions, txCoordinator, processor, Arrays.asList(suppliers));
	}
	
	/**
	 * Constructor
	 * @param id				ID of this processing
	 * @param processorOptions	options
	 * @param txCoordinator		transactions coordinator
	 * @param processor			simple batch processor
	 * @param maxBatchSize		the maximum number of data items to be fetched and processed in a batch. 
	 * 							The actual numbers of data items in a batch will be smaller than or equal to this number.
	 * @param receiveTimeout	total duration allowed for receiving all the data items in a closed range batch/transaction.
	 * 							When fetching data for retrying a previously failed transaction which has a closed data range, 
	 * 							this argument decides how long the processors wait for all the data to be fetched. 
	 * 							If the full range of data cannot be fetched within this duration, 
	 * 							the batch processing transaction will abort. Normally this duration should be long enough to make 
	 * 							sure that data items as many as maxBatchSize can always be successfully fetched.
	 * @param receiveTimeoutForOpenRange	total duration allowed for receiving all the data items in a open range batch/transaction.
	 * 							When fetching data for a new transaction which has an open data range (from a specific time to the infinity), 
	 * 							this argument decides how long the processors wait for a batch of data to be fetched.
	 * 							The longer this duration is, the more data will probably be fetched for a batch, and the larger the 
	 * 							batch is and the longer the end-to-end processing delay is. 
	 * 							Normally it should be shorter than receiveTimeout
	 * @param suppliers			stream data suppliers
	 */
	public DefaultTransactionalStreamDataBatchProcessing(String id, Options processorOptions, SequentialTransactionsCoordinator txCoordinator, 
			SimpleBatchProcessor processor, int maxBatchSize, Duration receiveTimeout, Duration receiveTimeoutForOpenRange,
			List> suppliers){
		this(id, processorOptions, txCoordinator, 
				new SimpleFlexibleBatchProcessor(processor, maxBatchSize, receiveTimeout, receiveTimeoutForOpenRange), 
				suppliers);
	}
	
	/**
	 * Constructor
	 * @param id				ID of this processing
	 * @param processorOptions	options
	 * @param txCoordinator		transactions coordinator
	 * @param processor			simple batch processor
	 * @param maxBatchSize		the maximum number of data items to be fetched and processed in a batch. 
	 * 							The actual numbers of data items in a batch will be smaller than or equal to this number.
	 * @param receiveTimeout	total duration allowed for receiving all the data items in a closed range batch/transaction.
	 * 							When fetching data for retrying a previously failed transaction which has a closed data range, 
	 * 							this argument decides how long the processors wait for all the data to be fetched. 
	 * 							If the full range of data cannot be fetched within this duration, 
	 * 							the batch processing transaction will abort. Normally this duration should be long enough to make 
	 * 							sure that data items as many as maxBatchSize can always be successfully fetched.
	 * @param receiveTimeoutForOpenRange	total duration allowed for receiving all the data items in a open range batch/transaction.
	 * 							When fetching data for a new transaction which has an open data range (from a specific time to the infinity), 
	 * 							this argument decides how long the processors wait for a batch of data to be fetched.
	 * 							The longer this duration is, the more data will probably be fetched for a batch, and the larger the 
	 * 							batch is and the longer the end-to-end processing delay is. 
	 * 							Normally it should be shorter than receiveTimeout
	 * @param suppliers			stream data suppliers
	 */
	@SafeVarargs
	public DefaultTransactionalStreamDataBatchProcessing(String id, Options processorOptions, SequentialTransactionsCoordinator txCoordinator, 
			SimpleBatchProcessor processor, int maxBatchSize, Duration receiveTimeout, Duration receiveTimeoutForOpenRange,
			StreamDataSupplierWithIdAndPositionRange... suppliers){
		this(id, processorOptions, txCoordinator, processor, maxBatchSize, receiveTimeout, receiveTimeoutForOpenRange, Arrays.asList(suppliers));
	}
	
	@Override
	public Runnable createProcessor(String processorId){
		Validate.notNull(processorId, "Processor id cannot be null");
		Processor runnable;
		if (processors.containsKey(processorId)){
			throw new IllegalArgumentException("Another runnable with the same processor ID already exists: " + processorId);
		}
		runnable = new Processor(processorId);
		processors.put(processorId, runnable);
		return runnable;
	}
	
	/**
	 * Start processing. Once started, the processing can later be paused or stopped.
	 * @param runnable the runnable to be started
	 */
	protected void start(Processor runnable){
		if (runnable.state.compareAndSet(State.READY, State.RUNNING)){
			return;
		}
		if (runnable.state.compareAndSet(State.PAUSED, State.RUNNING)){
			return;
		}
		throw new IllegalStateException("Cannot start when in " + runnable.state.get() + " state: " + runnable.processorId);
	}
	
	/**
	 * Pause processing. Once paused, the processing can later be started or stopped.
	 * @param runnable the runnable to be paused
	 */
	protected void pause(Processor runnable){
		if (runnable.state.compareAndSet(State.RUNNING, State.PAUSING)){
			return;
		}
		throw new IllegalStateException("Cannot pause when not in running state: " + runnable.processorId);
	}
	
	/**
	 * Stop processing. Once stopped, the processing cannot be restarted.
	 * @param runnable the runnable to be stopped
	 */
	protected void stop(Processor runnable){
		if (runnable.state.compareAndSet(State.RUNNING, State.STOPPING)){
			return;
		}
		if (runnable.state.compareAndSet(State.PAUSED, State.STOPPING)){
			return;
		}
		if (runnable.state.compareAndSet(State.PAUSING, State.STOPPING)){
			return;
		}
	}

	@Override
	public void start(String processorId){
		Processor runnable = processors.get(processorId);
		Validate.notNull(runnable, "There is no processor with the id: " + processorId);

		start(runnable);
	}
	
	@Override
	public void pause(String processorId){
		Processor runnable = processors.get(processorId);
		Validate.notNull(runnable, "There is no processor with the id: " + processorId);

		pause(runnable);
	}
	
	@Override
	public void stop(String processorId){
		Processor runnable = processors.get(processorId);
		Validate.notNull(runnable, "There is no processor with the id: " + processorId);

		stop(runnable);
	}
	
	@Override
	public void remove(String processorId) {
		processors.remove(processorId);
	}

	@Override
	public void removeUnused() {
		List stopped = getProcessorStatus().entrySet().stream()
				.filter(entry->entry.getValue().getState().isUnused())
				.map(entry->entry.getKey())
				.collect(Collectors.toList());
		processors.keySet().removeAll(stopped);
	}
	


	/**
	 * Start processing. Once started, the processing can later be paused or stopped.
	 */
	@Override
	public void startAll(){
		for (Processor runnable: processors.values()){
			start(runnable);
		}
	}
	
	/**
	 * Pause processing. Once paused, the processing can later be started or stopped.
	 */
	@Override
	public void pauseAll(){
		for (Processor runnable: processors.values()){
			pause(runnable);
		}
	}
	
	/**
	 * Stop processing. Once stopped, the processing cannot be restarted.
	 */
	@Override
	public void stopAll(){
		for (Processor runnable: processors.values()){
			stop(runnable);
		}
	}
	
	protected String seriesId(StreamDataSupplierWithIdAndRange supplierWithId){
		if (id == null || id.length() == 0){
			return supplierWithId.getId().replace('/', '_');
		}else{
			return id + "_" + supplierWithId.getId().replace('/', '_');
		}
	}
	
	class Processor implements Runnable{
		protected AtomicReference state = new AtomicReference<>(State.READY);
		private String processorId;
		
		Processor(String processorId){
			this.processorId = processorId;
		}
		
		private void await(){
			long millis = processorOptions.getTransactionAcquisitionDelay().toMillis();
			if (millis > 0){
				WaitStrategy waitStrategy = processorOptions.getWaitStrategy();
				try{
					waitStrategy.await(millis);
				}catch(InterruptedException ie){
					waitStrategy.handleInterruptedException(ie);
				}
			}
		}
		
		private boolean allProcessed(boolean[] outOfRangeReached){
			for (boolean b: outOfRangeReached){
				if (!b){
					return false;
				}
			}
			return true;
		}
		
		@Override
		public void run() {
			logger.debug("[{}] Start running: {}", processorId, state);
			
			List> localSuppliers = new ArrayList<>(suppliers.size());
			localSuppliers.addAll(suppliers);

			boolean[] outOfRangeReached = new boolean[localSuppliers.size()];
			// make sure we start from a random partition, and then do a round robin afterwards
			Random random = new Random();
			int partition = random.nextInt(outOfRangeReached.length);
			
			// reuse these data structures in the thread
			ProcessingContextImpl context = new ProcessingContextImpl(txCoordinator); 
			boolean sticky = false;
			
			while(!state.compareAndSet(State.STOPPING, State.STOPPED)){
				if (!localSuppliers.equals(suppliers)){	// if suppliers changed
					localSuppliers.clear();
					localSuppliers.addAll(suppliers);
					outOfRangeReached = new boolean[localSuppliers.size()];
					partition = partition % outOfRangeReached.length;
				}
				
				while(state.get() == State.RUNNING){
					if (allProcessed(outOfRangeReached)){
						state.set(State.FINISHED);
						break;
					}
					
					long startTime = System.currentTimeMillis();
					int attempts = 0;
					SequentialTransaction transaction = null;
					String seriesId = null;
					StreamDataSupplierWithIdAndRange supplierWithIdAndRange = null;
					StreamDataSupplier supplier = null;
					
					// if sticky is true, we don't need to get a transaction skeleton from the coordinator. and we don't change to another partition
					boolean newSticky = processorOptions.stickyMode == Options.STICKY_WHEN_OPEN_RANGE_SUCCEEDED && context.isOpenRangeSuccessfullyClosed ||
							processorOptions.stickyMode == Options.STICKY_WHEN_OPEN_RANGE_SUCCEEDED_OR_NO_DATA && (context.isOpenRangeSuccessfullyClosed || context.isOpenRangeAbortedBecauseNothingReceived);
					if (sticky != newSticky){
						sticky = newSticky;
						logger.debug("Processor '{}' {}stick on '{}'", processorId, sticky ? "" : "no longer ", seriesId(localSuppliers.get(partition)));
					}
					if (!sticky){
						partition = (partition+1) % outOfRangeReached.length;
					}

					try{
						while (state.get() == State.RUNNING && !outOfRangeReached[partition]){
							supplierWithIdAndRange = localSuppliers.get(partition);
							supplier = supplierWithIdAndRange.getSupplier();
							seriesId = seriesId(supplierWithIdAndRange);
							if (sticky){
								transaction = context.transaction;	// assume that we can get start position directly from the last transaction that had just finished
								break;
							}
							attempts++;
							try {
								transaction = txCoordinator.startTransaction(seriesId, processorId, 
										processorOptions.getInitialTransactionTimeoutDuration(), 
										processorOptions.getMaxInProgressTransactions(), processorOptions.getMaxRetringTransactions());
							} catch (Exception e) {
								logger.warn("[{}] Processor {} startTransaction(...) failed", seriesId, processorId, e);
							}
							if (transaction != null){
								break;
							}
							await();
							partition = (partition+1) % outOfRangeReached.length;
						}
						
						// got a skeleton, with matching seriesId
						while (transaction != null && (!transaction.hasStarted() || sticky && transaction == context.transaction) &&  state.get() == State.RUNNING){
							String previousTransactionId;
							String previousEndPosition;
							String startPosition = null;
							if (sticky && transaction == context.transaction && context.isOpenRangeAbortedBecauseNothingReceived){
								previousTransactionId = context.previousTransactionPreviousTransactionId;
								previousEndPosition = context.previousTransactionEndPosition;
								startPosition = transaction.getStartPosition();		// just retry last one, the transaction object is from context
							}else{
								previousTransactionId = transaction.getTransactionId();
								if (sticky && transaction == context.transaction && context.isOpenRangeSuccessfullyClosed){
									previousEndPosition = transaction.getEndPosition();		// the transaction object is from context 
								}else{	// non sticky or another skeleton was returned from startTransaction(...)
									previousEndPosition = transaction.getStartPosition();	// the transaction object is returned by the coordinator
								}
								if (previousEndPosition == null){
									startPosition = "";	// the beginning of the range
								}else{
									startPosition = supplier.nextStartPosition(previousEndPosition);
								}
							}
							transaction.setTransactionId(null);
							
							transaction.setStartPosition(startPosition);
							transaction.setEndPositionNull();	// for an open range transaction
							transaction.setTimeout(processorOptions.getInitialTransactionTimeoutDuration());
							attempts++;
							context.previousTransactionPreviousTransactionId = previousTransactionId;
							context.previousTransactionEndPosition = previousEndPosition;
							transaction = txCoordinator.startTransaction(seriesId, previousTransactionId, previousEndPosition, transaction, 
									processorOptions.getMaxInProgressTransactions(), processorOptions.getMaxRetringTransactions());
							if (logger.isDebugEnabled()){
								logger.debug("[{}] Processor {} tried to start transaction: sticky={}, previousTransactionId={}, previousEndPosition={}, startPosition={}, returnedTransactionId={}, returnedTransactionHasStarted={}", 
										seriesId, processorId, sticky, previousTransactionId, previousEndPosition, startPosition, transaction == null ? null : transaction.getTransactionId(), transaction == null ? null : transaction.hasStarted());
							}
						}
					}catch(TransactionStorageInfrastructureException e){
						logger.debug("[{}] In transaction storage infrastructure error happened", seriesId, e);
						transaction = null;
					}catch(DuplicatedTransactionIdException e){
						logger.warn("[{}] Transaction ID is duplicated: " + transaction.getTransactionId(), seriesId, e);
						transaction = null;
					}catch(Exception e){
						logger.error("[{}] Error happened", seriesId, e);
						transaction = null;
					}finally{
						// clear those flags so that the next round will start from a new state 
						// otherwise sometimes the processor may stick to the same partition forever
						context.isOpenRangeAbortedBecauseNothingReceived = false;
						context.isOpenRangeSuccessfullyClosed = false;
						context.isOutOfRangeMessageReached = false;
					}
					
					if (transaction != null && transaction.hasStarted() && state.get() == State.RUNNING){
						if (logger.isDebugEnabled()){
							logger.debug("[{}] Processor {} got a {} transaction {} ({}-{}] after {} attempts: {}", 
									seriesId,
									processorId,
									(transaction.getAttempts() == 1 ? "new" : "failed"),
									transaction.getTransactionId(),
									transaction.getStartPosition(), transaction.getEndPosition(),
									attempts,
									DurationFormatter.formatSince(startTime));
						}
						doTransaction(context.withSeriesId(seriesId).withTransaction(transaction), supplierWithIdAndRange);
						if (context.isOutOfRangeMessageReached){
							String finishedPosition;
							try {
								finishedPosition = SequentialTransactionsCoordinator.getFinishedPosition(txCoordinator.getRecentTransactions(seriesId));
								if (finishedPosition != null && 
										(finishedPosition.equals(transaction.getStartPosition()) || finishedPosition.equals(transaction.getEndPosition()))){
									outOfRangeReached[partition] = true;
								}
							} catch (Exception e) {
								logger.warn("[{}] Processor {} failed to get recent transactions", seriesId, processorId, e);
							}
						}
					}else{ // can't get a transaction
						if (state.get() == State.RUNNING && !outOfRangeReached[partition]){
							await();
						}
					}
				}  // state.get() == State.RUNNING
				state.compareAndSet(State.PAUSING, State.PAUSED);
				if (allProcessed(outOfRangeReached)){
					state.set(State.FINISHED);
					break;
				}
			} // state.compareAndSet(State.STOPPING, State.STOPPED)
			
			logger.debug("[{}] Finish running: {}", processorId, state);
		}
		

		/**
		 * perform a batch processing transaction
		 * @param context	the processing context which will be updated in this method
		 * @param supplierWithIdAndRange	the stream data supplier
		 */
		protected void doTransaction(ProcessingContextImpl context, StreamDataSupplierWithIdAndRange supplierWithIdAndRange) {
			String seriesId = context.seriesId;
			SequentialTransaction transaction = context.transaction;
			
			Boolean succeeded = false;
			String fetchedLastPosition = null;
			ReceiveStatus receiveStatus = null;
			
			boolean isInitiallyOpenRange = transaction.getEndPosition() == null;
			boolean isOpenRangeClosed = false;
			boolean isProcessingFailed = false;
			try{
				if (!batchProcessor.initialize(context)){
					throw new Exception("Unable to initilize processor");
				}
				long receiveTimeoutMillis = batchProcessor.receive(context, null);	// keep it for logging
				receiveStatus = supplierWithIdAndRange.receiveInRange(msg->{
						long remaining = batchProcessor.receive(context, msg);
						return state.get() == State.RUNNING ? remaining : 0;
					}, transaction.getStartPosition(), transaction.getEndPosition());
				fetchedLastPosition = receiveStatus.getLastPosition();
				if (fetchedLastPosition != null){
					if (isInitiallyOpenRange){  // we need to close the open range
						try{
							txCoordinator.updateTransactionEndPosition(seriesId, processorId, transaction.getTransactionId(), fetchedLastPosition);
							transaction.setEndPosition(fetchedLastPosition);
							isOpenRangeClosed = true;
						}catch(Exception e){
							throw new Exception("Unable to update end position in open range transaction", e);
						}
					}else{  // we need to make sure that all items in the range had been fetched
						if (!fetchedLastPosition.equals(transaction.getEndPosition())){
							throw new Exception("Unable to fetch all the data in range within duration " + DurationFormatter.format(receiveTimeoutMillis));
						}
					}
					succeeded = batchProcessor.finish(context);
				}else{
					if (logger.isDebugEnabled()){
						logDebugInTransaction("Fetched nothing within " + DurationFormatter.format(receiveTimeoutMillis), context, fetchedLastPosition, receiveStatus.isOutOfRangeReached());
					}
					if (receiveStatus.isOutOfRangeReached()){		// all data in range had been purged
						succeeded = batchProcessor.finish(context);
					}
				}
			}catch(Exception e){
				if (logger.isDebugEnabled()){
					logDebugInTransaction("Processing is not successful", context, fetchedLastPosition, e);
				}
			}
			if (succeeded == null){	// the batchProcessor will handle transaction by itself
				// do nothing because the batchProcessor will do it later
			}else if (succeeded){	// succeeded
				try{
					//txCoordinator.finishTransaction(seriesId, processorId, transaction.getTransactionId(), fetchedLastPosition);
					txCoordinator.finishTransaction(seriesId, processorId, transaction.getTransactionId());
				}catch(Exception e){
					if (logger.isDebugEnabled()){
						logDebugInTransaction("Unable to finish transaction", context, fetchedLastPosition, e);
					}
				}
			}else{	// failed
				isProcessingFailed = true;	// may also because that nothing had been received for the open range
				try{
					txCoordinator.abortTransaction(seriesId, processorId, transaction.getTransactionId());
					if (logger.isDebugEnabled()){
						logDebugInTransaction("Aborted transaction", context, fetchedLastPosition);
					}
				}catch(Exception e){
					if (logger.isDebugEnabled()){
						logDebugInTransaction("Unable to abort transaction", context, fetchedLastPosition, e);
					}
				}
			}
			
			context.isOutOfRangeMessageReached = receiveStatus == null ? false : receiveStatus.isOutOfRangeReached();
			context.isOpenRangeSuccessfullyClosed = isInitiallyOpenRange && isOpenRangeClosed && !isProcessingFailed;
			context.isOpenRangeAbortedBecauseNothingReceived = isInitiallyOpenRange && fetchedLastPosition == null;
		}
		
		protected void logDebugInTransaction(String message, ProcessingContextImpl context, String fetchedLastPosition, Exception e){
			SequentialTransaction transaction = context.transaction;
			logger.debug("[{} - {}] " + message + ": transactionId={}, startPosition={}, endPosition={}, fetchedLastPosition={}. Exception: {}", 
					context.seriesId, processorId, transaction.getTransactionId(), transaction.getStartPosition(), transaction.getEndPosition(), 
					fetchedLastPosition, exceptionSummary(e));
		}
		
		protected void logDebugInTransaction(String message, ProcessingContextImpl context, String fetchedLastPosition){
			SequentialTransaction transaction = context.transaction;
			logger.debug("[{} - {}] " + message + ": transactionId={}, startPosition={}, endPosition={}, fetchedLastPosition={}", 
					context.seriesId, processorId, transaction.getTransactionId(), transaction.getStartPosition(), transaction.getEndPosition(), 
					fetchedLastPosition);
		}
		
		protected void logDebugInTransaction(String message, ProcessingContextImpl context, String fetchedLastPosition, boolean isOutOfRangeReached){
			SequentialTransaction transaction = context.transaction;
			logger.debug("[{} - {}] " + message + ": transactionId={}, startPosition={}, endPosition={}, fetchedLastPosition={}, isOutOfRangeReached={}", 
					context.seriesId, processorId, transaction.getTransactionId(), transaction.getStartPosition(), transaction.getEndPosition(), 
					fetchedLastPosition, isOutOfRangeReached);
		}
	}
	
	static protected String exceptionSummary(final Throwable ex){
		Throwable e = ex;
		StringBuilder sb = new StringBuilder();
		sb.append('[');
		sb.append(e.getClass().getName());
		sb.append(":").append(e.getMessage());
		sb.append(']');
		for(int i = 0; i < 5 && e.getCause() != null && e.getCause() != e; i ++){
			e = e.getCause();
			sb.append(" caused by [");
			sb.append(e.getClass().getName());
			sb.append(":").append(e.getMessage());
			sb.append(']');
		}
		return sb.toString();
	}
	
	@Override
	public ProcessorStatus getProcessorStatus(String processorId) {
		Processor processor = processors.get(processorId);
		if (processor == null){
			return null;
		}else{
			return new ProcessorStatus(processor.state.get());
		}
	}


	
	@Override
	public SortedMap getProcessorStatus(){
		SortedMap result = new TreeMap<>();
		for (Processor runnable: processors.values()){
			result.put(runnable.processorId, new ProcessorStatus(runnable.state.get()));
		}
		return result;
	}
	
	@Override
	public LinkedHashMap getStreamStatus() throws TransactionStorageInfrastructureException, DataStreamInfrastructureException{
		List> localSuppliers = new ArrayList<>(suppliers.size());
		localSuppliers.addAll(suppliers);

		LinkedHashMap result = new LinkedHashMap<>(localSuppliers.size());
		for (StreamDataSupplierWithIdAndRange supplier: localSuppliers){
			String seriesId = seriesId(supplier);
			List transactions = txCoordinator.getRecentTransactions(seriesId);
			
			TransactionCounts transactionCounts = SequentialTransactionsCoordinator.getTransactionCounts(transactions);
			String finishedPosition = SequentialTransactionsCoordinator.getFinishedPosition(transactions);
			String lastUnfinishedStartPosition = null;
			String lastUnfinishedEndPosition = null;
			Instant finishedEnqueuedTime = null;
			Instant lastUnfinishedStartEnqueuedTime = null;
			Instant lastUnfinishedEndEnqueuedTime = null;
			if (transactions != null && transactions.size() > 0){
				ReadOnlySequentialTransaction tx = transactions.get(transactions.size() - 1);
				if (tx.isInProgress()){
					lastUnfinishedStartPosition = tx.getStartPosition();
					lastUnfinishedEndPosition = tx.getEndPosition();
					if (StringUtils.isNotBlank(lastUnfinishedStartPosition)){
						lastUnfinishedStartEnqueuedTime = supplier.getSupplier().enqueuedTime(lastUnfinishedStartPosition);
					}
					if (StringUtils.isNotBlank(lastUnfinishedEndPosition)){
						lastUnfinishedEndEnqueuedTime = supplier.getSupplier().enqueuedTime(lastUnfinishedEndPosition);
					}
				}
			}
			if (finishedPosition != null){
				finishedEnqueuedTime = supplier.getSupplier().enqueuedTime(finishedPosition);
			}
			
			StreamStatus status = new StreamStatus();
			status.transactionCounts = transactionCounts;
			status.finishedPosition = finishedPosition;
			status.lastUnfinishedStartPosition = lastUnfinishedStartPosition;
			status.lastUnfinishedEndPosition = lastUnfinishedEndPosition;
			status.finishedEnqueuedTime = finishedEnqueuedTime;
			status.lastUnfinishedStartEnqueuedTime = lastUnfinishedStartEnqueuedTime;
			status.lastUnfinishedEndEnqueuedTime = lastUnfinishedEndEnqueuedTime;
			
			result.put(supplier.getId(), status);
		}
		return result;
	}
	
	
	/**
	 * Options for the processing.
	 * 
    *
  • initialTransactionTimeoutDuration - the initial timeout duration for the transactions. It should be long enough * to allow data items for a batch to be fetched, Otherwise the processors may not have a chance to renew the transaction * timeout before the transaction times out.
  • *
  • maxInProgressTransactions - maximum number of transactions allowed to be in progress at the same time
  • *
  • maxRetringTransactions - among in progress transactions, the maximum number of retrying transactions allowed at the same time
  • *
  • transactionAcquisitionDelay - time to wait before next try to get a batch of data items for processing when * previously there was no data available for processing
  • *
  • waitStrategy - the {@link WaitStrategy} specifying how to wait for a specific time duration
  • *
  • noStick/stickyWhenOpenRangeSucceeded/stickyWhenOpenRangeSucceededOrNoData - how processors stick to suppliers
  • *
* @author James Hu * */ static public class Options{ static public final int STICKY_NEVER = 0; static public final int STICKY_WHEN_OPEN_RANGE_SUCCEEDED = 1; static public final int STICKY_WHEN_OPEN_RANGE_SUCCEEDED_OR_NO_DATA = 2; private Duration initialTransactionTimeoutDuration; private int maxInProgressTransactions; private int maxRetringTransactions; private Duration transactionAcquisitionDelay; private WaitStrategy waitStrategy; private int stickyMode = STICKY_NEVER; public Options(){ } public Options(Options that){ this.initialTransactionTimeoutDuration = that.initialTransactionTimeoutDuration; this.maxInProgressTransactions = that.maxInProgressTransactions; this.maxRetringTransactions = that.maxRetringTransactions; this.transactionAcquisitionDelay = that.transactionAcquisitionDelay; this.waitStrategy = that.waitStrategy; this.stickyMode = that.stickyMode; } public Duration getInitialTransactionTimeoutDuration() { return initialTransactionTimeoutDuration; } public void setInitialTransactionTimeoutDuration(Duration initialTransactionTimeoutDuration) { this.initialTransactionTimeoutDuration = initialTransactionTimeoutDuration; } public Options withInitialTransactionTimeoutDuration(Duration initialTransactionTimeoutDuration) { this.initialTransactionTimeoutDuration = initialTransactionTimeoutDuration; return this; } public int getMaxInProgressTransactions() { return maxInProgressTransactions; } public void setMaxInProgressTransactions(int maxInProgressTransactions) { this.maxInProgressTransactions = maxInProgressTransactions; } public Options withMaxInProgressTransactions(int maxInProgressTransactions) { this.maxInProgressTransactions = maxInProgressTransactions; return this; } public int getMaxRetringTransactions() { return maxRetringTransactions; } public void setMaxRetringTransactions(int maxRetringTransactions) { this.maxRetringTransactions = maxRetringTransactions; } public Options withMaxRetringTransactions(int maxRetringTransactions) { this.maxRetringTransactions = maxRetringTransactions; return this; } public Duration getTransactionAcquisitionDelay() { return transactionAcquisitionDelay; } public void setTransactionAcquisitionDelay(Duration transactionAcquisitionDelay) { this.transactionAcquisitionDelay = transactionAcquisitionDelay; } public Options withTransactionAcquisitionDelay(Duration transactionAcquisitionDelay) { this.transactionAcquisitionDelay = transactionAcquisitionDelay; return this; } public WaitStrategy getWaitStrategy() { return waitStrategy; } public void setWaitStrategy(WaitStrategy waitStrategy) { this.waitStrategy = waitStrategy; } public Options withWaitStrategy(WaitStrategy waitStrategy) { this.waitStrategy = waitStrategy; return this; } public Options withStickyMode(int stickyMode){ this.stickyMode = stickyMode; return this; } /** * Processors will always try to get and process a transaction from another supplier * after processed (successful or not) a transaction from a supplier. * @return the same Options object */ public Options withNoSticky(){ this.stickyMode = STICKY_NEVER; return this; } /** * Processors will keep trying to process transactions from the same supplier * only if previous transaction was a successfully closed open range one. * @return the same Options object */ public Options withStickyWhenOpenRangeSucceeded(){ this.stickyMode = STICKY_WHEN_OPEN_RANGE_SUCCEEDED; return this; } /** * Processors will keep trying to process transactions from the same supplier * if previous transaction was an open range one and the transaction succeeded * or there was no data received in the transaction. * @return the same Options object */ public Options withStickyWhenOpenRangeSucceededOrNoData(){ this.stickyMode = STICKY_WHEN_OPEN_RANGE_SUCCEEDED_OR_NO_DATA; return this; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy