diff --git a/.trivyignore b/.trivyignore
index 55309276c..1aa30ac1d 100644
--- a/.trivyignore
+++ b/.trivyignore
@@ -21,4 +21,8 @@ CVE-2025-65018 exp:2026-06-05
CVE-2025-66293 exp:2026-06-15
# UID2-6481
-CVE-2025-68973 exp:2026-06-15
\ No newline at end of file
+CVE-2025-68973 exp:2026-06-15
+
+# Libpng for testing
+CVE-2026-22695
+CVE-2026-22801
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 7f9f95f75..46cce2247 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,7 +27,8 @@ RUN adduser -D uid2-operator && mkdir -p /opt/uid2 && chmod 777 -R /opt/uid2 &&
USER uid2-operator
CMD java \
- -XX:MaxRAMPercentage=95 -XX:-UseCompressedOops -XX:+PrintFlagsFinal -XX:-OmitStackTraceInFastThrow \
+ -XX:+UseZGC -XX:+ZGenerational \
+ -XX:MaxRAMPercentage=85 -XX:-UseCompressedOops -XX:+PrintFlagsFinal -XX:-OmitStackTraceInFastThrow \
-Djava.security.egd=file:/dev/./urandom \
-Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory \
-Dlogback.configurationFile=/app/conf/logback.xml \
diff --git a/conf/default-config.json b/conf/default-config.json
index c5de0d87b..3b3c1c78e 100644
--- a/conf/default-config.json
+++ b/conf/default-config.json
@@ -40,5 +40,7 @@
"sharing_token_expiry_seconds": 2592000,
"operator_type": "public",
"enable_remote_config": true,
- "uid_instance_id_prefix": "local-operator"
+ "uid_instance_id_prefix": "local-operator",
+ "enable_async_batch_request": true,
+ "compute_pool_thread_count": 12
}
diff --git a/pom.xml b/pom.xml
index 45d68d711..88ed2aa1a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.uid2
uid2-operator
- 5.63.30
+ 5.63.35-alpha-294-SNAPSHOT
UTF-8
diff --git a/src/main/java/com/uid2/operator/Const.java b/src/main/java/com/uid2/operator/Const.java
index 14f878371..406c52df2 100644
--- a/src/main/java/com/uid2/operator/Const.java
+++ b/src/main/java/com/uid2/operator/Const.java
@@ -39,5 +39,8 @@ public class Config extends com.uid2.shared.Const.Config {
public static final String RuntimeConfigMetadataPathProp = "runtime_config_metadata_path";
public static final String IdentityEnvironmentProp = "identity_environment";
+
+ public static final String ComputePoolThreadCountProp = "compute_pool_thread_count";
+ public static final String EnableAsyncBatchRequestProp = "enable_async_batch_request";
}
}
diff --git a/src/main/java/com/uid2/operator/Main.java b/src/main/java/com/uid2/operator/Main.java
index b4e79f5c2..ca62ea4c6 100644
--- a/src/main/java/com/uid2/operator/Main.java
+++ b/src/main/java/com/uid2/operator/Main.java
@@ -341,8 +341,13 @@ private void run() throws Exception {
this.createVertxInstancesMetric();
this.createVertxEventLoopsMetric();
+ // Create shared compute pool for CPU-intensive operations
+ final int computePoolSize = config.getInteger(Const.Config.ComputePoolThreadCountProp, Math.max(1, Runtime.getRuntime().availableProcessors() - 2));
+ final WorkerExecutor computeWorkerPool = vertx.createSharedWorkerExecutor("compute", computePoolSize);
+ LOGGER.info("Created compute worker pool with size: {}", computePoolSize);
+
Supplier operatorVerticleSupplier = () -> {
- UIDOperatorVerticle verticle = new UIDOperatorVerticle(configStore, config, this.clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, getKeyManager(), saltProvider, optOutStore, Clock.systemUTC(), _statsCollectorQueue, new SecureLinkValidatorService(this.serviceLinkProvider, this.serviceProvider), this.shutdownHandler::handleSaltRetrievalResponse, this.uidInstanceIdProvider);
+ UIDOperatorVerticle verticle = new UIDOperatorVerticle(configStore, config, this.clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, getKeyManager(), saltProvider, optOutStore, Clock.systemUTC(), _statsCollectorQueue, new SecureLinkValidatorService(this.serviceLinkProvider, this.serviceProvider), this.shutdownHandler::handleSaltRetrievalResponse, this.uidInstanceIdProvider, computeWorkerPool);
return verticle;
};
@@ -371,6 +376,9 @@ private void run() throws Exception {
})
.onFailure(t -> {
LOGGER.error("Failed to bootstrap operator: " + t.getMessage(), new Exception(t));
+ if (computeWorkerPool != null) {
+ computeWorkerPool.close();
+ }
vertx.close();
System.exit(1);
});
@@ -488,7 +496,7 @@ private static Vertx createVertx() {
MicrometerMetricsOptions metricOptions = new MicrometerMetricsOptions()
.setPrometheusOptions(prometheusOptions)
- .setLabels(EnumSet.of(Label.HTTP_METHOD, Label.HTTP_CODE, Label.HTTP_PATH))
+ .setLabels(EnumSet.of(Label.HTTP_METHOD, Label.HTTP_CODE, Label.HTTP_PATH, Label.POOL_NAME))
.setJvmMetricsEnabled(true)
.setEnabled(true);
setupMetrics(metricOptions);
@@ -499,7 +507,8 @@ private static Vertx createVertx() {
VertxOptions vertxOptions = new VertxOptions()
.setMetricsOptions(metricOptions)
- .setBlockedThreadCheckInterval(threadBlockedCheckInterval);
+ .setBlockedThreadCheckInterval(threadBlockedCheckInterval)
+ .setWorkerPoolSize(12);
return Vertx.vertx(vertxOptions);
}
@@ -524,6 +533,7 @@ private static void setupMetrics(MicrometerMetricsOptions metricOptions) {
Objects.equals(id.getTag(Label.HTTP_CODE.toString()), "404")))
.meterFilter(new MeterFilter() {
private final String httpServerResponseTime = MetricsDomain.HTTP_SERVER.getPrefix() + MetricsNaming.v4Names().getHttpResponseTime();
+ private final String poolQueueTime = MetricsDomain.NAMED_POOLS.getPrefix() + MetricsNaming.v4Names().getPoolQueueTime();
@Override
public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
@@ -533,6 +543,12 @@ public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticC
.build()
.merge(config);
}
+ if (id.getName().equals(poolQueueTime)) {
+ return DistributionStatisticConfig.builder()
+ .percentiles(0.50, 0.90, 0.95, 0.99)
+ .build()
+ .merge(config);
+ }
return config;
}
})
diff --git a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java
index 7b36e8eea..075a6894a 100644
--- a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java
+++ b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java
@@ -43,6 +43,7 @@
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
+import io.vertx.core.WorkerExecutor;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerResponse;
@@ -133,6 +134,7 @@ public class UIDOperatorVerticle extends AbstractVerticle {
public static final long OPT_OUT_CHECK_CUTOFF_DATE = Instant.parse("2023-09-01T00:00:00.00Z").getEpochSecond();
private final Handler saltRetrievalResponseHandler;
private final int allowClockSkewSeconds;
+ private final WorkerExecutor computeWorkerPool;
protected Map> siteIdToInvalidOriginsAndAppNames = new HashMap<>();
protected boolean keySharingEndpointProvideAppNames;
protected Instant lastInvalidOriginProcessTime = Instant.now();
@@ -140,6 +142,8 @@ public class UIDOperatorVerticle extends AbstractVerticle {
private final int optOutStatusMaxRequestSize;
private final boolean optOutStatusApiEnabled;
+ private final boolean isAsyncBatchRequestsEnabled;
+
//"Android" is from https://github.com/IABTechLab/uid2-android-sdk/blob/ff93ebf597f5de7d440a84f7015a334ba4138ede/sdk/src/main/java/com/uid2/UID2Client.kt#L46
//"ios"/"tvos" is from https://github.com/IABTechLab/uid2-ios-sdk/blob/91c290d29a7093cfc209eca493d1fee80c17e16a/Sources/UID2/UID2Client.swift#L36-L38
private static final List SUPPORTED_IN_APP = Arrays.asList("Android", "ios", "tvos");
@@ -164,7 +168,8 @@ public UIDOperatorVerticle(IConfigStore configStore,
IStatsCollectorQueue statsCollectorQueue,
SecureLinkValidatorService secureLinkValidatorService,
Handler saltRetrievalResponseHandler,
- UidInstanceIdProvider uidInstanceIdProvider) {
+ UidInstanceIdProvider uidInstanceIdProvider,
+ WorkerExecutor computeWorkerPool) {
this.keyManager = keyManager;
this.secureLinkValidatorService = secureLinkValidatorService;
try {
@@ -198,6 +203,8 @@ public UIDOperatorVerticle(IConfigStore configStore,
this.identityV3Enabled = config.getBoolean(IdentityV3Prop, false);
this.disableOptoutToken = config.getBoolean(DisableOptoutTokenProp, false);
this.uidInstanceIdProvider = uidInstanceIdProvider;
+ this.computeWorkerPool = computeWorkerPool;
+ this.isAsyncBatchRequestsEnabled = config.getBoolean(EnableAsyncBatchRequestProp, false);
}
@Override
@@ -282,10 +289,6 @@ private void setUpEncryptedRoutes(Router mainRouter, BodyHandler bodyHandler) {
rc -> encryptedPayloadHandler.handleTokenRefresh(rc, this::handleTokenRefreshV2)));
mainRouter.post(V2_TOKEN_VALIDATE.toString()).handler(bodyHandler).handler(auth.handleV1(
rc -> encryptedPayloadHandler.handle(rc, this::handleTokenValidateV2), Role.GENERATOR));
- mainRouter.post(V2_IDENTITY_BUCKETS.toString()).handler(bodyHandler).handler(auth.handleV1(
- rc -> encryptedPayloadHandler.handle(rc, this::handleBucketsV2), Role.MAPPER));
- mainRouter.post(V2_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1(
- rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV2), Role.MAPPER));
mainRouter.post(V2_KEY_LATEST.toString()).handler(bodyHandler).handler(auth.handleV1(
rc -> encryptedPayloadHandler.handle(rc, this::handleKeysRequestV2), Role.ID_READER));
mainRouter.post(V2_KEY_SHARING.toString()).handler(bodyHandler).handler(auth.handleV1(
@@ -303,8 +306,22 @@ private void setUpEncryptedRoutes(Router mainRouter, BodyHandler bodyHandler) {
if (this.clientSideTokenGenerate)
mainRouter.post(V2_TOKEN_CLIENTGENERATE.toString()).handler(bodyHandler).handler(this::handleClientSideTokenGenerate);
- mainRouter.post(V3_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1(
- rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV3), Role.MAPPER));
+ if (isAsyncBatchRequestsEnabled) {
+ LOGGER.info("Async batch requests enabled");
+ mainRouter.post(V2_IDENTITY_BUCKETS.toString()).handler(bodyHandler).handler(auth.handleV1(
+ rc -> encryptedPayloadHandler.handleAsync(rc, this::handleBucketsV2Async), Role.MAPPER));
+ mainRouter.post(V2_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1(
+ rc -> encryptedPayloadHandler.handleAsync(rc, this::handleIdentityMapV2Async), Role.MAPPER));
+ mainRouter.post(V3_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1(
+ rc -> encryptedPayloadHandler.handleAsync(rc, this::handleIdentityMapV3Async), Role.MAPPER));
+ } else {
+ mainRouter.post(V2_IDENTITY_BUCKETS.toString()).handler(bodyHandler).handler(auth.handleV1(
+ rc -> encryptedPayloadHandler.handle(rc, this::handleBucketsV2), Role.MAPPER));
+ mainRouter.post(V2_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1(
+ rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV2), Role.MAPPER));
+ mainRouter.post(V3_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1(
+ rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV3), Role.MAPPER));
+ }
}
private void handleClientSideTokenGenerate(RoutingContext rc) {
@@ -1037,6 +1054,13 @@ private Future handleLogoutAsyncV2(RoutingContext rc) {
}
}
+ private Future handleBucketsV2Async(RoutingContext rc) {
+ return computeWorkerPool.executeBlocking(() -> {
+ handleBucketsV2(rc);
+ return null;
+ });
+ }
+
private void handleBucketsV2(RoutingContext rc) {
final JsonObject req = (JsonObject) rc.data().get("request");
final String qp = req.getString("since_timestamp");
@@ -1222,6 +1246,13 @@ private boolean validateServiceLink(RoutingContext rc) {
return false;
}
+ private Future handleIdentityMapV2Async(RoutingContext rc) {
+ return computeWorkerPool.executeBlocking(() -> {
+ handleIdentityMapV2(rc);
+ return null;
+ });
+ }
+
private void handleIdentityMapV2(RoutingContext rc) {
try {
final Integer siteId = RoutingContextUtil.getSiteId(rc);
@@ -1285,6 +1316,13 @@ private InputUtil.InputVal[] getIdentityMapV2Input(RoutingContext rc) {
getInputList.get();
}
+ private Future handleIdentityMapV3Async(RoutingContext rc) {
+ return computeWorkerPool.executeBlocking(() -> {
+ handleIdentityMapV3(rc);
+ return null;
+ });
+ }
+
private void handleIdentityMapV3(RoutingContext rc) {
try {
JsonObject jsonInput = (JsonObject) rc.data().get("request");
diff --git a/src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java b/src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java
index 43e06dc6b..79868da94 100644
--- a/src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java
+++ b/src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java
@@ -11,6 +11,7 @@
import com.uid2.shared.store.*;
import com.uid2.shared.store.salt.ISaltProvider;
import io.vertx.core.Handler;
+import io.vertx.core.WorkerExecutor;
import io.vertx.core.json.JsonObject;
import java.time.Clock;
@@ -33,8 +34,9 @@ public ExtendedUIDOperatorVerticle(IConfigStore configStore,
IStatsCollectorQueue statsCollectorQueue,
SecureLinkValidatorService secureLinkValidationService,
Handler saltRetrievalResponseHandler,
- UidInstanceIdProvider uidInstanceIdProvider) {
- super(configStore, config, clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, keyManager, saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidationService, saltRetrievalResponseHandler, uidInstanceIdProvider);
+ UidInstanceIdProvider uidInstanceIdProvider,
+ WorkerExecutor computeWorkerPool) {
+ super(configStore, config, clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, keyManager, saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidationService, saltRetrievalResponseHandler, uidInstanceIdProvider, computeWorkerPool);
}
public IUIDOperatorService getIdService() {
diff --git a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
index 835d76d91..e45bd9f46 100644
--- a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
+++ b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
@@ -38,6 +38,7 @@
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
+import io.vertx.core.WorkerExecutor;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.JsonArray;
@@ -142,6 +143,7 @@ public class UIDOperatorVerticleTest {
private ExtendedUIDOperatorVerticle uidOperatorVerticle;
private RuntimeConfig runtimeConfig;
private EncryptedTokenEncoder encoder;
+ private WorkerExecutor computeWorkerPool;
@BeforeEach
void deployVerticle(Vertx vertx, VertxTestContext testContext, TestInfo testInfo) {
@@ -165,7 +167,8 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext, TestInfo testInfo
this.uidInstanceIdProvider = new UidInstanceIdProvider("test-instance", "id");
- this.uidOperatorVerticle = new ExtendedUIDOperatorVerticle(configStore, config, config.getBoolean("client_side_token_generate"), siteProvider, clientKeyProvider, clientSideKeypairProvider, new KeyManager(keysetKeyStore, keysetProvider), saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidatorService, shutdownHandler::handleSaltRetrievalResponse, uidInstanceIdProvider);
+ this.computeWorkerPool = vertx.createSharedWorkerExecutor("compute", 4);
+ this.uidOperatorVerticle = new ExtendedUIDOperatorVerticle(configStore, config, config.getBoolean("client_side_token_generate"), siteProvider, clientKeyProvider, clientSideKeypairProvider, new KeyManager(keysetKeyStore, keysetProvider), saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidatorService, shutdownHandler::handleSaltRetrievalResponse, uidInstanceIdProvider, this.computeWorkerPool);
vertx.deployVerticle(uidOperatorVerticle, testContext.succeeding(id -> testContext.completeNow()));
this.registry = new SimpleMeterRegistry();
@@ -177,6 +180,9 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext, TestInfo testInfo
@AfterEach
void teardown() {
Metrics.globalRegistry.remove(registry);
+ if (computeWorkerPool != null) {
+ computeWorkerPool.close();
+ }
}
private RuntimeConfig setupRuntimeConfig(JsonObject config) {