Skip to content

Commit 590633d

Browse files
poorbarcodenodece
authored andcommitted
[fix] [broker] Fix nothing changed after removing dynamic configs (apache#22673)
(cherry picked from commit ada31a9)
1 parent 29108c1 commit 590633d

File tree

3 files changed

+126
-40
lines changed

3 files changed

+126
-40
lines changed

pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/BrokersBase.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ public Map<String, String> getAllDynamicConfigurations() throws Exception {
230230
@ApiResponse(code = 403, message = "You don't have admin permission to get configuration")})
231231
public List<String> getDynamicConfigurationName() {
232232
validateSuperUserAccess();
233-
return BrokerService.getDynamicConfiguration();
233+
return pulsar().getBrokerService().getDynamicConfiguration();
234234
}
235235

236236
@GET
@@ -253,11 +253,11 @@ public Map<String, String> getRuntimeConfiguration() {
253253
*/
254254
private synchronized CompletableFuture<Void> persistDynamicConfigurationAsync(
255255
String configName, String configValue) {
256-
if (!BrokerService.validateDynamicConfiguration(configName, configValue)) {
256+
if (!pulsar().getBrokerService().validateDynamicConfiguration(configName, configValue)) {
257257
return FutureUtil
258258
.failedFuture(new RestException(Status.PRECONDITION_FAILED, " Invalid dynamic-config value"));
259259
}
260-
if (BrokerService.isDynamicConfiguration(configName)) {
260+
if (pulsar().getBrokerService().isDynamicConfiguration(configName)) {
261261
return dynamicConfigurationResources().setDynamicConfigurationWithCreateAsync(old -> {
262262
Map<String, String> configurationMap = old.orElseGet(Maps::newHashMap);
263263
configurationMap.put(configName, configValue);
@@ -451,7 +451,7 @@ private CompletableFuture<Void> healthCheckRecursiveReadNext(Reader<String> read
451451
}
452452

453453
private CompletableFuture<Void> internalDeleteDynamicConfigurationOnMetadataAsync(String configName) {
454-
if (!BrokerService.isDynamicConfiguration(configName)) {
454+
if (!pulsar().getBrokerService().isDynamicConfiguration(configName)) {
455455
throw new RestException(Status.PRECONDITION_FAILED, " Can't update non-dynamic configuration");
456456
} else {
457457
return dynamicConfigurationResources().setDynamicConfigurationAsync(old -> {

pulsar-broker/src/main/java/org/apache/pulsar/broker/service/BrokerService.java

Lines changed: 104 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
import org.apache.pulsar.broker.intercept.ManagedLedgerInterceptorImpl;
108108
import org.apache.pulsar.broker.loadbalance.LoadManager;
109109
import org.apache.pulsar.broker.namespace.NamespaceService;
110+
import org.apache.pulsar.broker.resources.DynamicConfigurationResources;
110111
import org.apache.pulsar.broker.resources.LocalPoliciesResources;
111112
import org.apache.pulsar.broker.resources.NamespaceResources;
112113
import org.apache.pulsar.broker.resources.NamespaceResources.PartitionedTopicResources;
@@ -222,8 +223,7 @@ public class BrokerService implements Closeable {
222223
private final OrderedExecutor topicOrderedExecutor;
223224
// offline topic backlog cache
224225
private final ConcurrentOpenHashMap<TopicName, PersistentOfflineTopicStats> offlineTopicStatCache;
225-
private static final ConcurrentOpenHashMap<String, ConfigField> dynamicConfigurationMap =
226-
prepareDynamicConfigurationMap();
226+
private final ConcurrentOpenHashMap<String, ConfigField> dynamicConfigurationMap;
227227
private final ConcurrentOpenHashMap<String, Consumer<?>> configRegisteredListeners;
228228

229229
private final ConcurrentLinkedQueue<TopicLoadingContext> pendingTopicLoadingQueue;
@@ -293,6 +293,7 @@ public class BrokerService implements Closeable {
293293

294294
public BrokerService(PulsarService pulsar, EventLoopGroup eventLoopGroup) throws Exception {
295295
this.pulsar = pulsar;
296+
this.dynamicConfigurationMap = prepareDynamicConfigurationMap();
296297
this.preciseTopicPublishRateLimitingEnable =
297298
pulsar.getConfiguration().isPreciseTopicPublishRateLimiterEnable();
298299
this.managedLedgerFactory = pulsar.getManagedLedgerFactory();
@@ -2253,38 +2254,85 @@ private void handlePoliciesUpdates(NamespaceName namespace) {
22532254
}
22542255

22552256
private void handleDynamicConfigurationUpdates() {
2256-
pulsar().getPulsarResources().getDynamicConfigResources().getDynamicConfigurationAsync()
2257+
DynamicConfigurationResources dynamicConfigResources = null;
2258+
try {
2259+
dynamicConfigResources = pulsar()
2260+
.getPulsarResources()
2261+
.getDynamicConfigResources();
2262+
} catch (Exception e) {
2263+
log.warn("Failed to read dynamic broker configuration", e);
2264+
}
2265+
2266+
if (dynamicConfigResources != null) {
2267+
dynamicConfigResources.getDynamicConfigurationAsync()
22572268
.thenAccept(optMap -> {
2269+
// Case some dynamic configs have been removed.
2270+
dynamicConfigurationMap.forEach((configKey, fieldWrapper) -> {
2271+
boolean configRemoved = !optMap.isPresent() || !optMap.get().containsKey(configKey);
2272+
if (fieldWrapper.lastDynamicValue != null && configRemoved) {
2273+
configValueChanged(configKey, null);
2274+
}
2275+
});
2276+
// Some configs have been changed.
22582277
if (!optMap.isPresent()) {
22592278
return;
22602279
}
22612280
Map<String, String> data = optMap.get();
22622281
data.forEach((configKey, value) -> {
2263-
ConfigField configFieldWrapper = dynamicConfigurationMap.get(configKey);
2264-
if (configFieldWrapper == null) {
2265-
log.warn("{} does not exist in dynamicConfigurationMap, skip this config.", configKey);
2266-
return;
2267-
}
2268-
Field configField = configFieldWrapper.field;
2269-
Object newValue = FieldParser.value(data.get(configKey), configField);
2270-
if (configField != null) {
2271-
Consumer listener = configRegisteredListeners.get(configKey);
2272-
try {
2273-
Object existingValue = configField.get(pulsar.getConfiguration());
2274-
configField.set(pulsar.getConfiguration(), newValue);
2275-
log.info("Successfully updated configuration {}/{}", configKey,
2276-
data.get(configKey));
2277-
if (listener != null && !existingValue.equals(newValue)) {
2278-
listener.accept(newValue);
2279-
}
2280-
} catch (Exception e) {
2281-
log.error("Failed to update config {}/{}", configKey, newValue);
2282-
}
2283-
} else {
2284-
log.error("Found non-dynamic field in dynamicConfigMap {}/{}", configKey, newValue);
2285-
}
2282+
configValueChanged(configKey, value);
22862283
});
22872284
});
2285+
}
2286+
}
2287+
2288+
private void configValueChanged(String configKey, String newValueStr) {
2289+
ConfigField configFieldWrapper = dynamicConfigurationMap.get(configKey);
2290+
if (configFieldWrapper == null) {
2291+
log.warn("{} does not exist in dynamicConfigurationMap, skip this config.", configKey);
2292+
return;
2293+
}
2294+
Consumer listener = configRegisteredListeners.get(configKey);
2295+
try {
2296+
// Convert existingValue and newValue.
2297+
final Object existingValue;
2298+
final Object newValue;
2299+
if (configFieldWrapper.field != null) {
2300+
if (StringUtils.isBlank(newValueStr)) {
2301+
newValue = configFieldWrapper.defaultValue;
2302+
} else {
2303+
newValue = FieldParser.value(newValueStr, configFieldWrapper.field);
2304+
}
2305+
existingValue = configFieldWrapper.field.get(pulsar.getConfiguration());
2306+
configFieldWrapper.field.set(pulsar.getConfiguration(), newValue);
2307+
} else {
2308+
// This case only occurs when it is a customized item.
2309+
// Since https://github.com/apache/pulsar/blob/master/pip/pip-300.md has not been cherry-picked, this
2310+
// case should never occur.
2311+
log.error("Skip update customized dynamic configuration {}/{} in memory, only trigger an event"
2312+
+ " listeners. Since PIP-300 has net been cherry-picked, this case should never occur",
2313+
configKey, newValueStr);
2314+
existingValue = configFieldWrapper.lastDynamicValue;
2315+
newValue = newValueStr == null ? configFieldWrapper.defaultValue : newValueStr;
2316+
}
2317+
// Record the latest dynamic config.
2318+
configFieldWrapper.lastDynamicValue = newValueStr;
2319+
2320+
if (newValueStr == null) {
2321+
log.info("Successfully remove the dynamic configuration {}, and revert to the default value",
2322+
configKey);
2323+
} else {
2324+
log.info("Successfully updated configuration {}/{}", configKey, newValueStr);
2325+
}
2326+
2327+
if (listener != null && !Objects.equals(existingValue, newValue)) {
2328+
// So far, all config items that related to configuration listeners, their default value is not null.
2329+
// And the customized config can be null before.
2330+
// So call "listener.accept(null)" is okay.
2331+
listener.accept(newValue);
2332+
}
2333+
} catch (Exception e) {
2334+
log.error("Failed to update config {}", configKey, e);
2335+
}
22882336
}
22892337

22902338
/**
@@ -2654,6 +2702,9 @@ private void updateManagedLedgerConfig() {
26542702
* On notification, listener should first check if config value has been changed and after taking appropriate
26552703
* action, listener should update config value with new value if it has been changed (so, next time listener can
26562704
* compare values on configMap change).
2705+
*
2706+
* Note: The new value that the {@param listener} may accept could be a null value.
2707+
*
26572708
* @param <T>
26582709
*
26592710
* @param configKey
@@ -2729,7 +2780,7 @@ public DelayedDeliveryTrackerFactory getDelayedDeliveryTrackerFactory() {
27292780
return delayedDeliveryTrackerFactory;
27302781
}
27312782

2732-
public static List<String> getDynamicConfiguration() {
2783+
public List<String> getDynamicConfiguration() {
27332784
return dynamicConfigurationMap.keys();
27342785
}
27352786

@@ -2742,27 +2793,34 @@ public Map<String, String> getRuntimeConfiguration() {
27422793
return configMap;
27432794
}
27442795

2745-
public static boolean isDynamicConfiguration(String key) {
2796+
public boolean isDynamicConfiguration(String key) {
27462797
return dynamicConfigurationMap.containsKey(key);
27472798
}
27482799

2749-
public static boolean validateDynamicConfiguration(String key, String value) {
2800+
public boolean validateDynamicConfiguration(String key, String value) {
27502801
if (dynamicConfigurationMap.containsKey(key) && dynamicConfigurationMap.get(key).validator != null) {
27512802
return dynamicConfigurationMap.get(key).validator.test(value);
27522803
}
27532804
return true;
27542805
}
27552806

2756-
private static ConcurrentOpenHashMap<String, ConfigField> prepareDynamicConfigurationMap() {
2807+
private ConcurrentOpenHashMap<String, ConfigField> prepareDynamicConfigurationMap() {
27572808
ConcurrentOpenHashMap<String, ConfigField> dynamicConfigurationMap =
27582809
ConcurrentOpenHashMap.<String, ConfigField>newBuilder().build();
2759-
for (Field field : ServiceConfiguration.class.getDeclaredFields()) {
2760-
if (field != null && field.isAnnotationPresent(FieldContext.class)) {
2761-
field.setAccessible(true);
2762-
if (field.getAnnotation(FieldContext.class).dynamic()) {
2763-
dynamicConfigurationMap.put(field.getName(), new ConfigField(field));
2810+
try {
2811+
for (Field field : ServiceConfiguration.class.getDeclaredFields()) {
2812+
if (field != null && field.isAnnotationPresent(FieldContext.class)) {
2813+
field.setAccessible(true);
2814+
if (field.getAnnotation(FieldContext.class).dynamic()) {
2815+
Object defaultValue = field.get(pulsar.getConfiguration());
2816+
dynamicConfigurationMap.put(field.getName(), new ConfigField(field, defaultValue));
2817+
}
27642818
}
27652819
}
2820+
} catch (IllegalArgumentException | IllegalAccessException ex) {
2821+
// This error never occurs.
2822+
log.error("Failed to initialize dynamic configuration map", ex);
2823+
throw new RuntimeException(ex);
27662824
}
27672825
return dynamicConfigurationMap;
27682826
}
@@ -3036,11 +3094,21 @@ public void unblockDispatchersOnUnAckMessages(List<PersistentDispatcherMultipleC
30363094

30373095
private static class ConfigField {
30383096
final Field field;
3097+
3098+
// It is the dynamic config value if set.
3099+
// It is null if has does not set a dynamic config, even if the value of "pulsar.config" is present.
3100+
volatile String lastDynamicValue;
3101+
3102+
// The default value of "pulsar.config", which is initialized when the broker is starting.
3103+
// After the dynamic config has been removed, revert the config to this default value.
3104+
final Object defaultValue;
3105+
30393106
Predicate<String> validator;
30403107

3041-
public ConfigField(Field field) {
3108+
public ConfigField(Field field, Object defaultValue) {
30423109
super();
30433110
this.field = field;
3111+
this.defaultValue = defaultValue;
30443112
}
30453113
}
30463114

pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/AdminApiDynamicConfigurationsTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import lombok.extern.slf4j.Slf4j;
2727
import org.apache.pulsar.broker.auth.MockedPulsarServiceBaseTest;
2828
import org.apache.pulsar.client.admin.PulsarAdminException;
29+
import org.awaitility.Awaitility;
2930
import org.testng.annotations.AfterMethod;
3031
import org.testng.annotations.BeforeMethod;
3132
import org.testng.annotations.Test;
@@ -69,4 +70,21 @@ public void TestDeleteInvalidDynamicConfiguration() {
6970
}
7071
}
7172
}
73+
74+
@Test
75+
public void testDeleteIntDynamicConfig() throws PulsarAdminException {
76+
// Record the default value;
77+
int defaultValue = pulsar.getConfig().getMaxConcurrentTopicLoadRequest();
78+
// Set dynamic config.
79+
int newValue = defaultValue + 1000;
80+
admin.brokers().updateDynamicConfiguration("maxConcurrentTopicLoadRequest", newValue + "");
81+
Awaitility.await().untilAsserted(() -> {
82+
assertEquals(pulsar.getConfig().getMaxConcurrentTopicLoadRequest(), newValue);
83+
});
84+
// Verify: it has been reverted to the default value.
85+
admin.brokers().deleteDynamicConfiguration("maxConcurrentTopicLoadRequest");
86+
Awaitility.await().untilAsserted(() -> {
87+
assertEquals(pulsar.getConfig().getMaxConcurrentTopicLoadRequest(), defaultValue);
88+
});
89+
}
7290
}

0 commit comments

Comments
 (0)