Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ public class MemorySafeLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {

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.


private Rejector<E> rejector;
private volatile Rejector<E> rejector;

public MemorySafeLinkedBlockingQueue(final int maxFreeMemory) {
super(Integer.MAX_VALUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@

import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class MemorySafeLinkedBlockingQueueTest {
@Test
Expand All @@ -44,4 +48,13 @@ public void testCustomReject() throws Exception {
assertThrows(RejectException.class, () -> queue.offer(() -> {
}));
}

@Test
public void testMutableConfigurationIsVolatile() throws NoSuchFieldException {
Field maxFreeMemory = MemorySafeLinkedBlockingQueue.class.getDeclaredField("maxFreeMemory");
Field rejector = MemorySafeLinkedBlockingQueue.class.getDeclaredField("rejector");

assertTrue(Modifier.isVolatile(maxFreeMemory.getModifiers()));
assertTrue(Modifier.isVolatile(rejector.getModifiers()));
}
}
Loading