package org.threadly.concurrent.wrapper.limiter;
import java.util.concurrent.Callable;
import org.threadly.concurrent.SchedulerService;
import org.threadly.concurrent.wrapper.traceability.ThreadRenamingSchedulerService;
import org.threadly.util.StringUtils;
/**
* This is a cross between the {@link org.threadly.concurrent.wrapper.KeyDistributedScheduler} and
* a {@link SchedulerServiceLimiter}. This is designed to limit concurrency for a given thread,
* but permit more than one thread to run at a time for a given key. If the desired effect is to
* have a single thread per key, {@link org.threadly.concurrent.wrapper.KeyDistributedScheduler}
* is a much better option.
* <p>
* The easiest way to use this class would be to have it distribute out schedulers through
* {@link #getSubmitterSchedulerForKey(Object)}.
*
* @since 4.6.0 (since 4.3.0 at org.threadly.concurrent.limiter)
*/
public class KeyedSchedulerServiceLimiter extends AbstractKeyedSchedulerLimiter<SchedulerServiceLimiter> {
protected final SchedulerService scheduler;
/**
* Construct a new {@link KeyedSchedulerServiceLimiter} providing only the backing scheduler
* and the maximum concurrency per unique key. By default this will not rename threads for
* tasks executing.
*
* @param scheduler Scheduler to execute and schedule tasks on
* @param maxConcurrency Maximum concurrency allowed per task key
*/
public KeyedSchedulerServiceLimiter(SchedulerService scheduler, int maxConcurrency) {
this(scheduler, maxConcurrency, null, false);
}
/**
* Construct a new {@link KeyedSchedulerServiceLimiter} providing the backing scheduler, the maximum
* concurrency per unique key, and how keyed limiter threads should be named.
*
* @param scheduler Scheduler to execute and schedule tasks on
* @param maxConcurrency Maximum concurrency allowed per task key
* @param subPoolName Name prefix for sub pools, {@code null} to not change thread names
* @param addKeyToThreadName If {@code true} the key's .toString() will be added in the thread name
*/
public KeyedSchedulerServiceLimiter(SchedulerService scheduler, int maxConcurrency,
String subPoolName, boolean addKeyToThreadName) {
this(scheduler, maxConcurrency, subPoolName, addKeyToThreadName, DEFAULT_LOCK_PARALISM);
}
/**
* Construct a new {@link KeyedSchedulerServiceLimiter} providing the backing scheduler, the
* maximum concurrency per unique key, and how keyed limiter threads should be named.
* <p>
* The parallelism value should be a factor of how many keys are submitted to the pool during any
* given period of time. Depending on task execution duration, and quantity of threads executing
* tasks this value may be able to be smaller than expected. Higher values result in less lock
* contention, but more memory usage. Most systems will run fine with this anywhere from 4 to 64.
*
* @param scheduler Scheduler to execute and schedule tasks on
* @param maxConcurrency Maximum concurrency allowed per task key
* @param subPoolName Name prefix for sub pools, {@code null} to not change thread names
* @param addKeyToThreadName If {@code true} the key's .toString() will be added in the thread name
* @param expectedParallism Expected concurrent task addition access, used for performance tuning
*/
public KeyedSchedulerServiceLimiter(SchedulerService scheduler, int maxConcurrency,
String subPoolName, boolean addKeyToThreadName,
int expectedParallism) {
super(scheduler, maxConcurrency, subPoolName, addKeyToThreadName, expectedParallism);
this.scheduler = scheduler;
}
@Override
protected SchedulerServiceLimiter makeLimiter(String limiterThreadName) {
return new SchedulerServiceLimiter(StringUtils.isNullOrEmpty(limiterThreadName) ?
scheduler : new ThreadRenamingSchedulerService(scheduler,
limiterThreadName,
false),
maxConcurrency);
}
/**
* Removes the runnable task from the execution queue. It is possible for the runnable to still
* run until this call has returned.
* <p>
* See also: {@link SchedulerService#remove(Runnable)}
*
* @param task The original task provided to the executor
* @return {@code true} if the task was found and removed
*/
public boolean remove(Runnable task) {
for (LimiterContainer limiter : currentLimiters.values()) {
if (limiter.limiter.remove(task)) {
limiter.handlingTasks.decrementAndGet();
return true;
}
}
return false;
}
/**
* Removes the runnable task from the execution queue. It is possible for the runnable to still
* run until this call has returned.
* <p>
* See also: {@link SchedulerService#remove(Callable)}
*
* @param task The original task provided to the executor
* @return {@code true} if the task was found and removed
*/
public boolean remove(Callable<?> task) {
for (LimiterContainer limiter : currentLimiters.values()) {
if (limiter.limiter.remove(task)) {
limiter.handlingTasks.decrementAndGet();
return true;
}
}
return false;
}
/**
* Call to check how many tasks are currently being executed in this scheduler.
* <p>
* See also: {@link SchedulerService#getActiveTaskCount()}
*
* @return current number of running tasks
*/
public int getActiveTaskCount() {
return scheduler.getActiveTaskCount();
}
/**
* Returns how many tasks are either waiting to be executed, or are scheduled to be executed at
* a future point. Because this does not lock state can be modified during the calculation of
* this result. Ultimately resulting in an inaccurate number.
* <p>
* See also: {@link SchedulerService#getQueuedTaskCount()}
*
* @return quantity of tasks waiting execution or scheduled to be executed later
*/
public int getQueuedTaskCount() {
int result = 0;
for (LimiterContainer limiter : currentLimiters.values()) {
result += limiter.limiter.waitingTasks.size();
}
return result + scheduler.getQueuedTaskCount();
}
/**
* Function to check if the thread pool is currently accepting and handling tasks.
* <p>
* See also: {@link SchedulerService#isShutdown()}
*
* @return {@code true} if thread pool is running
*/
public boolean isShutdown() {
return scheduler.isShutdown();
}
}