This is an automated email from the ASF dual-hosted git repository.
rpuch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 78472d85c1 IGNITE-23652 Don't swallow component stop exception (#4703)
78472d85c1 is described below
commit 78472d85c1b1fb00fb30396a90d3333a86c6661d
Author: Roman Puchkovskiy <[email protected]>
AuthorDate: Tue Nov 12 11:51:58 2024 +0400
IGNITE-23652 Don't swallow component stop exception (#4703)
---
.../ignite/internal/app/LifecycleManager.java | 17 +++---
.../ignite/internal/app/LifecycleManagerTest.java | 62 ++++++++++++++++++++++
2 files changed, 72 insertions(+), 7 deletions(-)
diff --git
a/modules/runner/src/main/java/org/apache/ignite/internal/app/LifecycleManager.java
b/modules/runner/src/main/java/org/apache/ignite/internal/app/LifecycleManager.java
index 7b76bb0f93..dd18b5d4f4 100644
---
a/modules/runner/src/main/java/org/apache/ignite/internal/app/LifecycleManager.java
+++
b/modules/runner/src/main/java/org/apache/ignite/internal/app/LifecycleManager.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.app;
import static java.util.Collections.reverse;
import static java.util.concurrent.CompletableFuture.allOf;
+import static org.apache.ignite.internal.util.CompletableFutures.copyStateTo;
import static org.apache.ignite.internal.util.IgniteUtils.stopAsync;
import java.util.ArrayList;
@@ -52,7 +53,7 @@ class LifecycleManager implements StateProvider {
*/
private final List<IgniteComponent> startedComponents = new ArrayList<>();
- private final List<CompletableFuture<Void>> allComponentsStartFuture = new
ArrayList<>();
+ private final List<CompletableFuture<Void>> allComponentsStartFutures =
new ArrayList<>();
private final CompletableFuture<Void> stopFuture = new
CompletableFuture<>();
@@ -84,7 +85,7 @@ class LifecycleManager implements StateProvider {
startedComponents.add(component);
CompletableFuture<Void> future =
component.startAsync(componentContext);
- allComponentsStartFuture.add(future);
+ allComponentsStartFutures.add(future);
return future;
}
}
@@ -131,10 +132,10 @@ class LifecycleManager implements StateProvider {
* @return Future that will be completed when all components start futures
will be completed.
*/
synchronized CompletableFuture<Void> allComponentsStartFuture() {
- return
allOf(allComponentsStartFuture.toArray(CompletableFuture[]::new))
+ return
allOf(allComponentsStartFutures.toArray(CompletableFuture[]::new))
.whenComplete((v, e) -> {
synchronized (this) {
- allComponentsStartFuture.clear();
+ allComponentsStartFutures.clear();
}
});
}
@@ -148,7 +149,7 @@ class LifecycleManager implements StateProvider {
State currentStatus = status.getAndSet(State.STOPPING);
if (currentStatus != State.STOPPING) {
- stopAllComponents(componentContext);
+ initiateAllComponentsStop(componentContext);
}
return stopFuture;
@@ -158,9 +159,11 @@ class LifecycleManager implements StateProvider {
* Calls {@link IgniteComponent#beforeNodeStop()} and then {@link
IgniteComponent#stopAsync(ComponentContext)} for all components in
* start-reverse-order.
*
+ * <p>Does NOT wait for the async stop to be completed. To track it,
{@link #stopFuture} is used.
+ *
* @param componentContext Component context.
*/
- private synchronized void stopAllComponents(ComponentContext
componentContext) {
+ private synchronized void initiateAllComponentsStop(ComponentContext
componentContext) {
List<IgniteComponent> components = new ArrayList<>(startedComponents);
reverse(components);
@@ -173,6 +176,6 @@ class LifecycleManager implements StateProvider {
}
stopAsync(componentContext, components)
- .whenComplete((v, e) -> stopFuture.complete(null));
+ .whenComplete(copyStateTo(stopFuture));
}
}
diff --git
a/modules/runner/src/test/java/org/apache/ignite/internal/app/LifecycleManagerTest.java
b/modules/runner/src/test/java/org/apache/ignite/internal/app/LifecycleManagerTest.java
new file mode 100644
index 0000000000..ef657dbfbd
--- /dev/null
+++
b/modules/runner/src/test/java/org/apache/ignite/internal/app/LifecycleManagerTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.app;
+
+import static java.util.concurrent.CompletableFuture.failedFuture;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrow;
+import static
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.manager.ComponentContext;
+import org.apache.ignite.internal.manager.IgniteComponent;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.junit.jupiter.api.Test;
+
+class LifecycleManagerTest extends BaseIgniteAbstractTest {
+ private final LifecycleManager lifecycleManager = new
LifecycleManager("test");
+
+ private final ComponentContext context = new ComponentContext();
+
+ @Test
+ void startExceptionFromComponentIsPropagatedToStartFuture() throws
Exception {
+ IgniteComponent componentFailingStart = mock(IgniteComponent.class);
+
when(componentFailingStart.startAsync(context)).thenReturn(failedFuture(new
RuntimeException("Oops")));
+
+ CompletableFuture<Void> startFuture =
lifecycleManager.startComponentsAsync(context, componentFailingStart);
+
+ assertThat(startFuture, willThrow(RuntimeException.class, "Oops"));
+ assertThat(lifecycleManager.allComponentsStartFuture(),
willThrow(RuntimeException.class, "Oops"));
+ }
+
+ @Test
+ void stopExceptionFromComponentIsPropagatedToStopFuture() throws Exception
{
+ IgniteComponent componentFailingStop = mock(IgniteComponent.class);
+
when(componentFailingStop.startAsync(context)).thenReturn(nullCompletedFuture());
+
when(componentFailingStop.stopAsync(context)).thenReturn(failedFuture(new
RuntimeException("Oops")));
+
+ lifecycleManager.startComponentsAsync(context, componentFailingStop);
+ lifecycleManager.onStartComplete();
+
+ CompletableFuture<Void> stopFuture =
lifecycleManager.stopNode(context);
+
+ assertThat(stopFuture, willThrow(RuntimeException.class, "Oops"));
+ }
+}