-
Notifications
You must be signed in to change notification settings - Fork 906
Proactively avoid Unsafe on Java 23+ #7691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,15 +5,10 @@ | |
|
||
package io.opentelemetry.sdk.trace.internal; | ||
|
||
import java.util.Objects; | ||
import java.util.Queue; | ||
import java.util.concurrent.ArrayBlockingQueue; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.function.Consumer; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import org.jctools.queues.MessagePassingQueue; | ||
import org.jctools.queues.MpscArrayQueue; | ||
import org.jctools.queues.atomic.MpscAtomicArrayQueue; | ||
|
||
/** | ||
* Internal accessor of JCTools package for fast queues. | ||
|
@@ -23,38 +18,19 @@ | |
*/ | ||
public final class JcTools { | ||
|
||
private static final AtomicBoolean queueCreationWarningLogged = new AtomicBoolean(); | ||
private static final Logger logger = Logger.getLogger(JcTools.class.getName()); | ||
|
||
/** | ||
* Returns a new {@link Queue} appropriate for use with multiple producers and a single consumer. | ||
*/ | ||
public static <T> Queue<T> newFixedSizeQueue(int capacity) { | ||
try { | ||
return new MpscArrayQueue<>(capacity); | ||
} catch (java.lang.NoClassDefFoundError | java.lang.ExceptionInInitializerError e) { | ||
if (!queueCreationWarningLogged.getAndSet(true)) { | ||
logger.log( | ||
Level.WARNING, | ||
"Cannot create high-performance queue, reverting to ArrayBlockingQueue ({0})", | ||
Objects.toString(e, "unknown cause")); | ||
} | ||
// Happens when modules such as jdk.unsupported are disabled in a custom JRE distribution, | ||
// or a security manager preventing access to Unsafe is installed. | ||
return new ArrayBlockingQueue<>(capacity); | ||
} | ||
return new MpscAtomicArrayQueue<>(capacity); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. key insight here was from JCTools/JCTools#395 (comment):
I originally only used this implementation on Java 22+, just to avoid triggering the Unsafe warning but given that the benchmarks look fine, I think it would be ok to go straight to this implementation in all cases and simplify things There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the difference between MpscArrayQueue and MpscAtomicArrayQueue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. they're pretty similar, in fact looks like MpscAtomicArrayQueue is autogenerated from MpscArrayQueue: |
||
} | ||
|
||
/** | ||
* Returns the capacity of the {@link Queue}. We cast to the implementation so callers do not need | ||
* to use the shaded classes. | ||
*/ | ||
public static long capacity(Queue<?> queue) { | ||
if (queue instanceof MessagePassingQueue) { | ||
return ((MessagePassingQueue<?>) queue).capacity(); | ||
} else { | ||
return (long) ((ArrayBlockingQueue<?>) queue).remainingCapacity() + queue.size(); | ||
} | ||
return ((MessagePassingQueue<?>) queue).capacity(); | ||
} | ||
|
||
/** | ||
|
@@ -65,22 +41,7 @@ public static long capacity(Queue<?> queue) { | |
*/ | ||
@SuppressWarnings("unchecked") | ||
public static <T> int drain(Queue<T> queue, int limit, Consumer<T> consumer) { | ||
if (queue instanceof MessagePassingQueue) { | ||
return ((MessagePassingQueue<T>) queue).drain(consumer::accept, limit); | ||
} else { | ||
return drainNonJcQueue(queue, limit, consumer); | ||
} | ||
} | ||
|
||
private static <T> int drainNonJcQueue( | ||
Queue<T> queue, int maxExportBatchSize, Consumer<T> consumer) { | ||
int polledCount = 0; | ||
T item; | ||
while (polledCount < maxExportBatchSize && (item = queue.poll()) != null) { | ||
consumer.accept(item); | ||
++polledCount; | ||
} | ||
return polledCount; | ||
return ((MessagePassingQueue<T>) queue).drain(consumer::accept, limit); | ||
} | ||
|
||
private JcTools() {} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ | |
import java.security.AccessController; | ||
import java.security.PrivilegedAction; | ||
import java.util.Queue; | ||
import java.util.concurrent.ArrayBlockingQueue; | ||
import org.jctools.queues.atomic.MpscAtomicArrayQueue; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.condition.EnabledOnJre; | ||
import org.junit.jupiter.api.condition.JRE; | ||
|
@@ -30,7 +30,7 @@ void newFixedSizeQueue_SunMiscProhibited() { | |
Queue<Object> queue = | ||
AccessController.doPrivileged( | ||
(PrivilegedAction<Queue<Object>>) () -> JcTools.newFixedSizeQueue(10)); | ||
assertThat(queue).isInstanceOf(ArrayBlockingQueue.class); | ||
assertThat(queue).isInstanceOf(MpscAtomicArrayQueue.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was going to say that this test is no longer relevant. But I suppose its still good to assert htat MpscAtomicArrayQueue doesn't rely on unsafe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, though I think we could revisit and probably delete it after #7683 |
||
} finally { | ||
System.setSecurityManager(null); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,41 +9,18 @@ | |
|
||
import java.util.ArrayList; | ||
import java.util.Queue; | ||
import java.util.concurrent.ArrayBlockingQueue; | ||
import org.jctools.queues.MpscArrayQueue; | ||
import org.jctools.queues.MessagePassingQueue; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
import org.mockito.junit.jupiter.MockitoSettings; | ||
import org.mockito.quality.Strictness; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
@MockitoSettings(strictness = Strictness.LENIENT) | ||
class JcToolsTest { | ||
|
||
ArrayList<String> batch = new ArrayList<>(10); | ||
|
||
@Test | ||
void drain_ArrayBlockingQueue() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to test ArrayBlockingQueue anymore with this change |
||
// Arrange | ||
batch.add("Test3"); | ||
Queue<String> queue = new ArrayBlockingQueue<>(10); | ||
queue.add("Test1"); | ||
queue.add("Test2"); | ||
|
||
// Act | ||
JcTools.drain(queue, 5, batch::add); | ||
|
||
// Assert | ||
assertThat(batch).hasSize(3); | ||
assertThat(queue).hasSize(0); | ||
} | ||
|
||
@Test | ||
void drain_MessagePassingQueue() { | ||
// Arrange | ||
batch.add("Test3"); | ||
Queue<String> queue = new MpscArrayQueue<>(10); | ||
Queue<String> queue = JcTools.newFixedSizeQueue(10); | ||
queue.add("Test1"); | ||
queue.add("Test2"); | ||
|
||
|
@@ -58,7 +35,7 @@ void drain_MessagePassingQueue() { | |
@Test | ||
void drain_MaxBatch() { | ||
// Arrange | ||
Queue<String> queue = new MpscArrayQueue<>(10); | ||
Queue<String> queue = JcTools.newFixedSizeQueue(10); | ||
queue.add("Test1"); | ||
queue.add("Test2"); | ||
|
||
|
@@ -79,7 +56,7 @@ void newFixedSize_MpscQueue() { | |
Queue<Object> objects = JcTools.newFixedSizeQueue(capacity); | ||
|
||
// Assert | ||
assertThat(objects).isInstanceOf(MpscArrayQueue.class); | ||
assertThat(objects).isInstanceOf(MessagePassingQueue.class); | ||
} | ||
|
||
@Test | ||
|
@@ -94,16 +71,4 @@ void capacity_MpscQueue() { | |
// Assert | ||
assertThat(queueSize).isGreaterThan(capacity); | ||
} | ||
|
||
@Test | ||
void capacity_ArrayBlockingQueue() { | ||
// Arrange | ||
Queue<String> queue = new ArrayBlockingQueue<>(10); | ||
|
||
// Act | ||
long queueSize = JcTools.capacity(queue); | ||
|
||
// Assert | ||
assertThat(queueSize).isEqualTo(10); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BatchLogRecordProcessor uses ArrayBlockingQueue, so this test abstraction wasn't needed (probably a copy paste from similar test for BatchSpanProcessor):
opentelemetry-java/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/export/BatchLogRecordProcessor.java
Line 82 in 1e763b2