From 23acc7051d8141959abdf7f1c6a682ac21d36804 Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Fri, 9 Jan 2026 19:13:29 -0500 Subject: [PATCH 1/4] Use new Request Context for each realm during implicit bootstrap The implicit (auto) bootstrap calls used to share Request Context for potentially many realms. That used to work by coincidence because `RealmConfig`, for example, is a `RequestScoped` bean. With this change each realm will be bootstrapped in its own dedicated Request Context. This change lays down a foundation for future refactoring related to `RealmConfig`. --- .../polaris/service/config/Bootstrapper.java | 75 +++++++++++++++++++ .../service/config/ServiceProducers.java | 12 ++- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java new file mode 100644 index 0000000000..db2ac37687 --- /dev/null +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.polaris.service.config; + +import io.smallrye.common.annotation.Identifier; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.polaris.core.persistence.MetaStoreManagerFactory; +import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet; +import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult; +import org.apache.polaris.service.context.catalog.RealmContextHolder; + +/** Utility class for running per-realm bootstrap tasks each in a fresh Request Context. */ +@ApplicationScoped +public class Bootstrapper { + @Inject private RealmContextHolder realmContextHolder; + @Inject private MetaStoreManagerFactory factory; + + // Note: this executor is expected to NOT propagate CDI contexts to tasks. + @Inject + @Identifier("task-executor") + private ExecutorService executor; + + PrincipalSecretsResult bootstrapRealm(String realmId, RootCredentialsSet rootCredentialsSet) { + Task t = new Task(realmContextHolder, realmId, rootCredentialsSet, factory); + try { + // Submit an async task to ensure it runs in a fresh RequestContext + return executor.submit(t).get(2, TimeUnit.MINUTES); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private record Task( + RealmContextHolder realmContextHolder, + String realmId, + RootCredentialsSet rootCredentialsSet, + MetaStoreManagerFactory factory) + implements Callable { + + @Override + @ActivateRequestContext + public PrincipalSecretsResult call() { + // Note: each call to this method runs in a new CDI request context. + // Make the realm ID effective in the current request context. + realmContextHolder.set(() -> realmId); + Map res = + factory.bootstrapRealms(Collections.singleton(realmId), rootCredentialsSet); + return res.get(realmId); + } + } +} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java index 0042ac84e2..77048c1d3f 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java @@ -31,6 +31,7 @@ import jakarta.enterprise.inject.Produces; import jakarta.inject.Singleton; import java.time.Clock; +import java.util.HashMap; import java.util.stream.Collectors; import org.apache.polaris.core.PolarisCallContext; import org.apache.polaris.core.PolarisDefaultDiagServiceImpl; @@ -48,6 +49,7 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet; import org.apache.polaris.core.persistence.cache.EntityCache; +import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult; import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory; import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactoryImpl; import org.apache.polaris.core.persistence.resolver.Resolver; @@ -271,7 +273,7 @@ public StsClientsPool stsClientsPool( */ public void maybeBootstrap( @Observes Startup event, - MetaStoreManagerFactory factory, + Bootstrapper bootstrapper, PersistenceConfiguration config, RealmContextConfiguration realmContextConfiguration) { var rootCredentialsSet = RootCredentialsSet.fromEnvironment(); @@ -285,7 +287,13 @@ public void maybeBootstrap( RootCredentialsSet.ENVIRONMENT_VARIABLE, RootCredentialsSet.SYSTEM_PROPERTY); - var result = factory.bootstrapRealms(realmIds, rootCredentialsSet); + HashMap result = new HashMap<>(); + for (String realmId : realmIds) { + PrincipalSecretsResult r = bootstrapper.bootstrapRealm(realmId, rootCredentialsSet); + if (r != null) { + result.put(realmId, r); + } + } result.forEach( (realm, secrets) -> { From 98731658c78a741dd5b103a43a30c64eb25bdc07 Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Mon, 12 Jan 2026 18:00:31 -0500 Subject: [PATCH 2/4] review: return a Map from Bootstrapper --- .../polaris/service/config/Bootstrapper.java | 31 ++++++++++++------- .../service/config/ServiceProducers.java | 10 +----- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java index db2ac37687..8dfe44f6d8 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java @@ -24,6 +24,7 @@ import jakarta.enterprise.context.control.ActivateRequestContext; import jakarta.inject.Inject; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -44,14 +45,22 @@ public class Bootstrapper { @Identifier("task-executor") private ExecutorService executor; - PrincipalSecretsResult bootstrapRealm(String realmId, RootCredentialsSet rootCredentialsSet) { - Task t = new Task(realmContextHolder, realmId, rootCredentialsSet, factory); - try { - // Submit an async task to ensure it runs in a fresh RequestContext - return executor.submit(t).get(2, TimeUnit.MINUTES); - } catch (Exception e) { - throw new RuntimeException(e); + Map bootstrapRealms( + Iterable realmIds, RootCredentialsSet rootCredentialsSet) { + HashMap result = new HashMap<>(); + for (String realmId : realmIds) { + Task t = new Task(realmContextHolder, realmId, rootCredentialsSet, factory); + try { + // Submit an async task per realm to ensure it runs in a fresh RequestContext. + // Note: simultaneous bootstrap of multiple realms is an edge case - no need + // to optimize for fast concurrent completion. + result.putAll(executor.submit(t).get(2, TimeUnit.MINUTES)); + } catch (Exception e) { + throw new RuntimeException(e); + } } + + return result; } private record Task( @@ -59,17 +68,15 @@ private record Task( String realmId, RootCredentialsSet rootCredentialsSet, MetaStoreManagerFactory factory) - implements Callable { + implements Callable> { @Override @ActivateRequestContext - public PrincipalSecretsResult call() { + public Map call() { // Note: each call to this method runs in a new CDI request context. // Make the realm ID effective in the current request context. realmContextHolder.set(() -> realmId); - Map res = - factory.bootstrapRealms(Collections.singleton(realmId), rootCredentialsSet); - return res.get(realmId); + return factory.bootstrapRealms(Collections.singleton(realmId), rootCredentialsSet); } } } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java index 77048c1d3f..840b3fb80a 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java @@ -31,7 +31,6 @@ import jakarta.enterprise.inject.Produces; import jakarta.inject.Singleton; import java.time.Clock; -import java.util.HashMap; import java.util.stream.Collectors; import org.apache.polaris.core.PolarisCallContext; import org.apache.polaris.core.PolarisDefaultDiagServiceImpl; @@ -49,7 +48,6 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet; import org.apache.polaris.core.persistence.cache.EntityCache; -import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult; import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory; import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactoryImpl; import org.apache.polaris.core.persistence.resolver.Resolver; @@ -287,13 +285,7 @@ public void maybeBootstrap( RootCredentialsSet.ENVIRONMENT_VARIABLE, RootCredentialsSet.SYSTEM_PROPERTY); - HashMap result = new HashMap<>(); - for (String realmId : realmIds) { - PrincipalSecretsResult r = bootstrapper.bootstrapRealm(realmId, rootCredentialsSet); - if (r != null) { - result.put(realmId, r); - } - } + var result = bootstrapper.bootstrapRealms(realmIds, rootCredentialsSet); result.forEach( (realm, secrets) -> { From f61aa3cd38134d98dcf835d598dc65296df16092 Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Tue, 13 Jan 2026 11:24:39 -0500 Subject: [PATCH 3/4] review: non-public Bootstrapper --- .../java/org/apache/polaris/service/config/Bootstrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java index 8dfe44f6d8..1754d526df 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java @@ -36,7 +36,7 @@ /** Utility class for running per-realm bootstrap tasks each in a fresh Request Context. */ @ApplicationScoped -public class Bootstrapper { +class Bootstrapper { @Inject private RealmContextHolder realmContextHolder; @Inject private MetaStoreManagerFactory factory; From 04b4890d5041d5fc6a422aab1adf01a6d1ca018f Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Tue, 13 Jan 2026 11:43:36 -0500 Subject: [PATCH 4/4] review: inject via Bootstrapper ctor --- .../polaris/service/config/Bootstrapper.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java index 1754d526df..e5135c38dd 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java @@ -37,13 +37,20 @@ /** Utility class for running per-realm bootstrap tasks each in a fresh Request Context. */ @ApplicationScoped class Bootstrapper { - @Inject private RealmContextHolder realmContextHolder; - @Inject private MetaStoreManagerFactory factory; + private final ExecutorService executor; + private final RealmContextHolder realmContextHolder; + private final MetaStoreManagerFactory factory; - // Note: this executor is expected to NOT propagate CDI contexts to tasks. @Inject - @Identifier("task-executor") - private ExecutorService executor; + Bootstrapper( + // Note: this executor is expected to NOT propagate CDI contexts to tasks. + @Identifier("task-executor") ExecutorService executor, + RealmContextHolder realmContextHolder, + MetaStoreManagerFactory factory) { + this.executor = executor; + this.realmContextHolder = realmContextHolder; + this.factory = factory; + } Map bootstrapRealms( Iterable realmIds, RootCredentialsSet rootCredentialsSet) {