Skip to content

fix(common): publish queue configuration safely - #7141

Open
dengliming wants to merge 3 commits into
apache:masterfrom
dengliming:fix-6774-memory-safe-queue-visibility
Open

dengliming wants to merge 3 commits into
apache:masterfrom
dengliming:fix-6774-memory-safe-queue-visibility

Conversation

@dengliming

Copy link
Copy Markdown
Member

Summary

  • make the memory threshold visible across concurrent queue users
  • safely publish runtime rejector replacements
  • add regression coverage for both volatile configuration fields

Testing

  • mvn -q -pl shenyu-common -am -DskipTests=false -Dcheckstyle.skip=false -Dtest=MemorySafeLinkedBlockingQueueTest -DfailIfNoTests=false test

Fixes #6774

private static final long serialVersionUID = 8032578371749960142L;

private int maxFreeMemory;
private volatile int maxFreeMemory;

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.

volatile is the right fix and it is genuinely needed here - unlike a plain immutable field, both values have public setters (setMaxFreeMemory / setRejector), so they really are mutable shared state read from put/offer on arbitrary threads.

Two notes, neither blocking:

  1. hasRemainedMemory() is still check-then-act:
if (!hasRemainedMemory()) { rejector.reject(e, this); return false; }
return super.offer(e);

volatile gives visibility for maxFreeMemory/rejector, it does not make the memory check atomic with the enqueue. That is fine for a best-effort OOM guard - arguably publishing the config safely is exactly what was missing - but worth a comment so nobody reads volatile as "the guard is atomic".

  1. Same as elsewhere in this batch: asserting Modifier.isVolatile(...) by reflection checks an implementation detail, and would silently pass even if the setter were removed and the field became effectively final. A behavioural test (e.g. setMaxFreeMemory(0) from one thread while another offers) is more valuable, though harder to make deterministic - so keeping this as a cheap regression guard is acceptable.

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

Summary

Marks maxFreeMemory and rejector in MemorySafeLinkedBlockingQueue as volatile.

Why this is correct

These are not effectively-final fields - the class exposes setMaxFreeMemory(int) and setRejector(Rejector<E>), and both values are read on the hot path in put / offer / hasRemainedMemory from whatever thread is producing into the queue. Without volatile there is no happens-before between a configuration change and those reads, so a producer can keep using a stale threshold or a partially-published Rejector. Making both volatile is the minimal correct fix; int and reference volatile reads/writes are atomic, so no further synchronisation is needed for single-variable visibility.

Non-blocking notes (inline)

  1. The guard in offer/put is still check-then-act:
if (!hasRemainedMemory()) { rejector.reject(e, this); return false; }
return super.offer(e);

volatile fixes visibility, not atomicity - free memory can drop between the check and the enqueue. That is fine for a best-effort OOM guard, but worth a comment so volatile is not misread as "the guard is atomic".

  1. testMutableConfigurationIsVolatile asserts Modifier.isVolatile by reflection. It is a cheap regression guard and I will not block on it, but it tests an implementation detail: it would still pass if the setters were deleted and the fields became de-facto immutable. A behavioural test (call setMaxFreeMemory from one thread while another offers) is more meaningful if you want stronger coverage.

Approving - small, correct, and the risk is essentially zero.

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] MemorySafeLinkedBlockingQueue.maxFreeMemory and rejector lack volatile/visibility

2 participants