Yicong-Huang commented on code in PR #7122:
URL: https://github.com/apache/texera/pull/7122#discussion_r3773356916


##########
amber/src/main/scala/org/apache/texera/amber/engine/architecture/scheduling/RegionExecutionManager.scala:
##########
@@ -195,41 +205,41 @@ class RegionExecutionManager(
           }
       }.toSeq
 
-    val endWorkerFuture: Future[Unit] =
-      Future.collect(endWorkerRequests).unit
-
-    // 2. Send GracefulStops only after 1 has finished
-    val gracefulStopRequests: Future[Unit] =
-      endWorkerFuture.flatMap { _ =>
-        val gracefulStops =
-          regionExecution.getAllOperatorExecutions.flatMap {
-            case (_, opExec) =>
-              opExec.getWorkerIds.map { workerId =>
-                val actorRef = actorRefService.getActorRef(workerId)
-                // Remove the actorRef so that no other actors can find the 
worker and send messages.
-                actorRefService.removeActorRef(workerId)
-                // Restarted regions reuse actorId. Remove stale control 
channels so the
-                // coordinator does not reuse old control-message sequence 
numbers for new workers.
-                asyncRPCClient.inputGateway.removeControlChannel(workerId)
-                asyncRPCClient.outputGateway.removeControlChannel(workerId)
-                gracefulStop(actorRef, ScalaDuration(5, 
TimeUnit.SECONDS)).asTwitter()
-              }
-          }.toSeq
+    val terminationAttempt =
+      Future
+        .collect(endWorkerRequests)
+        .unit
+        .flatMap { _ =>
+          // 2. Only send GracefulStops after all EndWorkers have succeeded.
+          val gracefulStopRequests =
+            regionExecution.getAllOperatorExecutions.flatMap {
+              case (_, opExec) =>
+                opExec.getWorkerIds.map { workerId =>
+                  val actorRef = actorRefService.getActorRef(workerId)
+                  gracefulStop(actorRef, ScalaDuration(5, 
TimeUnit.SECONDS)).asTwitter()
+                }
+            }.toSeq
 
-        Future.collect(gracefulStops).unit
-      }
+          Future.collect(gracefulStopRequests).unit
+        }
+        .within(killTimeout)

Review Comment:
   Round 2 bounded each stage separately; this collapses both into one window 
(`grep -c "within(killTimeout)"` returns 2 at `d6a9695`, 1 here). The 6 s is 
now a shared budget. Whatever the EndWorker round-trip spends comes off 
`gracefulStop`, whose own deadline is 5 s (`:219`) — reachable only if 
EndWorker answers within 1 s.
   
   So a worker taking 4-5 s to stop, the slow-PVM case #6920 gives as its 
repro, now needs its EndWorker round-trip under 1-2 s. The retry cannot recover 
it: attempt 1 already sent the stop, so attempt 2's EndWorker reaches a 
stopping worker and never answers. On `main` that teardown just completed.
   
   Derive the outer window from the inner deadline instead of tuning a literal. 
Hoist gracefulStop's 5 s into a constant and set `DefaultTerminationTimeoutMs` 
to it plus a stated drain allowance, so the relationship cannot silently invert 
again.



##########
amber/src/main/scala/org/apache/texera/amber/engine/architecture/scheduling/RegionExecutionManager.scala:
##########
@@ -195,41 +205,41 @@ class RegionExecutionManager(
           }
       }.toSeq
 
-    val endWorkerFuture: Future[Unit] =
-      Future.collect(endWorkerRequests).unit
-
-    // 2. Send GracefulStops only after 1 has finished
-    val gracefulStopRequests: Future[Unit] =
-      endWorkerFuture.flatMap { _ =>
-        val gracefulStops =
-          regionExecution.getAllOperatorExecutions.flatMap {
-            case (_, opExec) =>
-              opExec.getWorkerIds.map { workerId =>
-                val actorRef = actorRefService.getActorRef(workerId)
-                // Remove the actorRef so that no other actors can find the 
worker and send messages.
-                actorRefService.removeActorRef(workerId)
-                // Restarted regions reuse actorId. Remove stale control 
channels so the
-                // coordinator does not reuse old control-message sequence 
numbers for new workers.
-                asyncRPCClient.inputGateway.removeControlChannel(workerId)
-                asyncRPCClient.outputGateway.removeControlChannel(workerId)
-                gracefulStop(actorRef, ScalaDuration(5, 
TimeUnit.SECONDS)).asTwitter()
-              }
-          }.toSeq
+    val terminationAttempt =
+      Future
+        .collect(endWorkerRequests)
+        .unit
+        .flatMap { _ =>
+          // 2. Only send GracefulStops after all EndWorkers have succeeded.
+          val gracefulStopRequests =
+            regionExecution.getAllOperatorExecutions.flatMap {
+              case (_, opExec) =>
+                opExec.getWorkerIds.map { workerId =>
+                  val actorRef = actorRefService.getActorRef(workerId)
+                  gracefulStop(actorRef, ScalaDuration(5, 
TimeUnit.SECONDS)).asTwitter()
+                }
+            }.toSeq
 
-        Future.collect(gracefulStops).unit
-      }
+          Future.collect(gracefulStopRequests).unit
+        }
+        .within(killTimeout)
 
-    // 3. Log whether the kills were successful
-    gracefulStopRequests.transform {
+    // 3. Cleanup only after graceful termination succeeds.

Review Comment:
   `Cleanup` is the noun; the sibling step comments are imperative.
   
   ```suggestion
       // 3. Clean up only after graceful termination succeeds.
   ```



##########
amber/src/test/scala/org/apache/texera/amber/engine/architecture/scheduling/RegionExecutionManagerSpec.scala:
##########
@@ -141,6 +141,45 @@ class RegionExecutionManagerSpec
     assert(workerState(fixture) == WorkerState.TERMINATED)
   }
 
+  it should "retry EndWorker when the termination timeout expires" in {
+    val fixture = createSingleRegionFixture(
+      endWorkerResponse = _ => None,
+      maxTerminationAttempts = 2,
+      killRetryBaseBackoffMs = fastRetryBackoffMs,
+      terminationTimeoutMs = 10L
+    )
+
+    launchRegion(fixture.manager)
+    val completion = requestRegionCompletion(fixture.manager)
+
+    val failure = intercept[IllegalStateException] {
+      await(completion)
+    }
+
+    assert(failure.getMessage.contains("could not be terminated after 2 
attempts"))
+    assert(fixture.rpcProbe.endWorkerCalls.size == 2)
+    assert(!fixture.manager.isCompleted)
+  }
+
+  it should "clean up control channels and actor refs after successful 
termination" in {

Review Comment:
   These three assertions are a strict subset of `"send gracefulStop only after 
EndWorker succeeds"` (`:101-104`), which already checks all of them plus the 
worker state.
   
   Despite the name it also does not reach `Coordinator.receive`: 
`CoordinatorHarness` carries its own `handleCleanupWorkerChannels` 
(`RegionExecutionManagerTestSupport.scala:171-179`), so deleting the case from 
`Coordinator.receive` still leaves the suite green.
   
   I would drop the test — it buys no coverage over `:101-104`.



##########
amber/src/test/scala/org/apache/texera/amber/engine/architecture/scheduling/RegionExecutionManagerSpec.scala:
##########
@@ -251,7 +293,8 @@ class RegionExecutionManagerSpec
     Time.withCurrentTimeFrozen { _ =>
       val fixture = createSingleRegionFixture(
         endWorkerResponse = _ => Some(transientEndWorkerFailure),
-        killRetryTimer = timer
+        killRetryTimer = timer,
+        terminationTimeoutMs = Long.MaxValue

Review Comment:
   Worth naming what `Long.MaxValue` means here. It reads as an arbitrary large 
number, but it is a sentinel: `Duration.fromMilliseconds(Long.MaxValue)` 
saturates to `Duration.Top`, and `within(Top)` short-circuits without 
scheduling.
   
   That matters, because `RecordingInlineTimer.scheduleOnce` runs whatever it 
is handed *inline*. A timeout task actually scheduled here would fire 
immediately and fail both frozen-time tests confusingly. A named constant or a 
one-line comment would keep the next reader from rediscovering it. (Mine to 
have caught last round.)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to