diff --git a/remote-config/remote-config-core/src/main/java/datadog/remoteconfig/state/ProductState.java b/remote-config/remote-config-core/src/main/java/datadog/remoteconfig/state/ProductState.java index 91a70c7be7b..e7d1c3d861a 100644 --- a/remote-config/remote-config-core/src/main/java/datadog/remoteconfig/state/ProductState.java +++ b/remote-config/remote-config-core/src/main/java/datadog/remoteconfig/state/ProductState.java @@ -57,44 +57,34 @@ public boolean apply( PollingRateHinter hinter) { errors = null; - List configBeenUsedByProduct = new ArrayList<>(); - List changedKeys = new ArrayList<>(); boolean changesDetected = false; - // Step 1: Detect all changes + // First, validate configurations and separate valid from invalid + List validKeysToApply = new ArrayList<>(); + List validKeysUnchanged = new ArrayList<>(); for (ParsedConfigKey configKey : relevantKeys) { try { RemoteConfigResponse.Targets.ConfigTarget target = getTargetOrThrow(fleetResponse, configKey); - configBeenUsedByProduct.add(configKey); - if (isTargetChanged(configKey, target)) { - changesDetected = true; - changedKeys.add(configKey); + validKeysToApply.add(configKey); + } else { + validKeysUnchanged.add(configKey); } } catch (ReportableException e) { recordError(e); + // Invalid configs will be removed below } } - // Step 2: For products other than ASM_DD, apply changes immediately - if (product != Product.ASM_DD) { - for (ParsedConfigKey configKey : changedKeys) { - try { - byte[] content = getTargetFileContent(fleetResponse, configKey); - callListenerApplyTarget(fleetResponse, hinter, configKey, content); - } catch (ReportableException e) { - recordError(e); - } - } - } - - // Step 3: Remove obsolete configurations (for all products) - // For ASM_DD, this is critical: removes MUST happen before applies to prevent - // duplicate rule warnings from the ddwaf rule parser and causing memory spikes. + // Remove obsolete or invalid configurations before applying new ones + // Remove configs that are: (1) not in relevantKeys OR (2) in relevantKeys but failed validation List keysToRemove = cachedTargetFiles.keySet().stream() - .filter(configKey -> !configBeenUsedByProduct.contains(configKey)) + .filter( + configKey -> + !validKeysToApply.contains(configKey) + && !validKeysUnchanged.contains(configKey)) .collect(Collectors.toList()); for (ParsedConfigKey configKey : keysToRemove) { @@ -102,22 +92,17 @@ public boolean apply( callListenerRemoveTarget(hinter, configKey); } - // Step 4: For ASM_DD, apply changes AFTER removes - // TODO: This is a temporary solution. The proper fix requires better synchronization - // between remove and add/update operations. This should be discussed - // with the guild to determine the best long-term design approach. - if (product == Product.ASM_DD) { - for (ParsedConfigKey configKey : changedKeys) { - try { - byte[] content = getTargetFileContent(fleetResponse, configKey); - callListenerApplyTarget(fleetResponse, hinter, configKey, content); - } catch (ReportableException e) { - recordError(e); - } + // Then, apply valid configurations that changed + for (ParsedConfigKey configKey : validKeysToApply) { + try { + changesDetected = true; + byte[] content = getTargetFileContent(fleetResponse, configKey); + callListenerApplyTarget(fleetResponse, hinter, configKey, content); + } catch (ReportableException e) { + recordError(e); } } - // Step 5: Commit if there were changes if (changesDetected) { try { callListenerCommit(hinter);