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 1196e7124 fix(instance): apply a single deadline to resource counts
(#2525)
1196e7124 is described below
commit 1196e7124f679b313b413ddbdce2589030253a4d
Author: 0 <[email protected]>
AuthorDate: Sat Aug 22 15:30:59 2026 +0800
fix(instance): apply a single deadline to resource counts (#2525)
fillCountsInParallel submitted one task per instance and then waited on
each future with its own 3s get(). A batch of hung vendor calls therefore cost
3s * instance-count of response latency.
Submit the whole batch through invokeAll with one shared deadline: the
request waits at most COUNT_TIMEOUT_SECONDS in total, the tasks that miss the
deadline are cancelled (interrupted) by invokeAll and their rows are marked
counts-unavailable like any other failure.
Fixes #2495
---
.../rocketmq/studio/instance/InstanceService.java | 50 ++++++++++++++--------
.../studio/instance/InstanceServiceTest.java | 29 +++++++++++++
2 files changed, 61 insertions(+), 18 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
index b31464a3b..6a7b48336 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
@@ -47,12 +47,12 @@ import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
@Slf4j
@Service
@@ -116,27 +116,41 @@ public class InstanceService {
if (instances.isEmpty()) {
return;
}
- List<InstanceVO> pending = new ArrayList<>(instances);
- List<Future<?>> futures = new ArrayList<>(instances.size());
- for (InstanceVO instance : pending) {
- futures.add(countExecutor.submit(() -> fillCounts(instance)));
+ List<Callable<Void>> tasks = instances.stream()
+ .<Callable<Void>>map(instance -> () -> {
+ fillCounts(instance);
+ return null;
+ })
+ .toList();
+ List<Future<Void>> futures;
+ try {
+ // A single deadline for the whole batch. Waiting per future would
let one hung
+ // vendor add COUNT_TIMEOUT_SECONDS to the response for every
instance.
+ futures = countExecutor.invokeAll(tasks, COUNT_TIMEOUT_SECONDS,
TimeUnit.SECONDS);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ instances.forEach(instance ->
instance.setResourceCountsAvailable(false));
+ return;
}
- for (int i = 0; i < pending.size(); i++) {
- InstanceVO instance = pending.get(i);
- try {
- futures.get(i).get(COUNT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
- } catch (TimeoutException ex) {
- futures.get(i).cancel(true);
+ for (int i = 0; i < futures.size(); i++) {
+ InstanceVO instance = instances.get(i);
+ Future<Void> future = futures.get(i);
+ if (future.isCancelled()) {
+ // Missed the shared deadline; invokeAll already interrupted
the task.
instance.setResourceCountsAvailable(false);
log.warn("Resource counts timed out after {}s for instance {}",
COUNT_TIMEOUT_SECONDS, instance.getId());
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- instance.setResourceCountsAvailable(false);
- } catch (ExecutionException ex) {
- instance.setResourceCountsAvailable(false);
- log.warn("Failed to load resource counts for instance {}: {}",
- instance.getId(), ex.getMessage());
+ } else {
+ try {
+ future.get();
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ instance.setResourceCountsAvailable(false);
+ } catch (ExecutionException ex) {
+ instance.setResourceCountsAvailable(false);
+ log.warn("Failed to load resource counts for instance {}:
{}",
+ instance.getId(), ex.getMessage());
+ }
}
}
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
index 405569b9f..d490791f3 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
@@ -41,6 +41,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDateTime;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -49,6 +50,7 @@ import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -289,6 +291,33 @@ class InstanceServiceTest {
assertThat(result.get(0).getConsumerGroupCount()).isZero();
}
+ @Test
+ void listInstancesShouldApplyASingleDeadlineToSlowCountsTest() throws
InterruptedException {
+ // Three hung providers: waiting per future would cost 3s each (9s
total); a shared
+ // deadline must bound the whole batch to roughly one timeout.
+ List<InstanceVO> slow = new ArrayList<>();
+ for (long id = 20L; id < 23L; id++) {
+ InstanceVO vo = InstanceVO.builder().name("slow-" + id).build();
+ vo.setId(id);
+ slow.add(vo);
+ }
+ when(instanceRepository.findAll()).thenReturn(slow);
+
when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
+ when(instanceProvider.countTopics(anyString())).thenAnswer(invocation
-> {
+ Thread.sleep(10_000);
+ return 0;
+ });
+
+ long start = System.nanoTime();
+ List<InstanceVO> result = instanceService.listInstances(null, null);
+ long elapsedMillis = (System.nanoTime() - start) / 1_000_000;
+
+ assertThat(elapsedMillis)
+ .as("batch must not wait one timeout per instance")
+ .isLessThan(2L * InstanceService.COUNT_TIMEOUT_SECONDS * 1000);
+ assertThat(result).allSatisfy(vo ->
assertThat(vo.isResourceCountsAvailable()).isFalse());
+ }
+
@Test
void createInstanceShouldThrowWhenRequestIsNull() {
assertThatThrownBy(() -> instanceService.createInstance(null))