1313import org .elasticsearch .action .ActionListener ;
1414import org .elasticsearch .action .ActionRequestValidationException ;
1515import org .elasticsearch .action .ActionResponse ;
16+ import org .elasticsearch .action .ActionRunnable ;
1617import org .elasticsearch .action .ActionType ;
18+ import org .elasticsearch .action .SingleResultDeduplicator ;
1719import org .elasticsearch .action .admin .cluster .node .stats .NodesStatsRequestParameters .Metric ;
1820import org .elasticsearch .action .support .ActionFilters ;
21+ import org .elasticsearch .action .support .SubscribableListener ;
1922import org .elasticsearch .action .support .master .MasterNodeReadRequest ;
2023import org .elasticsearch .action .support .master .TransportMasterNodeReadAction ;
2124import org .elasticsearch .cluster .ClusterState ;
2831import org .elasticsearch .cluster .service .ClusterService ;
2932import org .elasticsearch .common .io .stream .StreamInput ;
3033import org .elasticsearch .common .io .stream .StreamOutput ;
34+ import org .elasticsearch .common .util .concurrent .EsExecutors ;
3135import org .elasticsearch .core .Nullable ;
3236import org .elasticsearch .core .TimeValue ;
3337import org .elasticsearch .injection .guice .Inject ;
@@ -46,7 +50,7 @@ public class TransportGetAllocationStatsAction extends TransportMasterNodeReadAc
4650
4751 public static final ActionType <TransportGetAllocationStatsAction .Response > TYPE = new ActionType <>("cluster:monitor/allocation/stats" );
4852
49- private final AllocationStatsService allocationStatsService ;
53+ private final SingleResultDeduplicator < Map < String , NodeAllocationStats >> allocationStatsSupplier ;
5054 private final DiskThresholdSettings diskThresholdSettings ;
5155
5256 @ Inject
@@ -66,9 +70,15 @@ public TransportGetAllocationStatsAction(
6670 actionFilters ,
6771 TransportGetAllocationStatsAction .Request ::new ,
6872 TransportGetAllocationStatsAction .Response ::new ,
69- threadPool .executor (ThreadPool .Names .MANAGEMENT )
73+ // DIRECT is ok here because we fork the allocation stats computation onto a MANAGEMENT thread if needed, or else we return
74+ // very cheaply.
75+ EsExecutors .DIRECT_EXECUTOR_SERVICE
76+ );
77+ final var managementExecutor = threadPool .executor (ThreadPool .Names .MANAGEMENT );
78+ this .allocationStatsSupplier = new SingleResultDeduplicator <>(
79+ threadPool .getThreadContext (),
80+ l -> managementExecutor .execute (ActionRunnable .supply (l , allocationStatsService ::stats ))
7081 );
71- this .allocationStatsService = allocationStatsService ;
7282 this .diskThresholdSettings = new DiskThresholdSettings (clusterService .getSettings (), clusterService .getClusterSettings ());
7383 }
7484
@@ -84,12 +94,15 @@ protected void doExecute(Task task, Request request, ActionListener<Response> li
8494
8595 @ Override
8696 protected void masterOperation (Task task , Request request , ClusterState state , ActionListener <Response > listener ) throws Exception {
87- listener .onResponse (
88- new Response (
89- request .metrics ().contains (Metric .ALLOCATIONS ) ? allocationStatsService .stats () : Map .of (),
90- request .metrics ().contains (Metric .FS ) ? diskThresholdSettings : null
91- )
92- );
97+ // NB we are still on a transport thread here - if adding more functionality here make sure to fork to a different pool
98+
99+ final SubscribableListener <Map <String , NodeAllocationStats >> allocationStatsStep = request .metrics ().contains (Metric .ALLOCATIONS )
100+ ? SubscribableListener .newForked (allocationStatsSupplier ::execute )
101+ : SubscribableListener .newSucceeded (Map .of ());
102+
103+ allocationStatsStep .andThenApply (
104+ allocationStats -> new Response (allocationStats , request .metrics ().contains (Metric .FS ) ? diskThresholdSettings : null )
105+ ).addListener (listener );
93106 }
94107
95108 @ Override
0 commit comments