Skip to content

Fix busy-spin in SubscriberInputStream await() - #37159

Open
flex-donggyu wants to merge 1 commit into
spring-projects:mainfrom
flex-donggyu:fix-subscriber-input-stream-await-spin
Open

Fix busy-spin in SubscriberInputStream await()#37159
flex-donggyu wants to merge 1 commit into
spring-projects:mainfrom
flex-donggyu:fix-subscriber-input-stream-await-spin

Conversation

@flex-donggyu

Copy link
Copy Markdown

SubscriberInputStream.await() parks the reader with LockSupport.park() after storing the current thread in parkedThread. When park() returns without resume() having set READY — spurious wakeup or interrupt — the reference still points to the current thread, so the loop can neither exit nor park again (compareAndSet(null, ...) always fails). The thread spins at 100% CPU until the next upstream signal. And since park() doesn't consume the interrupt flag, every later await() call on that thread spins the same way.

The interrupt case is easy to hit through Spring MVC async handling: both the async timeout and a client disconnect cancel the task with Future.cancel(true) (CallableInterceptorChain#cancelTask). With StreamingResponseBody proxying a slow upstream, the worker is usually parked in await() at that moment.

On virtual threads a spinning thread never yields its carrier, so a few cancelled streaming requests can starve the whole scheduler — we traced a production outage (liveness probe kills) to this.

Reproduced on JDK 21 and 24: after the interrupt the thread stays RUNNABLE, burning a full core (~13M loop iterations in 200ms).

Fix

  • Park again when the wakeup was spurious and the reference is still ours.
  • On interrupt, clear the reference and propagate the cancellation — unless data arrived concurrently, in which case it is delivered first and the thread stays interrupted for the next call.

Applied to both near-duplicate classes (spring-web and spring-core), with a regression test that fails on the old code.

Related: #35978 fixed a separate defect in resume() in this class; the await() interrupt path wasn't covered by it.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 20, 2026
LockSupport.park() can return without resume() having set the parked
thread reference to READY, either spuriously or when the thread is
interrupted. The reference then still points to the current thread,
so the compareAndSet in the await() loop can never succeed again and
the loop neither parks nor exits, spinning on CPU until the upstream
emits the next signal.

On virtual threads this is particularly harmful since a spinning
thread never yields its carrier. Once as many requests spin as there
are carrier threads, unrelated virtual thread tasks are no longer
scheduled. The interrupt case is reachable through Spring MVC async
request handling, where both a timeout and a client disconnect cancel
the task with interruption.

Park again when the reference is still owned by the current thread
after a spurious wakeup. On interruption, clear the reference and
propagate the cancellation, unless data has arrived concurrently, in
which case the data is delivered first and the interrupt status is
preserved for the next call.

Closes spring-projectsgh-37159

Signed-off-by: donggyu <donggyu@flex.team>
@flex-donggyu
flex-donggyu force-pushed the fix-subscriber-input-stream-await-spin branch from 582211e to 493fb15 Compare August 20, 2026 08:17
@sbrannen sbrannen added in: web Issues in web modules (web, webmvc, webflux, websocket) in: core Issues in core modules (aop, beans, core, context, expression) labels Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: core Issues in core modules (aop, beans, core, context, expression) in: web Issues in web modules (web, webmvc, webflux, websocket) status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants