
com.gruelbox.transactionoutbox.ExecutorSubmitter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of transactionoutbox-core Show documentation
Show all versions of transactionoutbox-core Show documentation
A safe implementation of the transactional outbox pattern for Java (core library)
package com.gruelbox.transactionoutbox;
import com.gruelbox.transactionoutbox.spi.Utils;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.function.Consumer;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.event.Level;
/**
* Schedules background work using a local {@link Executor} implementation. Note that the {@link
* Runnable}s submitted to this will not be {@link java.io.Serializable} so will not be suitable for
* remoting. Remote submission of work is not yet supported.
*
* Note that there are some important aspects that should be considered in the configuration of
* this executor:
*
*
* - Should use a BOUNDED blocking queue implementation such as {@link ArrayBlockingQueue},
* otherwise under high volume, the queue may get so large it causes out-of-memory errors.
*
- Should use a {@link java.util.concurrent.RejectedExecutionHandler} which either throws
* (such as {@link java.util.concurrent.ThreadPoolExecutor.AbortPolicy}), silently fails (such
* as {@link java.util.concurrent.ThreadPoolExecutor.DiscardPolicy}) or blocks the calling
* thread until a thread is available. It should not execute the work in the
* calling thread (e.g. {@link java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy},
* since this could result in unpredictable effects with tasks assuming they will be run in a
* different thread context corrupting thread state. Generally, throwing or silently failing
* are preferred since this allows the database to absorb all backpressure, but if you have a
* strong reason to choose a blocking policy to enforce upstream backpressure, be aware that
* {@link TransactionOutbox#flush()} can potentially block for a long period of time too, so
* design any background processing which calls it accordingly (e.g. avoid calling from a
* timed scheduled job; perhaps instead simply loop it).
*
- The queue can afford to be quite large in most realistic production deployments, and it is
* advised that it be so (10000+).
*
*/
@Slf4j
@Builder
public class ExecutorSubmitter implements Submitter, Validatable {
/**
* @param executor The executor to use.
*/
@SuppressWarnings("JavaDoc")
private final Executor executor;
/**
* @param logLevelWorkQueueSaturation The log level to use when work submission hits the executor
* queue limit. This usually indicates saturation and may be of greater interest than the
* default {@code DEBUG} level.
*/
@SuppressWarnings("JavaDoc")
@Builder.Default
private final Level logLevelWorkQueueSaturation = Level.DEBUG;
@Override
public void submit(TransactionOutboxEntry entry, Consumer localExecutor) {
try {
executor.execute(() -> localExecutor.accept(entry));
log.debug("Submitted {} for immediate processing", entry.description());
} catch (RejectedExecutionException e) {
Utils.logAtLevel(
log,
logLevelWorkQueueSaturation,
"Queued {} for processing when executor is available",
entry.description());
} catch (Exception e) {
log.warn(
"Failed to submit {} for execution. It will be re-attempted later.",
entry.description(),
e);
}
}
@Override
public void validate(Validator validator) {
validator.notNull("executor", executor);
validator.notNull("logLevelWorkQueueSaturation", logLevelWorkQueueSaturation);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy