bitflicker64 commented on code in PR #357:
URL: 
https://github.com/apache/hugegraph-computer/pull/357#discussion_r3896555147


##########
computer/computer-core/src/main/java/org/apache/hugegraph/computer/core/worker/WorkerService.java:
##########
@@ -176,7 +186,6 @@ public synchronized void close() {
                 this.computeManager.close();
             } else {
                 LOG.warn("The computeManager is null");
-                return;
             }

Review Comment:
   ⚠️ Dropping the early `return` is the right fix, but the cleanup it unblocks 
stops at the first manager that fails. `Managers.closeAll()` 
(`computer/computer-core/src/main/java/org/apache/hugegraph/computer/core/manager/Managers.java:69`)
 calls `manager.close(config)` in a bare loop with no per-manager isolation, 
and on exactly this partial-init path `DataServerManager.close(config)` calls 
`address()` -> `TransportConnectionManager.getServer()`, which throws when the 
transport server was never bound.
   
   Evidence — harness compiled against this head (registers `DataServerManager` 
holding a never-started `TransportConnectionManager`, plus one more manager 
behind it, then calls `closeAll`):
   
   ```
   closeAll threw: java.lang.IllegalArgumentException: The TransportServer has 
not been initialized yet
   later manager closed = false
   ```
   
   In `initManagers()` the managers registered after `DataServerManager` are 
`DataClientManager`, `SendSortManager`, `MessageSendManager`, `SnapshotManager` 
and `WorkerInputManager`. So when `initAll()` fails before the data server 
binds (`transport.server_port` already in use is the obvious trigger), none of 
them are closed — and `DataClientManager.close()` is what calls 
`sender.close()`, i.e. the `QueuedMessageSender` send-executor this PR hardens 
is one of the threads left running. The surrounding `catch (Exception e)` 
swallows the `IllegalArgumentException`, so nothing in the log says cleanup 
stopped halfway.
   
   Requested change: make `Managers.closeAll()` close every manager, wrapping 
each `manager.close(config)` in its own try/catch and rethrowing the first 
failure with the rest attached as suppressed (or, narrower, make 
`DataServerManager.close()` a no-op when the server never bound). Please add a 
regression that fails `initAll()` before `DataServerManager` binds and asserts 
the managers registered after it were still closed.



##########
computer/computer-core/src/main/java/org/apache/hugegraph/computer/core/sender/QueuedMessageSender.java:
##########
@@ -63,15 +66,30 @@ public void init() {
     }
 
     public void close() {
+        this.closed = true;
         this.sendExecutor.interrupt();
         try {
             this.sendExecutor.join();
         } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
             throw new ComputerException("Interrupted when waiting for " +
                                         "send-executor to stop", e);
         }
     }
 
+    @Override
+    public void checkFatal() {
+        Throwable error = this.fatalError.get();
+        if (error != null) {
+            throw new ComputerException("Send-executor encountered fatal 
error",
+                                        error);
+        }
+    }
+
+    private void recordFatal(Throwable error) {
+        this.fatalError.compareAndSet(null, error);

Review Comment:
   ⚠️ `recordFatal()` stores the error but leaves every control future the 
channels are already holding unresolved, so the new fail-fast path only takes 
effect on the *next* caller. When the executor dies here, an in-flight or 
queued START/FINISH is never completed, and 
`MessageSendManager.sendControlMessageToWorkers()` is already parked in 
`future.get(timeout, MILLISECONDS)` — the `this.sender.checkFatal()` calls 
added to `startSend()`/`finishSend()` run only after that wait returns, so they 
can neither shorten it nor surface the real cause.
   
   Evidence — probe compiled against this head: one channel whose client always 
returns `false` from `send()` (so the executor parks in 
`waitAnyClientNotBusy()`), one queued data message plus a FINISH, then a 
spurious interrupt with `closed == false`:
   
   ```
   executor state before interrupt = WAITING
   ComputerException: Interrupted when waiting any client not busy
       at QueuedMessageSender.waitAnyClientNotBusy(QueuedMessageSender.java:256)
       at QueuedMessageSender$Sender.run(QueuedMessageSender.java:206)
   executor alive after fatal = false
   FINISH future done        = false
   checkFatal()              = Send-executor encountered fatal error
   FINISH still incomplete after 3004 ms
   ```
   
   With defaults the caller therefore blocks `transport.sync_request_timeout` = 
10s for START and `10_000 * transport.max_pending_requests(8)` = 80s for FINISH 
(`TransportConf.timeoutFinishSession()`), then reports `Timeout(80000ms) to 
wait for controlling message(FINISH) to finished` rather than the fatal error 
that was recorded 80 seconds earlier. `close()` has the same gap: it sets 
`closed = true` and joins without resolving pending control futures.
   
   Requested change: when the first fatal error is recorded (and on the 
`close()` path), walk `channels` and hand the error to each one — 
`channel.failDataSend(error)` already fails the registered control future and 
poisons the channel against later ones — so `sendControlMessageToWorkers()` 
returns immediately with the real cause. Please add a regression that 
terminates the send-executor with a FINISH already queued and asserts the 
future completes exceptionally with the recorded fatal error.



##########
computer/computer-core/src/main/java/org/apache/hugegraph/computer/core/worker/WorkerService.java:
##########
@@ -101,7 +108,9 @@ public synchronized void init(Config config) {
             this.workerInfo = new ContainerInfo();
 
             LOG.info("{} Start to initialize worker", this);
-            this.bsp4Worker = new Bsp4Worker(this.config, this.workerInfo);
+            if (this.bsp4Worker == null) {

Review Comment:
   🧹 This null check and the package-private `WorkerService(Bsp4Worker)` 
constructor at line 91 exist only so 
`WorkerServiceTest#testInitFailsAfterRegistration` can inject a mock; 
`initManagers()` was widened from `private` to package-private at line 320 for 
the same test. Two consequences worth avoiding: production `init()` now carries 
a test-only branch, and because `bsp4Worker` is never reset to `null` anywhere 
in this class, an instance whose `init()` threw — which leaves `inited == 
false`, so the `E.checkArgument(!this.inited, ...)` guard at line 102 still 
permits a retry — would re-`init()` against the already-closed `Bsp4Worker` 
from the failed attempt instead of building a fresh one.
   
   Requested change: keep `init()` constructing the `Bsp4Worker` 
unconditionally through a package-private factory method (e.g. `Bsp4Worker 
newBsp4Worker() { return new Bsp4Worker(this.config, this.workerInfo); }`) and 
have the test override it — the test already subclasses `WorkerService` to 
override `initManagers()`, so no extra seam is needed and the production path 
keeps no test-only branch.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to