Eliaaazzz opened a new issue, #39364:
URL: https://github.com/apache/beam/issues/39364

   ### What happened?
   
   On Windows, every Java portable ValidatesRunner test hangs for its full 
JUnit timeout (1200s) during pipeline submission, inside artifact staging. With 
#39332 fixed locally (so `beamTestPipelineOptions` parses), running
   
   ```
   gradlew.bat :runners:spark:3:job-server:validatesPortableRunnerBatch --tests 
"*SplittableDoFnTest*"
   ```
   
   gives 9 tests, 9 failures, all identical:
   
   ```
   org.junit.runners.model.TestTimedOutException: test timed out after 1200 
seconds
     at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2004)
     at 
org.apache.beam.runners.fnexecution.artifact.ArtifactStagingService.offer(ArtifactStagingService.java:555)
     at 
org.apache.beam.runners.portability.PortableRunner.run(PortableRunner.java:197)
     at 
org.apache.beam.runners.portability.testing.TestPortableRunner.run(TestPortableRunner.java:80)
   ```
   
   Nothing is ever written to the artifact staging directory 
(`$TMP/beam-artifact-staging` is never created). This is runner-agnostic: it is 
the job server's `ArtifactStagingService`, so any portable Java VR suite on 
Windows is affected.
   
   ### Root cause
   
   The staged file name embeds the environment id verbatim. 
`ArtifactStagingService.createFilename` does
   
   ```java
   return clip(String.format("%s-%s-%s", idGenerator.getId(), clip(environment, 
25), base), 100);
   ```
   
   and Java SDK environment ids are environment URNs: 
`SdkComponents.registerEnvironment` uses `uniqify(env.getUrn(), 
environmentIds.values())`, producing ids like `beam:env:embedded:v1`. Colons 
are legal in Linux file names (CI actually creates files named 
`1-beam:env:embedded:v1-<staged-name>.jar`) but illegal on Windows:
   
   ```java
   Paths.get("C:\\...\\beam-artifact-staging\\abc123")
       
.resolve("1-beam:env:embedded:v1-beam-runners-spark-3-job-server-2.68.0-SNAPSHOT-AbCdEf.jar")
   // java.nio.file.InvalidPathException: Illegal char <:> at index 6: 
1-beam:env:embedded:v1-...
   ```
   
   So on Windows `StoreArtifact.call()` throws `InvalidPathException` in its 
first statement, `destinationProvider.getDestination(...)` (via 
`LocalResourceId.resolve`).
   
   ### Why it hangs instead of failing
   
   `InvalidPathException` is a `RuntimeException`, and `StoreArtifact.call()` 
only handles `IOException | InterruptedException`:
   
   ```java
   } catch (IOException | InterruptedException exn) {
     totalPendingBytes.setException(exn);
     LOG.error("Exception staging artifacts", exn);
     ...
   }
   ```
   
   The runtime exception escapes: `totalPendingBytes.setException` is never 
called, nothing is logged, and the exception sits in a `Future` that is only 
inspected in `finishStaging`, which is never reached. The queue consumer is 
dead, so the gRPC `onNext` thread blocks forever in 
`OverflowingSemaphore.aquire` (GETCHUNK), the reverse-retrieval stream is never 
completed or errored, and the client waits forever in 
`ArtifactStagingService.offer`.
   
   Thread dump of a hung test worker (jstack while the test was inside the 
1200s wait):
   
   ```
   "Time-limited test"  WAITING
     at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2004)
     at 
org.apache.beam.runners.fnexecution.artifact.ArtifactStagingService.offer(ArtifactStagingService.java:555)
   
   "grpc-default-executor-2"  WAITING (on object monitor)
     at java.lang.Object.wait(Native Method)
     at 
org.apache.beam.runners.fnexecution.artifact.ArtifactStagingService$OverflowingSemaphore.aquire(ArtifactStagingService.java:230)
     at 
org.apache.beam.runners.fnexecution.artifact.ArtifactStagingService$2.onNext(ArtifactStagingService.java:411)
   
   "pool-3-thread-1" .. "pool-3-thread-10"  WAITING (parking)   <- staging 
executor, all idle, consumers dead
   ```
   
   `OverflowingSemaphore.aquire` is waiting with no exception set, which 
confirms the consumer died on a path that bypassed `setException`.
   
   ### Related weaknesses in the same code
   
   - The `GETCHUNK` catch block calls the inbound observer's `onError`, which 
logs and sets `state = ERROR` but never calls `responseObserver.onError(...)`, 
so even handled staging failures leave the client waiting rather than failing 
fast.
   - `createFilename` splits with `Splitter.onPattern("[^A-Za-z-_.]]")`. The 
doubled `]` looks like a typo (and `0-9` is missing), so the pattern only 
matches a non-alphanumeric character followed by a literal `]` and `base` ends 
up being the whole input rather than its last alphanumeric run. For artifacts 
without a `STAGING_TO` role this embeds the full source path (including `C:` on 
Windows) in the staged file name.
   
   Fixing this probably means (a) sanitizing the staged file name (environment 
id included) down to file-name-safe characters, and (b) hardening the failure 
path so a dead `StoreArtifact` reliably errors the stream to the client instead 
of deadlocking, e.g. catching `Throwable` in `StoreArtifact.call` and 
propagating staging errors to `responseObserver.onError`.
   
   This does not affect CI, which runs on Linux. Found while validating the fix 
for #39332 on Windows; with both issues present, #39332 masks this one (tests 
fail at `TestPipeline.create()` before ever reaching artifact staging).
   
   ### Issue Priority
   
   Priority: 3 (minor)
   
   ### Issue Components
   
   - [x] Component: Java SDK
   


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