Skip to content

Balance throttle permits in SimpleAsyncTaskExecutor#36916

Open
junhyeong9812 wants to merge 2 commits into
spring-projects:mainfrom
junhyeong9812:fix/simpleasync-immediate-throttle
Open

Balance throttle permits in SimpleAsyncTaskExecutor#36916
junhyeong9812 wants to merge 2 commits into
spring-projects:mainfrom
junhyeong9812:fix/simpleasync-immediate-throttle

Conversation

@junhyeong9812

@junhyeong9812 junhyeong9812 commented Jun 13, 2026

Copy link
Copy Markdown

Overview

SimpleAsyncTaskExecutor acquires a concurrency throttle permit (beforeAccess()) only on the
throttled execution branch, but TaskTrackingRunnable.run() released one (afterAccess())
unconditionally. This unbalanced the throttle's concurrency count in two ways.

Problem

  1. Immediate-timeout task — with the throttle active and active-thread tracking enabled
    (taskTerminationTimeout > 0 or cancelRemainingTasksOnClose), an immediate-timeout task takes
    the active-thread tracking branch without acquiring a permit, yet run() still released one,
    driving the count below zero and progressively defeating the configured concurrency limit. This
    is reachable through the deprecated execute(Runnable, long) overload; the regular
    execute(Runnable) / submit(...) paths use TIMEOUT_INDEFINITE and are unaffected.
  2. Cancelled-before-run task — a throttled task cancelled before it ran (checkCancelled()
    threw ahead of the try/finally in run()) never released its acquired permit.

Fix

Track whether a permit was acquired and release it in run()'s finally only in that case, with
the finally now also covering checkCancelled(), so an acquired permit is always released
exactly once. Deterministic regression tests are added to SimpleAsyncTaskExecutorTests.

SimpleAsyncTaskExecutor.execute(Runnable, long) routed an immediate-
timeout task through the active-thread tracking branch without acquiring
a concurrency throttle permit, yet TaskTrackingRunnable.run() always
released one via afterAccess(). With the throttle active and
active-thread tracking enabled (a task termination timeout or
cancel-on-close), each such task drove the concurrency count below zero,
progressively defeating the configured concurrency limit.

Track whether a permit was acquired and release it only in that case, so
immediate-timeout tasks bypass the throttle entirely as documented.

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 13, 2026
A throttled task cancelled before it ran never released its acquired
permit. Extend the try in TaskTrackingRunnable.run() to cover the
cancellation check so a permit is always released once acquired.

Rename the flag to releaseThrottle and make the throttle accounting
tests deterministic.

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
@junhyeong9812 junhyeong9812 changed the title Balance throttle permits for immediate-timeout tasks Balance throttle permits in SimpleAsyncTaskExecutor Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

2 participants