davidzollo opened a new pull request, #12028:
URL: https://github.com/apache/seatunnel/pull/12028

   ### Purpose
   
   Adds Zeta E2E regression coverage for the classloader leak fixed by #11812 
("[Fix][Zeta] Release classloaders after failed task deployment"). This is one 
PR in a series adding E2E regression tests for previously-unguarded Zeta engine 
bug fixes (see #12027 for the first in the series, which this PR follows in 
house style).
   
   ### What #11812 fixed
   
   `TaskExecutionService#deployTask` deserializes every task in a `TaskGroup` 
in a loop, acquiring a classloader reference for each task's jars right before 
deserializing that task's data. Before #11812, if a later task in the group 
failed to deserialize (or its context publication failed), the classloader 
references already acquired for the earlier tasks in that same attempt were 
silently dropped instead of released — the outer `catch (Throwable t)` in 
`deployTask` never called `classLoaderService.releaseClassLoader(...)` for 
them. Every failed deployment attempt against a multi-task group leaked one 
reference per already-processed task, and because nothing else in the system 
ever releases a reference for a `TaskGroupContext` that was never published, 
the leak was permanent and cumulative across repeated attempts.
   
   ### What coverage was missing before this PR
   
   `ClassLoaderEnableCacheModeIT` / `ClassLoaderDisableCacheModeIT` 
(`seatunnel-e2e/.../e2e/classloader/`) already prove classloader/thread counts 
stay bounded across 10 repeated **successful** job restarts (submit, run to 
completion, resubmit). That is a different code path entirely: their trigger is 
a job that runs and finishes; this bug only manifests when deployment of a 
multi-task group **fails partway through**, after some tasks' classloaders were 
already acquired. Those tests cannot see this regression because they never 
construct a failing deployment. The only existing coverage of this exact bug is 
the unit test that shipped with #11812 itself 
(`TaskExecutionServiceTest#testDeployTaskReleasesClassLoadersWhenDeserializationFails`),
 which is single-node, single-attempt, and bypasses the E2E/cluster layer 
entirely.
   
   ### What this test does
   
   
`TaskDeploymentClassLoaderLeakIT#testFailedMultiTaskDeploymentDoesNotLeakClassLoaders`:
   
   1. Boots a real 2-node cluster (one master, one worker) via 
`SeaTunnelServerStarter`, matching the house style already established by 
`SplitClusterPendingJobLifecycleFailoverIT` (#12027).
   2. Obtains the worker's real `TaskExecutionService` and 
`DefaultClassLoaderService` directly (`deployTask` runs on the worker in 
production — the master issues it as an RPC), since engineering a specific 
task's deserialization to fail needs white-box control over the serialized 
payload.
   3. Repeats 10 times (matching `ClassLoaderITBase`'s iteration count): builds 
a 3-task `TaskGroup` where the first two tasks are minimal valid `Task` 
implementations, each with its own jar (standing in for "earlier tasks" that 
successfully acquire a classloader), and the third task's serialized payload is 
a plain `String` instead of a `Task`. Deploying that group makes `deployTask`'s 
deserialization loop throw `ClassCastException` on the third task — after the 
first two tasks' classloaders have already been acquired. Each attempt uses a 
fresh jobId/jars/`TaskGroupLocation` so no state is shared between attempts.
   4. After every attempt, asserts:
      - `deployTask` reports failure and never publishes a `TaskGroupContext` 
(`TaskGroupContextNotFoundException` on lookup).
      - The classloader reference count for each of the 3 tasks' jars is 
exactly 0 (`DefaultClassLoaderService#queryClassLoaderReferenceCount`) — the 
same precise signal #11812's own unit test uses.
      - The service's total live classloader count 
(`DefaultClassLoaderService#queryClassLoaderCount()`) has returned to the 
pre-loop baseline — a coarser, `ClassLoaderITBase`-style bounded-growth check. 
Classloader cache mode is disabled for this test specifically so a released 
classloader is actually evicted from the cache (the engine's default cache mode 
never evicts a cached entry, which would mask this signal), making this a 
meaningful additional check on top of the exact reference-count assertion.
   
   All of these assertions are synchronous — `deployTask`'s failure path 
releases classloaders in the same `catch` block before returning — so no 
`Awaitility` polling is needed for them; `Awaitility` is only used to wait for 
the 2-node cluster to form at startup.
   
   ### Why the trigger is reliable
   
   The "serialize a `String` where a `Task` is expected" mechanism is not a 
synthetic exception — it is a genuine deserialization/type mismatch, and it is 
the exact mechanism #11812's own accompanying unit test uses to reproduce this 
bug. I additionally verified the underlying call path 
(`com.hazelcast.jet.impl.execution.init.CustomClassLoadedObject#deserializeWithCustomClassLoader`,
 which every task in this test goes through since all 3 tasks use non-empty 
jars) by decompiling it: it deserializes the `String` successfully and returns 
it as `Object`; the `ClassCastException` actually fires at 
`TaskExecutionService.deployTask`'s own implicit checkcast on the assignment to 
the `Task task` local variable, immediately after the classloader for that task 
has already been recorded in `deployTask`'s `acquiredClassLoaderJars` list. 
Reusing a proven mechanism (rather than inventing a new poison-task type) 
minimizes the risk that this test's trigger is itself unreliable or diverges 
from what
  the original fix actually guards against.
   
   ### Test plan
   
   - `./mvnw spotless:apply -pl 
seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base -am -nsu` — 
BUILD SUCCESS.
   - `./mvnw install -pl 
seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base -am -nsu 
-Dmaven.gitcommitid.skip=true -DskipTests -Dmaven.test.skip=true 
-Dspotless.check.skip=true -T 3C` — BUILD SUCCESS.
   - The test itself is not run locally per this series' convention (Apache 
SeaTunnel local-verification policy restricts local runs to 
formatting/compilation only); it will run under this PR's GitHub Actions CI 
(`seatunnel-e2e/seatunnel-engine-e2e` job).
   


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