11use crate :: common:: deserialize_uuid;
2- use crate :: config_extension_ext :: set_distributed_option_extension_from_headers ;
3- use crate :: protobuf :: DistributedCodec ;
4- use crate :: work_unit_feed :: { RemoteWorkUnitFeedRegistry , RemoteWorkUnitFeedTxs } ;
2+ use crate :: work_unit_feed :: RemoteWorkUnitFeedRegistry ;
3+ use crate :: worker :: LocalWorkerContext ;
4+ use crate :: worker :: generated :: worker :: coordinator_to_worker_msg :: Inner ;
55use crate :: worker:: generated:: worker:: set_plan_request:: WorkUnitFeedDeclaration ;
6- use crate :: worker:: generated:: worker:: { PreOrderTaskMetrics , SetPlanRequest } ;
7- use crate :: worker:: worker_connection_pool:: LocalWorkerContext ;
8- use crate :: { DistributedConfig , DistributedTaskContext , Worker , WorkerQueryContext } ;
9- use datafusion:: error:: DataFusionError ;
10- use datafusion:: execution:: { SessionStateBuilder , TaskContext } ;
11- use datafusion:: physical_plan:: ExecutionPlan ;
6+ use crate :: worker:: generated:: worker:: worker_service_server:: WorkerService ;
7+ use crate :: worker:: generated:: worker:: {
8+ CoordinatorToWorkerMsg , WorkerToCoordinatorMsg , worker_to_coordinator_msg,
9+ } ;
10+ use crate :: {
11+ DistributedCodec , DistributedConfig , DistributedExt , DistributedTaskContext , TaskData , Worker ,
12+ WorkerQueryContext ,
13+ } ;
14+ use datafusion:: common:: DataFusionError ;
15+ use datafusion:: execution:: SessionStateBuilder ;
1216use datafusion:: prelude:: SessionConfig ;
1317use datafusion_proto:: physical_plan:: AsExecutionPlan ;
1418use datafusion_proto:: protobuf:: PhysicalPlanNode ;
19+ use futures:: { FutureExt , StreamExt } ;
1520use std:: sync:: Arc ;
1621use std:: sync:: atomic:: AtomicUsize ;
1722use tokio:: sync:: oneshot;
18- use tonic:: Status ;
19- use tonic:: metadata:: MetadataMap ;
23+ use tonic:: { Request , Response , Status , Streaming } ;
2024use url:: Url ;
2125
22- #[ derive( Clone , Debug ) ]
23- /// TaskData stores state for a single task being executed by this Endpoint. It may be shared
24- /// by concurrent requests for the same task which execute separate partitions.
25- pub struct TaskData {
26- /// Task context suitable for execute different partitions from the same task.
27- pub ( super ) task_ctx : Arc < TaskContext > ,
28- /// Plan to be executed.
29- pub ( crate ) plan : Arc < dyn ExecutionPlan > ,
30- /// `num_partitions_remaining` is initialized to the total number of partitions in the task (not
31- /// only tasks in the partition group). This is decremented for each request to the endpoint
32- /// for this task. Once this count is zero, the task is likely complete. The task may not be
33- /// complete because it's possible that the same partition was retried and this count was
34- /// decremented more than once for the same partition.
35- pub ( super ) num_partitions_remaining : Arc < AtomicUsize > ,
36- /// Sender half of the metrics channel. `impl_execute_task` takes this (via `Option::take`)
37- /// once all partitions have finished or been dropped, sending the collected metrics back to
38- /// the coordinator through the `CoordinatorChannel` side channel.
39- pub ( super ) metrics_tx : Arc < std:: sync:: Mutex < Option < oneshot:: Sender < PreOrderTaskMetrics > > > > ,
40- }
41-
42- impl TaskData {
43- /// Returns the number of partitions remaining to be processed.
44- pub ( crate ) fn num_partitions_remaining ( & self ) -> usize {
45- self . num_partitions_remaining
46- . load ( std:: sync:: atomic:: Ordering :: Relaxed )
47- }
48-
49- /// Returns the total number of partitions in this task.
50- pub ( crate ) fn total_partitions ( & self ) -> usize {
51- self . plan . properties ( ) . partitioning . partition_count ( )
52- }
53- }
54-
5526impl Worker {
56- /// Sets the plan for a task and returns a receiver that will yield the collected metrics
57- /// once all partitions of that task have finished executing (or been dropped early).
58- pub ( crate ) async fn impl_set_plan (
27+ pub ( super ) async fn impl_coordinator_channel (
5928 & self ,
60- request : SetPlanRequest ,
61- grpc_headers : MetadataMap ,
62- ) -> Result <
63- (
64- RemoteWorkUnitFeedTxs ,
65- oneshot:: Receiver < PreOrderTaskMetrics > ,
66- ) ,
67- Status ,
68- > {
29+ request : Request < Streaming < CoordinatorToWorkerMsg > > ,
30+ ) -> Result < Response < <Worker as WorkerService >:: CoordinatorChannelStream > , Status > {
31+ let ( grpc_headers, _ext, mut body) = request. into_parts ( ) ;
32+
33+ // The first message must be a SetPlanRequest.
34+ let Some ( msg) = body. next ( ) . await else {
35+ return Err ( Status :: internal ( "Empty Coordinator stream" ) ) ;
36+ } ;
37+ let Some ( Inner :: SetPlanRequest ( request) ) = msg?. inner else {
38+ return Err ( Status :: internal (
39+ "First Coordinator message must be SetPlanRequest" ,
40+ ) ) ;
41+ } ;
6942 let key = request. task_key . ok_or_else ( missing ( "task_key" ) ) ?;
7043
7144 let entry = self
@@ -94,11 +67,12 @@ impl Worker {
9467 . with_extension ( Arc :: new ( LocalWorkerContext {
9568 self_url : Url :: parse ( & request. target_worker_url )
9669 . map_err ( |e| DataFusionError :: External ( Box :: new ( e) ) ) ?,
97- } ) ) ;
98- set_distributed_option_extension_from_headers :: < DistributedConfig > ( & mut cfg , & headers) ?;
70+ } ) )
71+ . with_distributed_option_extension_from_headers :: < DistributedConfig > ( & headers) ?;
9972
100- let shuffle_batch_size =
101- DistributedConfig :: from_config_options ( cfg. options ( ) ) ?. shuffle_batch_size ;
73+ let d_cfg = DistributedConfig :: from_config_options ( cfg. options ( ) ) ?;
74+ let shuffle_batch_size = d_cfg. shuffle_batch_size ;
75+ let collect_metrics = d_cfg. collect_metrics ;
10276 if shuffle_batch_size != 0 {
10377 cfg = cfg. with_batch_size ( shuffle_batch_size) ;
10478 }
@@ -129,7 +103,10 @@ impl Worker {
129103 plan,
130104 task_ctx,
131105 num_partitions_remaining : Arc :: new ( AtomicUsize :: new ( total_partitions) ) ,
132- metrics_tx : Arc :: new ( std:: sync:: Mutex :: new ( Some ( metrics_tx) ) ) ,
106+ metrics_tx : match collect_metrics {
107+ true => Arc :: new ( std:: sync:: Mutex :: new ( Some ( metrics_tx) ) ) ,
108+ false => Arc :: new ( std:: sync:: Mutex :: new ( None ) ) ,
109+ } ,
133110 } )
134111 } ;
135112
@@ -138,7 +115,39 @@ impl Worker {
138115 "Logic error while setting plan for TaskKey {key:?}: the plan was set twice. This is a bug in datafusion-distributed, please report it."
139116 ) )
140117 } ) ?;
141- Ok ( ( remote_work_unit_feed_registry. senders , metrics_rx) )
118+
119+ // Continue reading remaining messages (work unit feed data) in the background.
120+ let work_unit_senders = remote_work_unit_feed_registry. senders ;
121+ tokio:: spawn ( async move {
122+ while let Some ( Ok ( msg) ) = body. next ( ) . await {
123+ let Some ( Inner :: WorkUnit ( msg) ) = msg. inner else {
124+ continue ;
125+ } ;
126+ let Ok ( id) = deserialize_uuid ( & msg. id ) else {
127+ continue ;
128+ } ;
129+ let Some ( tx) = work_unit_senders. get ( & ( id, msg. partition as usize ) ) else {
130+ continue ;
131+ } ;
132+ if tx. send ( Ok ( msg. body ) ) . is_err ( ) {
133+ break ; // channel closed
134+ }
135+ }
136+ } ) ;
137+
138+ // Stream back the metrics once the task finishes executing.
139+ // The oneshot receiver resolves when impl_execute_task sends the collected
140+ // metrics after all partitions have finished or been dropped.
141+ let metrics_stream = metrics_rx. into_stream ( ) ;
142+ let metrics_stream = metrics_stream. filter_map ( |task_metrics| async move {
143+ match task_metrics {
144+ Ok ( task_metrics) => Some ( WorkerToCoordinatorMsg {
145+ inner : Some ( worker_to_coordinator_msg:: Inner :: TaskMetrics ( task_metrics) ) ,
146+ } ) ,
147+ Err ( _) => None , // channel dropped without sending any message
148+ }
149+ } ) ;
150+ Ok ( Response :: new ( metrics_stream. map ( Ok ) . boxed ( ) ) )
142151 }
143152}
144153
0 commit comments