Skip to content

Commit dd2fdf9

Browse files
authored
Handle RequestScoped instance injection gracefully for DefaultConfigurationStore (#1758)
Although we do a check of !realmContextInstance.isUnsatisfied() it only checks if there is matching bean, however, it doesn't check if the context is active or not. Similarly isResolvable also don't check if the scope is active. Therefore if getConfiguration is called when there is no active scope, it will get Exception like ContextNotActiveException. This actually fails the TaskExecutor because it runs in a separate thread. In order to fix the problem, we introduces a new getConfiguration function to handle the background tasks, and also use isResolvable instead of isUnsatisfied to handle ambiguities.
1 parent 9c09e03 commit dd2fdf9

File tree

5 files changed

+99
-23
lines changed

5 files changed

+99
-23
lines changed

polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfigurationStore.java

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import org.apache.polaris.core.PolarisCallContext;
28+
import org.apache.polaris.core.context.RealmContext;
2829
import org.apache.polaris.core.entity.CatalogEntity;
2930
import org.slf4j.Logger;
3031
import org.slf4j.LoggerFactory;
@@ -39,11 +40,18 @@ public interface PolarisConfigurationStore {
3940
/**
4041
* Retrieve the current value for a configuration key. May be null if not set.
4142
*
43+
* <p>This function will be deprecated, it can not be called outside of active request scope, such
44+
* as background tasks (TaskExecutor). Please use the function getConfiguration(RealmContext
45+
* realmContext, String configName) to get the configuration value in a more robust way. TODO:
46+
* Remove this function and replace the usage with the function takes realm. Github issue
47+
* https://github.com/apache/polaris/issues/1775
48+
*
4249
* @param ctx the current call context
4350
* @param configName the name of the configuration key to check
4451
* @return the current value set for the configuration key or null if not set
4552
* @param <T> the type of the configuration value
4653
*/
54+
@Deprecated
4755
default <T> @Nullable T getConfiguration(PolarisCallContext ctx, String configName) {
4856
return null;
4957
}
@@ -52,19 +60,54 @@ public interface PolarisConfigurationStore {
5260
* Retrieve the current value for a configuration key. If not set, return the non-null default
5361
* value.
5462
*
63+
* <p>This function will be deprecated, it can not be called outside of active request scope, such
64+
* as background tasks (TaskExecutor). Please use the function getConfiguration(RealmContext
65+
* realmContext, String configName, T defaultValue) to get the configuration value in a more
66+
* robust way. TODO: Remove this function and replace the usage with the function takes realm.
67+
* Github issue https://github.com/apache/polaris/issues/1775
68+
*
5569
* @param ctx the current call context
5670
* @param configName the name of the configuration key to check
5771
* @param defaultValue the default value if the configuration key has no value
5872
* @return the current value or the supplied default value
59-
* @param <T> the type of the configuration value
6073
*/
74+
@Deprecated
6175
default <T> @Nonnull T getConfiguration(
6276
PolarisCallContext ctx, String configName, @Nonnull T defaultValue) {
6377
Preconditions.checkNotNull(defaultValue, "Cannot pass null as a default value");
6478
T configValue = getConfiguration(ctx, configName);
6579
return configValue != null ? configValue : defaultValue;
6680
}
6781

82+
/**
83+
* Retrieve the current value for a configuration key for a given realm. May be null if not set.
84+
*
85+
* @param realmContext the realm context
86+
* @param configName the name of the configuration key to check
87+
* @return the current value set for the configuration key for the given realm, or null if not set
88+
* @param <T> the type of the configuration value
89+
*/
90+
default <T> @Nullable T getConfiguration(@Nonnull RealmContext realmContext, String configName) {
91+
return null;
92+
}
93+
94+
/**
95+
* Retrieve the current value for a configuration key for the given realm. If not set, return the
96+
* non-null default value.
97+
*
98+
* @param realmContext the realm context
99+
* @param configName the name of the configuration key to check
100+
* @param defaultValue the default value if the configuration key has no value
101+
* @return the current value or the supplied default value
102+
* @param <T> the type of the configuration value
103+
*/
104+
default <T> @Nonnull T getConfiguration(
105+
RealmContext realmContext, String configName, @Nonnull T defaultValue) {
106+
Preconditions.checkNotNull(defaultValue, "Cannot pass null as a default value");
107+
T configValue = getConfiguration(realmContext, configName);
108+
return configValue != null ? configValue : defaultValue;
109+
}
110+
68111
/**
69112
* In some cases, we may extract a value that doesn't match the expected type for a config. This
70113
* method can be used to attempt to force-cast it using `String.valueOf`
@@ -90,7 +133,8 @@ public interface PolarisConfigurationStore {
90133
}
91134

92135
/**
93-
* Retrieve the current value for a configuration.
136+
* Retrieve the current value for a configuration. TODO: update the function to take RealmContext
137+
* instead of PolarisCallContext. Github issue https://github.com/apache/polaris/issues/1775
94138
*
95139
* @param ctx the current call context
96140
* @param config the configuration to load
@@ -104,7 +148,8 @@ public interface PolarisConfigurationStore {
104148

105149
/**
106150
* Retrieve the current value for a configuration, overriding with a catalog config if it is
107-
* present.
151+
* present. TODO: update the function to take RealmContext instead of PolarisCallContext Github
152+
* issue https://github.com/apache/polaris/issues/1775
108153
*
109154
* @param ctx the current call context
110155
* @param catalogEntity the catalog to check for an override

quarkus/service/src/test/java/org/apache/polaris/service/quarkus/config/DefaultConfigurationStoreTest.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public Map<String, String> getConfigOverrides() {
7474
}
7575

7676
private PolarisCallContext polarisContext;
77+
private RealmContext realmContext;
7778

7879
@Inject MetaStoreManagerFactory managerFactory;
7980
@Inject PolarisConfigurationStore configurationStore;
@@ -87,8 +88,7 @@ public void before(TestInfo testInfo) {
8788
.formatted(
8889
testInfo.getTestMethod().map(java.lang.reflect.Method::getName).orElse("test"),
8990
System.nanoTime());
90-
RealmContext realmContext = () -> realmName;
91-
QuarkusMock.installMockForType(realmContext, RealmContext.class);
91+
realmContext = () -> realmName;
9292
polarisContext =
9393
new PolarisCallContext(
9494
managerFactory.getOrCreateSessionSupplier(realmContext).get(),
@@ -97,8 +97,16 @@ public void before(TestInfo testInfo) {
9797
Clock.systemDefaultZone());
9898
}
9999

100+
@Test
101+
public void testGetConfigurationWithNoRealmContext() {
102+
Assertions.assertThatThrownBy(
103+
() -> configurationStore.getConfiguration(polarisContext, "missingKeyWithoutDefault"))
104+
.isInstanceOf(IllegalStateException.class);
105+
}
106+
100107
@Test
101108
public void testGetConfiguration() {
109+
QuarkusMock.installMockForType(realmContext, RealmContext.class);
102110
Object value = configurationStore.getConfiguration(polarisContext, "missingKeyWithoutDefault");
103111
assertThat(value).isNull();
104112
Object defaultValue =
@@ -137,8 +145,28 @@ public void testGetRealmConfiguration() {
137145
.isFalse();
138146
}
139147

148+
@Test
149+
void testGetConfigurationWithRealm() {
150+
// the falseByDefaultKey is set to `false` for all realms, but overwrite with value `true` for
151+
// realmOne.
152+
assertThat((Boolean) configurationStore.getConfiguration(realmOneContext, falseByDefaultKey))
153+
.isTrue();
154+
// the trueByDefaultKey is set to `false` for all realms, no overwrite for realmOne
155+
assertThat((Boolean) configurationStore.getConfiguration(realmOneContext, trueByDefaultKey))
156+
.isTrue();
157+
158+
// the falseByDefaultKey is set to `false` for all realms, no overwrite for realmTwo
159+
assertThat((Boolean) configurationStore.getConfiguration(realmTwoContext, falseByDefaultKey))
160+
.isFalse();
161+
// the trueByDefaultKey is set to `false` for all realms, and overwrite with value `false` for
162+
// realmTwo
163+
assertThat((Boolean) configurationStore.getConfiguration(realmTwoContext, trueByDefaultKey))
164+
.isFalse();
165+
}
166+
140167
@Test
141168
public void testInjectedConfigurationStore() {
169+
QuarkusMock.installMockForType(realmContext, RealmContext.class);
142170
// the default value for trueByDefaultKey is `true`
143171
boolean featureDefaultValue =
144172
configurationStore.getConfiguration(polarisContext, trueByDefaultKey);
@@ -159,6 +187,7 @@ public void testInjectedConfigurationStore() {
159187

160188
@Test
161189
public void testInjectedFeaturesConfiguration() {
190+
QuarkusMock.installMockForType(realmContext, RealmContext.class);
162191
assertThat(featuresConfiguration).isInstanceOf(QuarkusResolvedFeaturesConfiguration.class);
163192

164193
assertThat(featuresConfiguration.defaults())
@@ -179,6 +208,7 @@ public void testInjectedFeaturesConfiguration() {
179208

180209
@Test
181210
public void testRegisterAndUseFeatureConfigurations() {
211+
QuarkusMock.installMockForType(realmContext, RealmContext.class);
182212
String prefix = "testRegisterAndUseFeatureConfigurations";
183213

184214
FeatureConfiguration<Boolean> safeConfig =

service/common/src/main/java/org/apache/polaris/service/catalog/io/FileIOUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static AccessConfig refreshAccessConfig(
8787

8888
boolean skipCredentialSubscopingIndirection =
8989
configurationStore.getConfiguration(
90-
callContext.getPolarisCallContext(),
90+
callContext.getRealmContext(),
9191
FeatureConfiguration.SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION.key,
9292
FeatureConfiguration.SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION.defaultValue);
9393
if (skipCredentialSubscopingIndirection) {

service/common/src/main/java/org/apache/polaris/service/config/DefaultConfigurationStore.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,23 @@ public DefaultConfigurationStore(
4949
this.realmOverrides = Map.copyOf(configurations.parseRealmOverrides(objectMapper));
5050
}
5151

52+
@Override
53+
public <T> @Nullable T getConfiguration(@Nonnull RealmContext realmContext, String configName) {
54+
String realm = realmContext.getRealmIdentifier();
55+
LOGGER.debug("Get configuration value for {} with realm {}", configName, realm);
56+
@SuppressWarnings("unchecked")
57+
T confgValue =
58+
(T)
59+
Optional.ofNullable(realmOverrides.getOrDefault(realm, Map.of()).get(configName))
60+
.orElseGet(() -> getDefaultConfiguration(configName));
61+
return confgValue;
62+
}
63+
5264
@Override
5365
public <T> @Nullable T getConfiguration(@Nonnull PolarisCallContext ctx, String configName) {
54-
if (!realmContextInstance.isUnsatisfied()) {
66+
if (realmContextInstance.isResolvable()) {
5567
RealmContext realmContext = realmContextInstance.get();
56-
String realm = realmContext.getRealmIdentifier();
57-
LOGGER.debug("Get configuration value for {} with realm {}", configName, realm);
58-
@SuppressWarnings("unchecked")
59-
T confgValue =
60-
(T)
61-
Optional.ofNullable(realmOverrides.getOrDefault(realm, Map.of()).get(configName))
62-
.orElseGet(() -> getDefaultConfiguration(configName));
63-
return confgValue;
68+
return getConfiguration(realmContext, configName);
6469
} else {
6570
LOGGER.debug(
6671
"No RealmContext is injected when lookup value for configuration {} ", configName);

service/common/src/main/java/org/apache/polaris/service/task/TableCleanupTaskHandler.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,7 @@ public boolean handleTask(TaskEntity cleanupTask, CallContext callContext) {
115115

116116
Stream<TaskEntity> metadataFileCleanupTasks =
117117
getMetadataTaskStream(
118-
cleanupTask,
119-
tableMetadata,
120-
fileIO,
121-
tableEntity,
122-
metaStoreManager,
123-
polarisCallContext);
118+
cleanupTask, tableMetadata, fileIO, tableEntity, metaStoreManager, callContext);
124119

125120
List<TaskEntity> taskEntities =
126121
Stream.concat(manifestCleanupTasks, metadataFileCleanupTasks).toList();
@@ -206,11 +201,12 @@ private Stream<TaskEntity> getMetadataTaskStream(
206201
FileIO fileIO,
207202
IcebergTableLikeEntity tableEntity,
208203
PolarisMetaStoreManager metaStoreManager,
209-
PolarisCallContext polarisCallContext) {
204+
CallContext callContext) {
205+
PolarisCallContext polarisCallContext = callContext.getPolarisCallContext();
210206
int batchSize =
211207
polarisCallContext
212208
.getConfigurationStore()
213-
.getConfiguration(polarisCallContext, BATCH_SIZE_CONFIG_KEY, 10);
209+
.getConfiguration(callContext.getRealmContext(), BATCH_SIZE_CONFIG_KEY, 10);
214210
return getMetadataFileBatches(tableMetadata, batchSize).stream()
215211
.map(
216212
metadataBatch -> {

0 commit comments

Comments
 (0)