Commits
Tarun Sukhani authored e9e0d22f84a
fix: third audit pass — concurrency correctness + DX + CI P1 fixes: - ExecutorFacade: replace three volatile fields with a single immutable State record snapshot. shutdownNow() previously cleared the executor refs but left usingVirtualThreads=true, so a post-shutdown submit dereferenced null. Now submit/schedule read one volatile snapshot and reject cleanly with RejectedExecutionException when the facade is empty. Adds ExecutorFacadeTest covering shutdown-then-submit paths for both VT and platform installs. - VirtualThreadScheduledExecutor.SelfReschedulingFuture.isDone(): drop the sf.isDone() delegation. The dispatch future completes the moment it hands off to the virtual executor, so isDone() falsely reported true while the periodic body was still running. Now matches STPE contract: terminal state only (cancellation or abnormal termination). Adds regression test that reproduces the active-run case. P2 fixes: - Mail back-pressure: gate every msg.send() through a Semaphore sized by play.mail.maxConcurrent (default 32). Without this, virtual-thread mode lets callers create unbounded concurrent SMTP sends, amplifying rate-limit failures and connection storms. Cap applies to both executor modes for consistent semantics; resetExecutor() also resets the gate to pick up config changes. 3 new tests verify default, configured cap, and real blocking via tryAcquire timeout. - PlayHandler WebSocket writeFutures: CopyOnWriteArrayList → ConcurrentHashMap.newKeySet(). COW copies the backing array on every add/remove, so a high-throughput VT-sender storm paid O(n) allocations per frame; ConcurrentHashSet gives O(1) add/remove with the same lock-free read semantics. - CI matrix: bump from JDK 17/21 to JDK 25 across testing, build, and build-on-mac jobs. Required because the just-tightened ApplicationCompiler now rejects pre-25 runtimes; CI on 17/21 would fail at startup. - Ant test-single target: was unusable. Translate dotted FQN to slash path via <loadresource>+tokenfilter so the include actually resolves to the .class file on disk; switch to classpath.test (so AssertJ and other test deps are visible); use junit.failure failureproperty pattern matching the unittest target. Deferred (require design input): - F.java ArchivedEventStream callbacks under monitor (P3, consistency). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>