baiyangtx commented on PR #4282:
URL: https://github.com/apache/amoro/pull/4282#issuecomment-5033599633

   Thanks for the fix! The direction is correct: the previous `Map<String, 
ActionCoordinatorScheduler>` keyed by `action().getName()` silently overwrites 
entries when multiple coordinators share the same action name but target 
different `TableFormat`s (exactly the mixed-iceberg case in
     #4281). Switching to `Map<String, List<ActionCoordinatorScheduler>>` + 
dispatching by `TableFormat` addresses the root cause.
   
     A few things to address before merge.
   
     ## 1. Missing unit tests (blocker)
   
     The PR description checks "Add some test cases … including negative and 
positive cases" but the diff contains no test changes. Please add at least one 
test that pins down the regression:
   
     - Register two `MockActionCoordinator`s with the **same** 
`action().getName()` but different `formatSupported` (e.g. one accepts only 
`PAIMON`, one only `MIXED_ICEBERG`).
     - Fire `handleTableAdded` / `handleStatusChanged` with a `TableRuntime` of 
each format.
     - Assert each event lands **only** on the matching scheduler.
   
     Without this, the bug can silently regress again if `actionCoordinators` 
is ever re-keyed or the dispatch helpers are refactored.
   
     ## 2. `(action, format)` uniqueness is not enforced anywhere
   
     `AbstractPluginManager` only enforces uniqueness of `plugin.name()` (see 
`foundedPlugins` at `AbstractPluginManager.java:344-358` and `pluginConfigs` at 
`AbstractPluginManager.java:107-115`). Two `ActionCoordinator`s can 
legitimately share the same `action().getName()` and overlapping
     `formatSupported(...)` as long as their `plugin.name()` differ. In that 
case `findScheduler` returns the first match via `findFirst` and the second 
coordinator is silently ignored — the same class of bug as before, just harder 
to see (both schedulers exist in the map).
   
     Please validate `(action().getName(), format)` uniqueness at registration 
time (in `initialize` and `installActionCoordinator`) and fail fast, or at 
minimum `LOG.warn` with both coordinators' plugin names so operators can spot 
the collision on startup.
   
     ## 3. `recoverProcesses` now silently drops records when no 
format-matching scheduler exists
   
     ```java
     if (tableRuntime != null) {
       ActionCoordinatorScheduler scheduler =
           findScheduler(processMeta.getProcessType(), 
tableRuntime.getFormat());
       if (scheduler != null) {
         recoverProcess(tableRuntime, scheduler, processMeta);
       }
     }
   ```
   
    Previously any scheduler registered under that action name would recover 
the process; now the format also has to match. This is desirable, but if a 
coordinator's format support changes across releases, historical process rows 
will be skipped with no signal. Please add a LOG.warn in the
     scheduler == null branch including processId, processType, and 
tableRuntime.getFormat() to keep this diagnosable.
   
     ProcessService.java:193-204
   
    ##   4. executeOrTraceProcess.onProcessFinished — silent behavior change 
when tableRuntime == null
   
   ```java
     ActionCoordinatorScheduler scheduler = null;
     if (process.getTableRuntime() != null) {
       scheduler =
           findScheduler(store.getAction().getName(), 
process.getTableRuntime().getFormat());
     }
   ```
   
     Before, a null tableRuntime could still resolve a scheduler by action name 
and trigger retry. After this change we never retry in that branch. That's 
probably the intended behavior (we can't classify format without a runtime), 
but it deserves a LOG.info or LOG.debug so we don't wonder why
     a FAILED process stopped retrying.
   
     ProcessService.java:292-316
   
    ##   5. Inner ArrayList is not concurrent-safe
   
     actionCoordinators is a ConcurrentHashMap, but each value is a plain new 
ArrayList<>(). Today writes happen in initialize() (single-threaded startup) 
and @VisibleForTesting installActionCoordinator, while reads happen from event 
handlers on runtime threads. That's safe only if init
     strictly happens-before any handler dispatch. If installActionCoordinator 
(or a future hot-install path) ever runs after startup, the current structure 
will race.
   
     Either:
   
     - switch the inner list to CopyOnWriteArrayList, or
     - add a Javadoc/inline comment stating "no writes after initialize() 
returns" and rely on the existing lifecycle guarantee.
   
     ProcessService.java:63-64, 166-169, 514-518
   
    ##   6. @VisibleForTesting return type change ripples to callers
   
     getActionCoordinators() changed from Map<String, 
ActionCoordinatorScheduler> to Map<String, List<ActionCoordinatorScheduler>>. 
Please double-check every existing test that consumes this (e.g. under 
amoro-ams/src/test/java/org/apache/amoro/server/process/... and 
amoro-paimon-internal
     integration tests) still compiles and asserts sensibly — a local mvn -pl 
amoro-ams,amoro-paimon-internal test-compile should be enough.
   
     ———
   
     Minimum bar to merge
   
     1. Add the regression test from (1).
     2. Enforce (action, format) uniqueness at registration (2).
     3. Add the missing LOG.warn in recoverProcesses (3).
   
     (4), (5), (6) are nice-to-haves.


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