Visitar URL original
fix(common): restore eager task queue offer policy by lymerin · Pull Request #7482 · apache/shenyu · GitHub
Skip to content

fix(common): restore eager task queue offer policy - #7482

Merged
Aias00 merged 1 commit into
apache:masterfrom
lymerin:7463
Oct 11, 2026
Merged

Aias00 merged 1 commit into
apache:masterfrom
lymerin:7463

Conversation

@lymerin

@lymerin lymerin commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #7463

Problem

When all workers are busy and the pool size is below maximumPoolSize, TaskQueue.offer is intended to return false so that the executor creates an additional worker.

However, MemorySafeTaskQueue and MemoryLimitedTaskQueue inherit concrete offer methods from their superclass queues. These methods take precedence over the interface default method, preventing the eager-growth policy from being called.

Impact

Tasks can remain queued while the configured maximum still allows additional workers, increasing queueing latency under load.

Both production task queue implementations are affected. Relevant callers include shared gateway pools configured with maximumPoolSize > corePoolSize and the websocket reconnect executor, which uses core/max sizes of 1/8.

Changes

  • Explicitly override offer(Runnable) in both task queue implementations and delegate to TaskQueue.super.offer.
  • Retain doOffer for physical enqueueing through the superclass methods, preserving existing memory checks.
  • Add parameterized tests for both queue implementations covering:
    • Eager growth when the core worker is busy.
    • Queueing for an idle worker.
  • Reuse a common executor factory in the test class.

Validation

  • ./mvnw -pl shenyu-common -Dtest=ShenyuThreadPoolExecutorTest -Djacoco.skip=true test — 5 tests passed, with no failures, errors, or skipped tests.
  • Checkstyle — passed with zero violations.

Checklist

Make sure that:

@lymerin
lymerin force-pushed the 7463 branch 3 times, most recently from 3271d2c to 844b120 Compare October 7, 2026 18:39
Aias00
Aias00 previously approved these changes Oct 8, 2026

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 844b120ee3f4.

The queue offer policy fix is source-correct, and exact-head ShenyuThreadPoolExecutorTest passed.

Validation: exact-head targeted Maven reactor tests passed on Java 17.

./mvnw -pl shenyu-common -am test -Dtest=ShenyuThreadPoolExecutorTest -Dsurefire.failIfNoSpecifiedTests=false -Drat.skip=true -Djacoco.skip=true -DskipRemoteResources=true -Dmaven.javadoc.skip=true

GitHub still reports failing checks: e2e, e2e-case (e2e-logging-rabbitmq-compose, shenyu-e2e-case-logging-rabbitmq, :shenyu-examples-http). This review does not claim those checks have passed.

@lymerin

lymerin commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed current head 844b120ee3f4.

The queue offer policy fix is source-correct, and exact-head ShenyuThreadPoolExecutorTest passed.

Validation: exact-head targeted Maven reactor tests passed on Java 17.

./mvnw -pl shenyu-common -am test -Dtest=ShenyuThreadPoolExecutorTest -Dsurefire.failIfNoSpecifiedTests=false -Drat.skip=true -Djacoco.skip=true -DskipRemoteResources=true -Dmaven.javadoc.skip=true

GitHub still reports failing checks: e2e, e2e-case (e2e-logging-rabbitmq-compose, shenyu-e2e-case-logging-rabbitmq, :shenyu-examples-http). This review does not claim those checks have passed.

Thanks for the review. The failure came from a RabbitMQ E2E synchronization race, unrelated to this queue fix. Following the current plan to defer CI changes , we retriggered the checks with a comment-only update (5d1244b7551a). RabbitMQ E2E and the E2E summary now pass.

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(common): restore eager task queue offer policy

Reviewed the change against TaskQueue, MemorySafeTaskQueue, MemoryLimitedTaskQueue, and their LinkedBlockingQueue superclasses at head 5d1244b.

Root cause (confirmed)

TaskQueue<E> extends BlockingQueue<E> defines a default offer(E) that implements the eager-growth policy:

  • if activeCount < poolSize → doOffer(e) (idle worker exists, enqueue),
  • else if poolSize < maximumPoolSize → return false (let the executor spawn a new worker),
  • else → doOffer(e) (at max, enqueue).

MemorySafeTaskQueue/MemoryLimitedTaskQueue extend concrete LinkedBlockingQueue subclasses (MemorySafeLinkedBlockingQueue/MemoryLimitedLinkedBlockingQueue) that provide their own concrete offer(E). Because a concrete superclass method takes precedence over an interface default, calls to queue.offer(task) from ShenyuThreadPoolExecutor dispatched to the physical enqueue instead of the eager-growth default — exactly the "tasks remain queued while max still allows additional workers" symptom described in #7463. Both production implementations were affected, so a pool with maximumPoolSize > corePoolSize (including the websocket reconnect executor at 1/8) would not grow under load.

Fix (correct)

  • Both classes now explicitly override offer(Runnable) and delegate to TaskQueue.super.offer(runnable), invoking the interface default. TaskQueue.super.offer is valid Java and resolves to the intended policy.
  • doOffer(Runnable) still calls super.offer(runnable), i.e. the physical enqueue in the LinkedBlockingQueue subclass, which retains the existing memory guard (MemorySafeLinkedBlockingQueue.offer / MemoryLimitedLinkedBlockingQueue.offer check free memory before enqueueing). Memory-safety semantics are therefore preserved; only the dispatch target of offer changes.

Tests (adequate)

  • ShenyuThreadPoolExecutorTest now parameterizes over both queue implementations via taskQueues().
    • testGrowsWhenCoreWorkerIsBusy: pins the single core worker and submits a second task; with the fix the eager-growth policy returns false and a second worker is created, so the second submit(...).get() returns within the timeout. This directly exercises the bug fix.
    • testOffersToIdleWorker: mocks EagerExecutorService (poolSize=2, activeCount=1, max=3) and asserts offer enqueues + poll returns the same task — covers the idle-worker branch.
  • Referenced constructors (MemorySafeTaskQueue(int maxFreeMemory), MemoryLimitedTaskQueue(Instrumentation)) exist. MemorySafeTaskQueue<>(1) sets maxFreeMemory=1, and since isAvailable() checks maxAvailable() > maxFreeMemory, the JVM's free memory is always well above 1, so the assertion is stable (not flaky).
  • testNullCommand remains valid after the getTestExecutor signature change (core/max 1/2 still throws NPE on execute(null)).

CI

All checks green (build, Analyze/CodeQL, the full integrated/e2e matrix, agent_gateway_migrations, rat/license/changes).

Non-blocking suggestion

The shadowing of an interface-default offer by a superclass concrete offer is subtle. A one-line comment on the new offer override (e.g. "delegate to TaskQueue's eager-growth default; do not call super.offer here or the growth policy is bypassed") would help future maintainers avoid re-introducing the regression.

LGTM — approving. Good catch and clean, well-tested fix.

@Aias00
Aias00 merged commit c5931a1 into apache:master Oct 11, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Concrete task queues shadow the eager executor offer policy

2 participants