CopilotAutomationExecutorConfiguration.java
package com.taxonomy.portfolio.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Bounded coordinator capacity for persisted Copilot operations.
*
* <p>Workers remain virtual threads because they mostly wait for durable analysis
* jobs, but both concurrently active coordinators and queued operations are bounded
* so a large import cannot create an unbounded stream of database polling tasks.</p>
*/
@Configuration
public class CopilotAutomationExecutorConfiguration {
private static final int MAX_CONFIGURED_CONCURRENCY = 64;
private static final int MAX_CONFIGURED_QUEUE = 10_000;
@Bean(name = "copilotAutomationExecutor", destroyMethod = "shutdown")
ExecutorService copilotAutomationExecutor(
@Value("${taxonomy.ai.coordinator.max-concurrent-operations:4}")
int maximumConcurrentOperations,
@Value("${taxonomy.ai.coordinator.queue-capacity:100}")
int queueCapacity) {
int concurrency = bounded(
maximumConcurrentOperations,
1,
MAX_CONFIGURED_CONCURRENCY,
"taxonomy.ai.coordinator.max-concurrent-operations");
int backlog = bounded(
queueCapacity,
1,
MAX_CONFIGURED_QUEUE,
"taxonomy.ai.coordinator.queue-capacity");
ThreadFactory workers = Thread.ofVirtual()
.name("taxonomy-copilot-coordinator-", 0)
.factory();
return new ThreadPoolExecutor(
concurrency,
concurrency,
0L,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(backlog),
workers,
new ThreadPoolExecutor.AbortPolicy());
}
private static int bounded(int value, int minimum, int maximum, String property) {
if (value < minimum || value > maximum) {
throw new IllegalArgumentException(
property + " must be between " + minimum + " and " + maximum);
}
return value;
}
}