jjj-n opened a new issue, #12924:
URL: https://github.com/apache/seatunnel/issues/12924

   ### Search before asking
   
   - [x] I searched the related open/closed issues and PRs before reporting. No 
matching atomic-publication fix was found within those searches (2026-10-20).
   
   ### What happened
   
   The E2E restore path copies connector JARs directly over the formal paths 
referenced by an already-running SeaTunnel service. A concurrent `GET 
/job-info/{jobId}` can deserialize a job DAG while the LocalFile connector 
archive is only partially written. A controlled copy-window regression observed 
HTTP 500 and a `LocalFileSink` class-loading failure, although the complete 
input JAR contains the class and the job can subsequently restore.
   
   Expected: the formal connector path should continue exposing the previous 
complete archive until its complete replacement is available. A failed copy 
should retain that previous archive. This report concerns E2E artifact 
publication; no checkpoint barrier or production classloader change is proposed.
   
   ### SeaTunnel Version
   
   Unreleased `3.0.1-SNAPSHOT`, fixed upstream dev commit 
`1ce2d781566564d5bd603710e7442bd88c531414`. The same dev SHA was observed again 
before filing this report. Released versions were not tested.
   
   ### SeaTunnel Config
   
   Existing E2E fixture 
`savepoint-restore/stream_checkpointable_sequence_to_localfile.conf` (uses the 
E2E-only CheckpointableSequenceSource test plugin):
   
   ```hocon
   env {
     parallelism = 1
     job.mode = "STREAMING"
     checkpoint.interval = 3000
     checkpoint.retain-after-job-cancelled = true
   }
   
   source {
     CheckpointableSequenceSource {
       start_offset = 0
       end_offset = 1000000
       split_num = 1
       emit_interval_ms = 50
       records_per_poll = 1
     }
   }
   
   transform {
   }
   
   sink {
     LocalFile {
       path = "/tmp/seatunnel_mnt/savepoint-restore/sinkfile/"
       row_delimiter = "\n"
       file_name_expression = "offset_${transactionId}"
       file_format_type = "text"
       is_enable_transaction = true
     }
   }
   ```
   
   ### Running Command
   
   The observed red/green runs used an external JUnit ConsoleLauncher in an 
isolated Docker daemon with fixed runtime artifacts, Java 11, and freshly 
compiled test sources. They did not use Maven Failsafe. Those experimental 
launchers and logs remain local; the commands below are a suggested 
repository-standard reproduction, not a claim of an executed Failsafe run.
   
   The controlled sequence is: run the fixture, wait for a completed 
checkpoint, create a savepoint, and enter the actual restore helper. Expose 
only the first 64 KiB of the LocalFile connector at the copy target and issue a 
job-info request before completing that copy. The old direct copy exposes a 
truncated formal archive; staging at a unique same-directory non-JAR path 
avoids that exposure.
   
   The strengthened current regression checks the formal old SHA before 
requesting REST. Applied alone to the unpatched base, it is expected to reject 
direct overwrite at that SHA assertion before a new HTTP500 observation. That 
proposed reproduction is distinct from the archived earlier HTTP500 run and has 
not been newly executed through Failsafe.
   
   ### Error Exception
   
   Observed in the archived red service log:
   
   ```text
   com.hazelcast.nio.serialization.HazelcastSerializationException: 
java.lang.ClassNotFoundException: 
org.apache.seatunnel.connectors.seatunnel.file.local.sink.LocalFileSink
   Caused by: java.lang.ClassNotFoundException: 
org.apache.seatunnel.connectors.seatunnel.file.local.sink.LocalFileSink
   ```
   
   ### Zeta or Flink or Spark Version
   
   Zeta built from the dev SHA above.
   
   ### Java or Scala Version
   
   Java 11 for the isolated Linux service and IT runtime. Directed unit tests 
were also validated on Windows Java 17.
   
   ### Suggested fix and local validation
   
   Copy each selected connector into a unique same-directory temporary name 
that does not end in `.jar`, then publish the completed file with checked `mv 
-f`. Use POSIX container paths on every host. If copying or publication fails, 
attempt to remove only that temporary file, retain the primary failure and 
interrupt status, and leave the old formal file intact.
   
   The independent local patch passes 10 directed UTs, an affected 66-module 
quick verify, and five serial external-launcher rounds (25 tests: 10 recovery 
and 15 local REST fault checks; zero failures/skips). All 587 job-info checks 
during publication returned 200; the corresponding REST ERROR count was zero. 
The original recovery/checkpoint/offset assertions are unchanged. These results 
do not establish whole-repository coverage, Maven Failsafe coverage, remote CI, 
or long-term stability.
   
   Related context only: #12316 fixes source-to-barrier handoff, #12271 handles 
task-generation classloader ownership, #11514 concerns NaN metric 
serialization, and closed/unmerged #8095 reduces repeated connector copying by 
filename. None is the issue this patch would close.
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://www.apache.org/foundation/policies/conduct).
   
   ### Affected code and reproduction outline
   
   The affected helper is 
[`ContainerUtil.copyConnectorJarToContainer`](https://github.com/apache/seatunnel/blob/1ce2d781566564d5bd603710e7442bd88c531414/seatunnel-e2e/seatunnel-e2e-common/src/test/java/org/apache/seatunnel/e2e/common/util/ContainerUtil.java#L93-L119).
 Its `copyFileToContainer` destination is the formal connector path. The 
existing recovery fixture is 
[`SavepointRestoreIT`](https://github.com/apache/seatunnel/blob/1ce2d781566564d5bd603710e7442bd88c531414/seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/java/org/apache/seatunnel/engine/e2e/SavepointRestoreIT.java).
   
   To make the copy window deterministic in a test wrapper around the actual 
helper:
   
   1. Run the existing savepoint fixture, wait for a completed checkpoint, 
save/cancel, and enter the restore path while the service stays alive.
   2. Intercept the LocalFile connector copy and transfer only its first 64 KiB 
to the destination requested by the helper. Pause before completing the 
transfer. The original complete archive contains `LocalFileSink`.
   3. While paused, query `GET /job-info/{jobId}`. With direct overwrite, the 
requested destination is the formal JAR and can now be incomplete. The 
historical controlled run returned HTTP 500 with the exception above.
   4. Complete the copy and continue restoration. The recovery assertions can 
pass even though a REST request failed during publication.
   5. Repeat with a unique staged non-JAR destination followed by 
same-directory publication. Assert the formal old SHA remains unchanged during 
the partial copy and after an injected failed copy, then assert that successful 
publication exposes the complete, different replacement SHA and that 
REST/recovery pass.
   
   An ordinary run may miss this short race; the controlled copy-window 
interception is necessary for a deterministic regression. The regression 
additions and fix are currently local and are not yet available in a PR.
   
   Suggested Maven command after adding that regression (**not executed here**):
   
   ```bash
   ./mvnw -B -T 1 -pl :connector-seatunnel-e2e-base -am \
     -Dit.test=SavepointRestoreIT -Dfailsafe.failIfNoSpecifiedTests=false \
     -DskipTests=false -DskipUT=true -DskipIT=false \
     -Dskip.ui=true -Dlicense.skipAddThirdParty=true verify
   ```
   
   The planned supplemental Failsafe run was explicitly skipped; no Failsafe 
success or remote CI result is claimed.
   
   ### Completed local external-launcher rounds
   
   | Round | Tests | Failures/errors/skips | Publication-window job-info HTTP 
200 | Corresponding REST ERROR lines |
   | --- | ---: | --- | ---: | ---: |
   | 1 | 5 | 0/0/0 | 53 | 0 |
   | 2 | 5 | 0/0/0 | 66 | 0 |
   | 3 | 5 | 0/0/0 | 68 | 0 |
   | 4 | 5 | 0/0/0 | 55 | 0 |
   | 5 | 5 | 0/0/0 | 345 | 0 |
   
   Each round includes two recovery tests and three local REST fault tests. 
These finite runs support the local patch's behavior for the recorded inputs; 
they do not prove long-term stability or full-repository integration coverage.
   


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