From dc08b141a5178a159e6bf6bfbcac3448bc9ede97 Mon Sep 17 00:00:00 2001 From: Sunny Wu Date: Thu, 16 Jul 2026 15:04:41 +1000 Subject: [PATCH] UID2-7489: route /optout/status through the blocking handler when async batch requests are enabled /v2/optout/status is a batch endpoint (accepts up to optout_status_max_request_size, default 5000, advertising IDs per request) but was registered with a plain event-loop handler regardless of the enable_async_batch_request flag, unlike the other batch/ mapping endpoints (identity/map, identity/buckets, key/sharing, key/bidstream). Move its registration into the isAsyncBatchRequestsEnabled if/else, guarded by optOutStatusApiEnabled, so it uses blockingHandler(..., false) (worker pool, unordered) when the flag is on and a plain handler when off, mirroring identity/map. Behaviour is unchanged when enable_async_batch_request is false (the default), so this is a no-op in current prod config; the worker-pool path only activates under the same flag that already governs the other batch endpoints. Added a test asserting the endpoint still succeeds with async batch requests enabled. Co-Authored-By: Claude Opus 4.8 Ticket: UID2-7489 Branch: syw-UID2-7489-optout-status-blocking-handler --- .../operator/vertx/UIDOperatorVerticle.java | 15 +++++++---- .../operator/UIDOperatorVerticleTest.java | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java index 2481b1019..4bf00c2b3 100644 --- a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java +++ b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java @@ -301,11 +301,6 @@ private void setUpEncryptedRoutes(Router mainRouter, BodyHandler bodyHandler) { rc -> encryptedPayloadHandler.handle(rc, this::handleKeysRequestV2), Role.ID_READER)); mainRouter.post(V2_TOKEN_LOGOUT.toString()).handler(bodyHandler).handler(auth.handleV1( rc -> encryptedPayloadHandler.handleAsync(rc, this::handleLogoutAsyncV2), Role.OPTOUT)); - if (this.optOutStatusApiEnabled) { - mainRouter.post(V2_OPTOUT_STATUS.toString()).handler(bodyHandler).handler(auth.handleV1( - rc -> encryptedPayloadHandler.handle(rc, this::handleOptoutStatus), - Role.MAPPER, Role.SHARER, Role.ID_READER)); - } if (this.clientSideTokenGenerate) mainRouter.post(V2_TOKEN_CLIENTGENERATE.toString()).handler(bodyHandler).handler(this::handleClientSideTokenGenerate); @@ -322,6 +317,11 @@ private void setUpEncryptedRoutes(Router mainRouter, BodyHandler bodyHandler) { rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV2), Role.MAPPER), false); mainRouter.post(V3_IDENTITY_MAP.toString()).handler(bodyHandler).blockingHandler(auth.handleV1( rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV3), Role.MAPPER), false); + if (this.optOutStatusApiEnabled) { + mainRouter.post(V2_OPTOUT_STATUS.toString()).handler(bodyHandler).blockingHandler(auth.handleV1( + rc -> encryptedPayloadHandler.handle(rc, this::handleOptoutStatus), + Role.MAPPER, Role.SHARER, Role.ID_READER), false); + } } else { LOGGER.info("Async batch requests disabled"); mainRouter.post(V2_KEY_SHARING.toString()).handler(bodyHandler).handler(auth.handleV1( @@ -334,6 +334,11 @@ private void setUpEncryptedRoutes(Router mainRouter, BodyHandler bodyHandler) { 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)); + if (this.optOutStatusApiEnabled) { + mainRouter.post(V2_OPTOUT_STATUS.toString()).handler(bodyHandler).handler(auth.handleV1( + rc -> encryptedPayloadHandler.handle(rc, this::handleOptoutStatus), + Role.MAPPER, Role.SHARER, Role.ID_READER)); + } } } diff --git a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java index 7159adc9d..3d976e5f6 100644 --- a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java +++ b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java @@ -172,6 +172,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext, TestInfo testInfo asyncBatchRequestLogWatcher.start(); ((Logger) LoggerFactory.getLogger(UIDOperatorVerticle.class)).addAppender(asyncBatchRequestLogWatcher); } + if (testInfo.getTestMethod().isPresent() && + testInfo.getTestMethod().get().getName().equals("optOutStatusRequestWithAsyncBatchEnabled")) { + config.put(Const.Config.EnableAsyncBatchRequestProp, true); + } when(configStore.getConfig()).thenAnswer(x -> runtimeConfig); this.uidInstanceIdProvider = new UidInstanceIdProvider("test-instance", "id"); @@ -2308,6 +2312,28 @@ void optOutStatusRequest(boolean useV4Uid, Map optedOutIds, int op }); } + @Test + void optOutStatusRequestWithAsyncBatchEnabled(Vertx vertx, VertxTestContext testContext) { + fakeAuth(126, Role.MAPPER); + setupKeys(); + setupSalts(true); + + String rawUid = "qAmIGxqLk_RhOtm4f1nLlqYewqSma8fgvjEXYnQ3Jr0K"; + long optedOutSince = Instant.now().minus(1, DAYS).getEpochSecond(); + when(this.optOutStore.getOptOutTimestampByAdId(rawUid)).thenReturn(optedOutSince); + + JsonObject requestJson = new JsonObject(); + requestJson.put("advertising_ids", new JsonArray().add(rawUid)); + + send(vertx, "v2/optout/status", requestJson, 200, respJson -> { + assertEquals("success", respJson.getString("status")); + JsonArray optOutJsonArray = respJson.getJsonObject("body").getJsonArray("opted_out"); + assertEquals(1, optOutJsonArray.size()); + assertEquals(rawUid, optOutJsonArray.getJsonObject(0).getString("advertising_id")); + testContext.completeNow(); + }); + } + private static Stream optOutStatusValidationErrorData() { // Test case 1 JsonArray rawUIDs = new JsonArray();