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"));
+    }
+}

Reply via email to