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 @@ -44,6 +44,8 @@
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeValidationException;
import org.elasticsearch.plugins.PluginsLoader;
import org.elasticsearch.rest.MethodHandlers;
import org.elasticsearch.transport.RequestHandlerRegistry;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -201,7 +203,11 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
SubscribableListener.class,
RunOnce.class,
// We eagerly initialize to work around log4j permissions & JDK-8309727
VectorUtil.class
VectorUtil.class,
// RequestHandlerRegistry and MethodHandlers classes do nontrivial static initialization which should always succeed but load
// it now (before SM) to be sure
RequestHandlerRegistry.class,
MethodHandlers.class
);

// load the plugin Java modules and layers now for use in entitlements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
/**
* Encapsulate multiple handlers for the same path, allowing different handlers for different HTTP verbs and versions.
*/
final class MethodHandlers {
public final class MethodHandlers {

private final String path;
private final Map<RestRequest.Method, Map<RestApiVersion, RestHandler>> methodHandlers;

@SuppressWarnings("unused") // only accessed via #STATS_TRACKER_HANDLE, lazy initialized because instances consume non-trivial heap
private volatile HttpRouteStatsTracker statsTracker;
private HttpRouteStatsTracker statsTracker;

private static final VarHandle STATS_TRACKER_HANDLE;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.elasticsearch.telemetry.tracing.Tracer;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.Executor;

import static org.elasticsearch.core.Releasables.assertOnce;
Expand All @@ -33,7 +35,19 @@ public class RequestHandlerRegistry<Request extends TransportRequest> implements
private final TaskManager taskManager;
private final Tracer tracer;
private final Writeable.Reader<Request> requestReader;
private final TransportActionStatsTracker statsTracker = new TransportActionStatsTracker();
@SuppressWarnings("unused") // only accessed via #STATS_TRACKER_HANDLE, lazy initialized because instances consume non-trivial heap
private TransportActionStatsTracker statsTracker;

private static final VarHandle STATS_TRACKER_HANDLE;

static {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add this to the classes passed to org.elasticsearch.bootstrap.Elasticsearch#ensureInitialized to ensure it's always initialized eagerly before the security manager comes along and causes problems?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing, dded this one as well as MethodHandlers where I did the same thing now.

try {
STATS_TRACKER_HANDLE = MethodHandles.lookup()
.findVarHandle(RequestHandlerRegistry.class, "statsTracker", TransportActionStatsTracker.class);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}

public RequestHandlerRegistry(
String action,
Expand Down Expand Up @@ -118,15 +132,34 @@ public static <R extends TransportRequest> RequestHandlerRegistry<R> replaceHand
}

public void addRequestStats(int messageSize) {
statsTracker.addRequestStats(messageSize);
statsTracker().addRequestStats(messageSize);
}

@Override
public void addResponseStats(int messageSize) {
statsTracker.addResponseStats(messageSize);
statsTracker().addResponseStats(messageSize);
}

public TransportActionStats getStats() {
var statsTracker = existingStatsTracker();
if (statsTracker == null) {
return TransportActionStats.EMPTY;
}
return statsTracker.getStats();
}

private TransportActionStatsTracker statsTracker() {
var tracker = existingStatsTracker();
Copy link
Contributor

Choose a reason for hiding this comment

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

If you're looking for yet-weaker memory semantics, I think this one can be a regular (nonvolatile) read. If it's null then the compareAndExchange will establish the right guarantees, and if it's not null then it has to be the right value.

Risks perhaps creating a few more unused TransportActionStatsTracker instances tho I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea somehow a plain read in this kind of spot weirds me out a little :D but you are right, we could relax these spots further technically I guess I just don't feel adventurous enough for that this evening (I do realise though that we actually do this kind of thing ... sync write -> plain read ... in a couple spots like the CS + routing table computation already).

if (tracker == null) {
var newTracker = new TransportActionStatsTracker();
if ((tracker = (TransportActionStatsTracker) STATS_TRACKER_HANDLE.compareAndExchange(this, null, newTracker)) == null) {
tracker = newTracker;
}
}
return tracker;
}

private TransportActionStatsTracker existingStatsTracker() {
return (TransportActionStatsTracker) STATS_TRACKER_HANDLE.getAcquire(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public record TransportActionStats(
long[] responseSizeHistogram
) implements Writeable, ToXContentObject {

public static final TransportActionStats EMPTY = new TransportActionStats(0, 0, new long[0], 0, 0, new long[0]);

public TransportActionStats(StreamInput in) throws IOException {
this(in.readVLong(), in.readVLong(), in.readVLongArray(), in.readVLong(), in.readVLong(), in.readVLongArray());
}
Expand Down