This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 84b573363 fix(alerting): survive malformed snapshot-retention
configuration (#4647)
84b573363 is described below
commit 84b573363f024ae6b794f00a3051b4699f8fcd45
Author: Apulupie <[email protected]>
AuthorDate: Thu Oct 1 17:24:25 2026 +0800
fix(alerting): survive malformed snapshot-retention configuration (#4647)
cleanUpSnapshots parses studio.alerting.snapshot-retention with an
unguarded Duration.parse on every scheduled run. A value like "24h"
(the natural spelling, vs. the required ISO-8601 "PT24H") throws a
DateTimeParseException on every hourly invocation, so snapshot cleanup
never runs again and the snapshot table grows without bound until the
config is fixed and the process restarted.
Catch the parse failure, log the offending value once per run, and skip
the run instead of guessing a deletion horizon.
Co-authored-by: Frun1na <[email protected]>
---
.../studio/cluster/metrics/CollectorScheduler.java | 12 +++++++++-
.../cluster/metrics/CollectorSchedulerTest.java | 28 ++++++++++++++++++++++
2 files changed, 39 insertions(+), 1 deletion(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/CollectorScheduler.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/CollectorScheduler.java
index 44301997b..d5e982569 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/CollectorScheduler.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/CollectorScheduler.java
@@ -48,6 +48,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.time.Duration;
import java.time.Instant;
+import java.time.format.DateTimeParseException;
import java.util.Set;
/** Runs independent, bounded collection jobs for each configured instance. */
@@ -262,7 +263,16 @@ public class CollectorScheduler {
@Scheduled(fixedDelayString =
"${studio.alerting.snapshot-cleanup-interval:PT1H}")
public void cleanUpSnapshots() {
- Duration retention = Duration.parse(properties.getSnapshotRetention());
+ Duration retention;
+ try {
+ retention = Duration.parse(properties.getSnapshotRetention());
+ } catch (DateTimeParseException | NullPointerException error) {
+ // A malformed value must not turn the hourly scheduler into a
permanent failure
+ // loop; skip this run and keep the old snapshots rather than
guessing a horizon.
+ log.warn("Ignoring malformed studio.alerting.snapshot-retention
'{}': {}", properties.getSnapshotRetention(),
+ error.getMessage());
+ return;
+ }
if (retention.isNegative() || retention.isZero()) {
return;
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/CollectorSchedulerTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/CollectorSchedulerTest.java
index 33fa2ed72..8273169d3 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/CollectorSchedulerTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/CollectorSchedulerTest.java
@@ -35,9 +35,11 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
@@ -399,6 +401,32 @@ class CollectorSchedulerTest {
}
}
+ @Test
+ void cleanUpSnapshotsSkipsRunWhenRetentionIsMalformedTest() {
+ AlertingProperties properties = new AlertingProperties();
+ properties.setSnapshotRetention("24h");
+ MetricSnapshotRepository snapshots =
mock(MetricSnapshotRepository.class);
+ CollectorScheduler scheduler = new CollectorScheduler(properties,
mock(InstanceRepository.class), List.of(),
+ List.of(), snapshots, mock(NativeAlertProcessor.class),
mock(AlertCollectionLease.class));
+
+ assertDoesNotThrow(scheduler::cleanUpSnapshots);
+ verify(snapshots, never()).deleteBefore(any());
+ }
+
+ @Test
+ void cleanUpSnapshotsDeletesBeforeRetentionHorizonTest() {
+ AlertingProperties properties = new AlertingProperties();
+ properties.setSnapshotRetention("PT24H");
+ MetricSnapshotRepository snapshots =
mock(MetricSnapshotRepository.class);
+ CollectorScheduler scheduler = new CollectorScheduler(properties,
mock(InstanceRepository.class), List.of(),
+ List.of(), snapshots, mock(NativeAlertProcessor.class),
mock(AlertCollectionLease.class));
+
+ scheduler.cleanUpSnapshots();
+
+ verify(snapshots).deleteBefore(argThat(horizon ->
horizon.isAfter(Instant.now().minusSeconds(24 * 3600 + 600))
+ && horizon.isBefore(Instant.now().minusSeconds(24 * 3600 -
600))));
+ }
+
private static MetricSample sampleFor(InstanceVO instance) {
return new MetricSample("nameserver.availability",
AlertDomain.CLUSTER, instance.getName(), null,
null, 1D, MetricAvailability.AVAILABLE, Instant.now());