1use std::{
2 collections::HashMap,
3 net::SocketAddr,
4 sync::{
5 Arc, Mutex,
6 atomic::{AtomicU64, Ordering},
7 },
8 time::Duration,
9};
10
11use datum_net::quic::quinn;
12use prost::Message as ProstMessage;
13use tokio::{
14 io::{AsyncRead, AsyncWrite, AsyncWriteExt},
15 net::TcpStream,
16 sync::{mpsc, oneshot},
17 task::JoinHandle,
18};
19
20use crate::dcp::{
21 DcpError, DcpResult,
22 frame::{read_frame, write_frame},
23 proto::{
24 ClientKind, ClusterEvent, ClusterJobList, ClusterNodeInfo, ClusterNodeList,
25 CompleteShardingAsk, ConfigValue, DcpFrame, Event, ForwardShardEnvelopes, GetConfig,
26 GetShardAllocations, Hello, JobStatus as WireJobStatus, JobStatusRequest, ListClusterJobs,
27 ListJobs, MetricFrame, MetricSample, OpenShardPipe, PlacementSpec, PutConfig,
28 RememberClusterAssignment, RememberShardAllocations, Request, Response, ResponseStatus,
29 ShardAllocation, ShardAllocationRequest, ShardAllocationTable, ShardEnvelopeBatchResult,
30 ShardPipeFrame, StartJob, StopJob, SubmitClusterJob, SubscribeClusterEvents,
31 SubscribeEvents, SubscribeMetrics, UnsubscribeMetrics, dcp_frame, request,
32 },
33 server::connect_quic_stream,
34};
35
36type PendingMap = HashMap<u64, oneshot::Sender<DcpResult<Response>>>;
37
38#[derive(Clone)]
39pub struct DcpClient {
40 inner: Arc<ClientInner>,
41 _reader_task: Arc<JoinHandle<()>>,
42 _writer_task: Arc<JoinHandle<()>>,
43 _quic_endpoint: Option<quinn::Endpoint>,
44 _quic_connection: Option<quinn::Connection>,
45}
46
47struct ClientInner {
48 outbound: mpsc::Sender<DcpFrame>,
49 pending: Mutex<PendingMap>,
50 event_subscriptions: Mutex<HashMap<u64, mpsc::Sender<Event>>>,
51 cluster_event_subscriptions: Mutex<HashMap<u64, mpsc::Sender<ClusterEvent>>>,
52 metric_subscriptions: Mutex<HashMap<u64, mpsc::Sender<MetricSample>>>,
53 next_request_id: AtomicU64,
54 disconnect_error: Arc<Mutex<Option<String>>>,
55}
56
57pub struct EventSubscription {
58 receiver: mpsc::Receiver<Event>,
59 disconnect_error: Arc<Mutex<Option<String>>>,
60}
61
62pub struct ClusterEventSubscription {
63 receiver: mpsc::Receiver<ClusterEvent>,
64 disconnect_error: Arc<Mutex<Option<String>>>,
65}
66
67impl ClusterEventSubscription {
68 pub async fn recv(&mut self) -> Option<ClusterEvent> {
69 self.receiver.recv().await
70 }
71
72 pub fn disconnect_reason(&self) -> Option<String> {
73 self.disconnect_error
74 .lock()
75 .unwrap_or_else(|p| p.into_inner())
76 .clone()
77 }
78}
79
80impl EventSubscription {
81 pub async fn recv(&mut self) -> Option<Event> {
82 self.receiver.recv().await
83 }
84
85 pub fn disconnect_reason(&self) -> Option<String> {
86 self.disconnect_error
87 .lock()
88 .unwrap_or_else(|p| p.into_inner())
89 .clone()
90 }
91}
92
93pub struct MetricSubscription {
94 receiver: mpsc::Receiver<MetricSample>,
95 disconnect_error: Arc<Mutex<Option<String>>>,
96 subscription_id: u64,
97 inner: Arc<ClientInner>,
98 cancelled: bool,
99}
100
101impl MetricSubscription {
102 pub async fn recv(&mut self) -> Option<MetricSample> {
103 self.receiver.recv().await
104 }
105
106 pub fn disconnect_reason(&self) -> Option<String> {
107 self.disconnect_error
108 .lock()
109 .unwrap_or_else(|p| p.into_inner())
110 .clone()
111 }
112
113 pub async fn cancel(mut self) -> DcpResult<()> {
116 self.remove_registration();
117 self.cancelled = true;
118 send_unsubscribe_metrics(&self.inner, self.subscription_id).await
119 }
120
121 fn remove_registration(&self) {
122 self.inner
123 .metric_subscriptions
124 .lock()
125 .unwrap_or_else(|p| p.into_inner())
126 .remove(&self.subscription_id);
127 }
128}
129
130impl Drop for MetricSubscription {
131 fn drop(&mut self) {
132 self.remove_registration();
133 if self.cancelled {
134 return;
135 }
136 let request_id = self.inner.next_request_id.fetch_add(1, Ordering::Relaxed);
137 let _ = self.inner.outbound.try_send(DcpFrame::request(Request {
138 request_id,
139 deadline_ms: 0,
140 command: Some(request::Command::UnsubscribeMetrics(UnsubscribeMetrics {
141 subscription_id: self.subscription_id,
142 })),
143 }));
144 }
145}
146
147#[derive(Clone)]
148pub struct ShardPipeClient {
149 outbound: mpsc::Sender<ShardPipeFrame>,
150 _reader_task: Arc<JoinHandle<()>>,
151 _writer_task: Arc<JoinHandle<()>>,
152 _quic_endpoint: Option<quinn::Endpoint>,
153 _quic_connection: Option<quinn::Connection>,
154}
155
156impl ShardPipeClient {
157 pub async fn connect_tcp(addr: SocketAddr, hello: Hello) -> DcpResult<Self> {
158 let mut stream = TcpStream::connect(addr).await?;
159 stream.set_nodelay(true)?;
160 {
161 let (mut reader, mut writer) = stream.split();
162 open_shard_pipe(&mut reader, &mut writer, hello).await?;
163 }
164 let (reader, writer) = stream.into_split();
165 Ok(Self::from_parts(reader, writer, None, None))
166 }
167
168 pub async fn connect_quic(
169 addr: SocketAddr,
170 server_name: &str,
171 client_config: quinn::ClientConfig,
172 hello: Hello,
173 ) -> DcpResult<Self> {
174 let (endpoint, connection, mut reader, mut writer) =
175 connect_quic_stream(addr, server_name, client_config).await?;
176 open_shard_pipe(&mut reader, &mut writer, hello).await?;
177 Ok(Self::from_parts(
178 reader,
179 writer,
180 Some(endpoint),
181 Some(connection),
182 ))
183 }
184
185 fn from_parts<R, W>(
186 reader: R,
187 writer: W,
188 quic_endpoint: Option<quinn::Endpoint>,
189 quic_connection: Option<quinn::Connection>,
190 ) -> Self
191 where
192 R: AsyncRead + Unpin + Send + 'static,
193 W: AsyncWrite + Unpin + Send + 'static,
194 {
195 let (outbound, outbound_receiver) = mpsc::channel(256);
196 let reader_task = tokio::spawn(async move {
197 read_shard_pipe_loop(reader).await;
198 });
199 let writer_task = tokio::spawn(async move {
200 let _ = write_shard_pipe_loop(writer, outbound_receiver).await;
201 });
202 Self {
203 outbound,
204 _reader_task: Arc::new(reader_task),
205 _writer_task: Arc::new(writer_task),
206 _quic_endpoint: quic_endpoint,
207 _quic_connection: quic_connection,
208 }
209 }
210
211 #[cfg(test)]
212 pub(crate) fn from_test_parts<R, W>(reader: R, writer: W) -> Self
213 where
214 R: AsyncRead + Unpin + Send + 'static,
215 W: AsyncWrite + Unpin + Send + 'static,
216 {
217 Self::from_parts(reader, writer, None, None)
218 }
219
220 pub async fn send(&self, frame: ShardPipeFrame) -> DcpResult<()> {
221 self.outbound
222 .send(frame)
223 .await
224 .map_err(|_| DcpError::Closed)
225 }
226}
227
228const DEFAULT_LOCAL_REQUEST_TIMEOUT_MS: u64 = 30_000;
229
230impl DcpClient {
231 pub async fn connect_tcp(addr: SocketAddr, hello: Hello) -> DcpResult<Self> {
232 let stream = TcpStream::connect(addr).await?;
233 stream.set_nodelay(true)?;
234 let (reader, writer) = stream.into_split();
235 let client = Self::from_parts(reader, writer, None, None);
236 client.hello(hello).await?;
237 Ok(client)
238 }
239
240 pub async fn connect_quic(
241 addr: SocketAddr,
242 server_name: &str,
243 client_config: quinn::ClientConfig,
244 hello: Hello,
245 ) -> DcpResult<Self> {
246 let (endpoint, connection, reader, writer) =
247 connect_quic_stream(addr, server_name, client_config).await?;
248 let client = Self::from_parts(reader, writer, Some(endpoint), Some(connection));
249 client.hello(hello).await?;
250 Ok(client)
251 }
252
253 fn from_parts<R, W>(
254 reader: R,
255 writer: W,
256 quic_endpoint: Option<quinn::Endpoint>,
257 quic_connection: Option<quinn::Connection>,
258 ) -> Self
259 where
260 R: AsyncRead + Unpin + Send + 'static,
261 W: AsyncWrite + Unpin + Send + 'static,
262 {
263 let (outbound, outbound_receiver) = mpsc::channel(256);
264 let disconnect_error = Arc::new(Mutex::new(None));
265 let inner = Arc::new(ClientInner {
266 outbound,
267 pending: Mutex::new(HashMap::new()),
268 event_subscriptions: Mutex::new(HashMap::new()),
269 cluster_event_subscriptions: Mutex::new(HashMap::new()),
270 metric_subscriptions: Mutex::new(HashMap::new()),
271 next_request_id: AtomicU64::new(1),
272 disconnect_error: Arc::clone(&disconnect_error),
273 });
274 let reader_task = {
275 let inner = Arc::clone(&inner);
276 tokio::spawn(async move {
277 read_loop(reader, inner).await;
278 })
279 };
280 let writer_task = tokio::spawn(async move {
281 let _ = write_loop(writer, outbound_receiver).await;
282 });
283 Self {
284 inner,
285 _reader_task: Arc::new(reader_task),
286 _writer_task: Arc::new(writer_task),
287 _quic_endpoint: quic_endpoint,
288 _quic_connection: quic_connection,
289 }
290 }
291
292 #[cfg(test)]
293 pub(crate) fn from_test_parts<R, W>(reader: R, writer: W) -> Self
294 where
295 R: AsyncRead + Unpin + Send + 'static,
296 W: AsyncWrite + Unpin + Send + 'static,
297 {
298 Self::from_parts(reader, writer, None, None)
299 }
300
301 pub async fn hello(&self, hello: Hello) -> DcpResult<()> {
302 let response = self
303 .send_frame_with_response(0, DcpFrame::hello(hello))
304 .await?;
305 ensure_ok(response).map(|_| ())
306 }
307
308 pub async fn request(&self, request: Request) -> DcpResult<Response> {
309 let response = self
310 .send_frame_with_response(request.request_id, DcpFrame::request(request))
311 .await?;
312 ensure_ok(response)
313 }
314
315 pub async fn list_jobs(&self) -> DcpResult<Vec<WireJobStatus>> {
316 let response = self
317 .send_command(
318 request::Command::ListJobs(ListJobs {}),
319 DEFAULT_LOCAL_REQUEST_TIMEOUT_MS,
320 )
321 .await?;
322 let list = crate::dcp::proto::JobList::decode(response.payload.as_slice())?;
323 Ok(list.jobs)
324 }
325
326 pub async fn list_cluster_jobs(&self, timeout_ms: u64) -> DcpResult<ClusterJobList> {
327 let request = self.send_command(
328 request::Command::ListClusterJobs(ListClusterJobs { timeout_ms }),
329 cluster_request_deadline(timeout_ms),
330 );
331 let response = timeout_cluster_request(timeout_ms, request).await?;
332 Ok(ClusterJobList::decode(response.payload.as_slice())?)
333 }
334
335 pub async fn cluster_node_info(&self, timeout_ms: u64) -> DcpResult<ClusterNodeList> {
336 let request = self.send_command(
337 request::Command::ClusterNodeInfo(ClusterNodeInfo { timeout_ms }),
338 cluster_request_deadline(timeout_ms),
339 );
340 let response = timeout_cluster_request(timeout_ms, request).await?;
341 Ok(ClusterNodeList::decode(response.payload.as_slice())?)
342 }
343
344 pub async fn start_job(
345 &self,
346 factory_name: impl Into<String>,
347 instance_name: impl Into<String>,
348 params: HashMap<String, String>,
349 ) -> DcpResult<WireJobStatus> {
350 self.job_status_response(request::Command::StartJob(StartJob {
351 factory_name: factory_name.into(),
352 instance_name: instance_name.into(),
353 params,
354 cluster: None,
355 }))
356 .await
357 }
358
359 pub(crate) async fn start_cluster_job_on_node(
360 &self,
361 factory_name: impl Into<String>,
362 instance_name: impl Into<String>,
363 params: HashMap<String, String>,
364 cluster: crate::dcp::proto::ClusterJobStart,
365 ) -> DcpResult<WireJobStatus> {
366 self.job_status_response(request::Command::StartJob(StartJob {
367 factory_name: factory_name.into(),
368 instance_name: instance_name.into(),
369 params,
370 cluster: Some(cluster),
371 }))
372 .await
373 }
374
375 pub async fn submit_cluster_job(
376 &self,
377 factory_name: impl Into<String>,
378 instance_name: impl Into<String>,
379 params: HashMap<String, String>,
380 placement: PlacementSpec,
381 timeout_ms: u64,
382 ) -> DcpResult<WireJobStatus> {
383 let request = self.send_command(
384 request::Command::SubmitClusterJob(SubmitClusterJob {
385 factory_name: factory_name.into(),
386 instance_name: instance_name.into(),
387 params,
388 placement: Some(placement),
389 timeout_ms,
390 }),
391 cluster_request_deadline(timeout_ms),
392 );
393 let response = timeout_cluster_request(timeout_ms, request).await?;
394 Ok(WireJobStatus::decode(response.payload.as_slice())?)
395 }
396
397 pub async fn drain_job(&self, name: impl Into<String>) -> DcpResult<WireJobStatus> {
398 self.job_status_response(request::Command::DrainJob(crate::dcp::proto::DrainJob {
399 name: name.into(),
400 cluster: false,
401 }))
402 .await
403 }
404
405 pub async fn drain_cluster_job(
406 &self,
407 name: impl Into<String>,
408 timeout_ms: u64,
409 ) -> DcpResult<WireJobStatus> {
410 let request = self.send_command(
411 request::Command::DrainJob(crate::dcp::proto::DrainJob {
412 name: name.into(),
413 cluster: true,
414 }),
415 cluster_request_deadline(timeout_ms),
416 );
417 let response = timeout_cluster_request(timeout_ms, request).await?;
418 Ok(WireJobStatus::decode(response.payload.as_slice())?)
419 }
420
421 pub async fn stop_job(&self, name: impl Into<String>) -> DcpResult<WireJobStatus> {
422 self.job_status_response(request::Command::StopJob(StopJob {
423 name: name.into(),
424 cluster: false,
425 }))
426 .await
427 }
428
429 pub async fn stop_cluster_job(
430 &self,
431 name: impl Into<String>,
432 timeout_ms: u64,
433 ) -> DcpResult<WireJobStatus> {
434 let request = self.send_command(
435 request::Command::StopJob(StopJob {
436 name: name.into(),
437 cluster: true,
438 }),
439 cluster_request_deadline(timeout_ms),
440 );
441 let response = timeout_cluster_request(timeout_ms, request).await?;
442 Ok(WireJobStatus::decode(response.payload.as_slice())?)
443 }
444
445 pub async fn restart_job(&self, name: impl Into<String>) -> DcpResult<WireJobStatus> {
446 self.job_status_response(request::Command::RestartJob(
447 crate::dcp::proto::RestartJob {
448 name: name.into(),
449 cluster: false,
450 },
451 ))
452 .await
453 }
454
455 pub async fn job_status(&self, name: impl Into<String>) -> DcpResult<WireJobStatus> {
456 self.job_status_response(request::Command::JobStatus(JobStatusRequest {
457 name: name.into(),
458 cluster: false,
459 }))
460 .await
461 }
462
463 pub async fn cluster_job_status(
464 &self,
465 name: impl Into<String>,
466 timeout_ms: u64,
467 ) -> DcpResult<WireJobStatus> {
468 let request = self.send_command(
469 request::Command::JobStatus(JobStatusRequest {
470 name: name.into(),
471 cluster: true,
472 }),
473 cluster_request_deadline(timeout_ms),
474 );
475 let response = timeout_cluster_request(timeout_ms, request).await?;
476 Ok(WireJobStatus::decode(response.payload.as_slice())?)
477 }
478
479 pub(crate) async fn remember_cluster_assignment(
480 &self,
481 instance_name: impl Into<String>,
482 assignment: Option<crate::dcp::proto::ClusterJobStart>,
483 tombstone_placement_generation: u64,
484 tombstone_coordinator_node_id: impl Into<String>,
485 ) -> DcpResult<()> {
486 let response = self
487 .send_command(
488 request::Command::RememberClusterAssignment(RememberClusterAssignment {
489 instance_name: instance_name.into(),
490 assignment,
491 tombstone_placement_generation,
492 tombstone_coordinator_node_id: tombstone_coordinator_node_id.into(),
493 }),
494 0,
495 )
496 .await?;
497 let _ = response;
498 Ok(())
499 }
500
501 pub async fn allocate_shard(
502 &self,
503 type_name: impl Into<String>,
504 shard_id: impl Into<String>,
505 timeout_ms: u64,
506 ) -> DcpResult<ShardAllocation> {
507 let request = self.send_command(
508 request::Command::AllocateShard(ShardAllocationRequest {
509 type_name: type_name.into(),
510 shard_id: shard_id.into(),
511 timeout_ms,
512 }),
513 cluster_request_deadline(timeout_ms),
514 );
515 let response = timeout_cluster_request(timeout_ms, request).await?;
516 Ok(ShardAllocation::decode(response.payload.as_slice())?)
517 }
518
519 pub async fn remember_shard_allocations(&self, table: ShardAllocationTable) -> DcpResult<()> {
520 let response = self
521 .send_command(
522 request::Command::RememberShardAllocations(RememberShardAllocations {
523 table: Some(table),
524 }),
525 0,
526 )
527 .await?;
528 let _ = response;
529 Ok(())
530 }
531
532 pub async fn get_shard_allocations(
533 &self,
534 type_name: impl Into<String>,
535 timeout_ms: u64,
536 ) -> DcpResult<ShardAllocationTable> {
537 let request = self.send_command(
538 request::Command::GetShardAllocations(GetShardAllocations {
539 type_name: type_name.into(),
540 }),
541 cluster_request_deadline(timeout_ms),
542 );
543 let response = timeout_cluster_request(timeout_ms, request).await?;
544 Ok(ShardAllocationTable::decode(response.payload.as_slice())?)
545 }
546
547 pub async fn forward_shard_envelopes(
548 &self,
549 batch: ForwardShardEnvelopes,
550 timeout_ms: u64,
551 ) -> DcpResult<ShardEnvelopeBatchResult> {
552 let request = self.send_command(
553 request::Command::ForwardShardEnvelopes(batch),
554 cluster_request_deadline(timeout_ms),
555 );
556 let response = timeout_cluster_request(timeout_ms, request).await?;
557 Ok(ShardEnvelopeBatchResult::decode(
558 response.payload.as_slice(),
559 )?)
560 }
561
562 pub async fn complete_sharding_ask(&self, reply: CompleteShardingAsk) -> DcpResult<()> {
563 let response = self
564 .send_command(request::Command::CompleteShardingAsk(reply), 0)
565 .await?;
566 let _ = response;
567 Ok(())
568 }
569
570 pub async fn subscribe_events(&self) -> DcpResult<EventSubscription> {
571 let request_id = self.next_request_id();
572 let (sender, receiver) = mpsc::channel(256);
573 self.inner
574 .event_subscriptions
575 .lock()
576 .expect("DCP event subscriptions poisoned")
577 .insert(request_id, sender);
578 let request = Request {
579 request_id,
580 deadline_ms: DEFAULT_LOCAL_REQUEST_TIMEOUT_MS,
581 command: Some(request::Command::SubscribeEvents(SubscribeEvents {
582 buffer: 0,
583 })),
584 };
585 if let Err(error) = self.request(request).await {
586 self.inner
587 .event_subscriptions
588 .lock()
589 .expect("DCP event subscriptions poisoned")
590 .remove(&request_id);
591 return Err(error);
592 }
593 Ok(EventSubscription {
594 receiver,
595 disconnect_error: Arc::clone(&self.inner.disconnect_error),
596 })
597 }
598
599 pub async fn subscribe_cluster_events(&self) -> DcpResult<ClusterEventSubscription> {
600 let request_id = self.next_request_id();
601 let (sender, receiver) = mpsc::channel(256);
602 self.inner
603 .cluster_event_subscriptions
604 .lock()
605 .expect("DCP cluster event subscriptions poisoned")
606 .insert(request_id, sender);
607 let request = Request {
608 request_id,
609 deadline_ms: DEFAULT_LOCAL_REQUEST_TIMEOUT_MS,
610 command: Some(request::Command::SubscribeClusterEvents(
611 SubscribeClusterEvents {},
612 )),
613 };
614 if let Err(error) = self.request(request).await {
615 self.inner
616 .cluster_event_subscriptions
617 .lock()
618 .expect("DCP cluster event subscriptions poisoned")
619 .remove(&request_id);
620 return Err(error);
621 }
622 Ok(ClusterEventSubscription {
623 receiver,
624 disconnect_error: Arc::clone(&self.inner.disconnect_error),
625 })
626 }
627
628 pub async fn subscribe_metrics(&self, interval_ms: u64) -> DcpResult<MetricSubscription> {
629 self.subscribe_metrics_inner(interval_ms, Vec::new(), false)
630 .await
631 }
632
633 pub(crate) async fn subscribe_local_metrics(
634 &self,
635 interval_ms: u64,
636 job_names: Vec<String>,
637 ) -> DcpResult<MetricSubscription> {
638 self.subscribe_metrics_inner(interval_ms, job_names, true)
639 .await
640 }
641
642 async fn subscribe_metrics_inner(
643 &self,
644 interval_ms: u64,
645 job_names: Vec<String>,
646 local_only: bool,
647 ) -> DcpResult<MetricSubscription> {
648 let request_id = self.next_request_id();
649 let (sender, receiver) = mpsc::channel(16);
650 self.inner
651 .metric_subscriptions
652 .lock()
653 .expect("DCP metric subscriptions poisoned")
654 .insert(request_id, sender);
655 let request = Request {
656 request_id,
657 deadline_ms: DEFAULT_LOCAL_REQUEST_TIMEOUT_MS,
658 command: Some(request::Command::SubscribeMetrics(SubscribeMetrics {
659 interval_ms,
660 job_names,
661 local_only,
662 })),
663 };
664 if let Err(error) = self.request(request).await {
665 self.inner
666 .metric_subscriptions
667 .lock()
668 .expect("DCP metric subscriptions poisoned")
669 .remove(&request_id);
670 return Err(error);
671 }
672 Ok(MetricSubscription {
673 receiver,
674 disconnect_error: Arc::clone(&self.inner.disconnect_error),
675 subscription_id: request_id,
676 inner: Arc::clone(&self.inner),
677 cancelled: false,
678 })
679 }
680
681 pub async fn get_config(&self, key: impl Into<String>) -> DcpResult<ConfigValue> {
682 let response = self
683 .send_command(
684 request::Command::GetConfig(GetConfig { key: key.into() }),
685 DEFAULT_LOCAL_REQUEST_TIMEOUT_MS,
686 )
687 .await?;
688 Ok(ConfigValue::decode(response.payload.as_slice())?)
689 }
690
691 pub async fn put_config(
692 &self,
693 key: impl Into<String>,
694 value: impl Into<String>,
695 ) -> DcpResult<ConfigValue> {
696 let response = self
697 .send_command(
698 request::Command::PutConfig(PutConfig {
699 key: key.into(),
700 value: value.into(),
701 }),
702 DEFAULT_LOCAL_REQUEST_TIMEOUT_MS,
703 )
704 .await?;
705 Ok(ConfigValue::decode(response.payload.as_slice())?)
706 }
707
708 async fn job_status_response(&self, command: request::Command) -> DcpResult<WireJobStatus> {
709 let response = self
710 .send_command(command, DEFAULT_LOCAL_REQUEST_TIMEOUT_MS)
711 .await?;
712 Ok(WireJobStatus::decode(response.payload.as_slice())?)
713 }
714
715 async fn send_command(
716 &self,
717 command: request::Command,
718 deadline_ms: u64,
719 ) -> DcpResult<Response> {
720 let request_id = self.next_request_id();
721 let request = Request {
722 request_id,
723 deadline_ms,
724 command: Some(command),
725 };
726 self.request(request).await
727 }
728
729 async fn send_frame_with_response(
730 &self,
731 request_id: u64,
732 frame: DcpFrame,
733 ) -> DcpResult<Response> {
734 let (sender, receiver) = oneshot::channel();
735 self.inner
736 .pending
737 .lock()
738 .expect("DCP pending map poisoned")
739 .insert(request_id, sender);
740 if self.inner.outbound.send(frame).await.is_err() {
741 self.inner
742 .pending
743 .lock()
744 .expect("DCP pending map poisoned")
745 .remove(&request_id);
746 return Err(DcpError::Closed);
747 }
748 receiver.await.map_err(|_| DcpError::Closed)?
749 }
750
751 fn next_request_id(&self) -> u64 {
752 self.inner.next_request_id.fetch_add(1, Ordering::Relaxed)
753 }
754}
755
756async fn send_unsubscribe_metrics(inner: &Arc<ClientInner>, subscription_id: u64) -> DcpResult<()> {
757 let request_id = inner.next_request_id.fetch_add(1, Ordering::Relaxed);
758 inner
759 .outbound
760 .send(DcpFrame::request(Request {
761 request_id,
762 deadline_ms: 0,
763 command: Some(request::Command::UnsubscribeMetrics(UnsubscribeMetrics {
764 subscription_id,
765 })),
766 }))
767 .await
768 .map_err(|_| DcpError::Closed)
769}
770
771async fn read_loop<R>(mut reader: R, inner: Arc<ClientInner>)
772where
773 R: AsyncRead + Unpin,
774{
775 loop {
776 match read_frame(&mut reader).await {
777 Ok(Some(frame)) => handle_inbound_frame(frame, &inner).await,
778 Ok(None) => {
779 set_disconnect_error(&inner, DcpError::Closed);
780 fail_pending(&inner, DcpError::Closed);
781 clear_subscriptions(&inner);
782 break;
783 }
784 Err(error) => {
785 set_disconnect_error(
786 &inner,
787 DcpError::Protocol(format!("DCP read error: {}", error)),
788 );
789 fail_pending(&inner, error);
790 clear_subscriptions(&inner);
791 break;
792 }
793 }
794 }
795}
796
797fn clear_subscriptions(inner: &ClientInner) {
798 inner
799 .event_subscriptions
800 .lock()
801 .unwrap_or_else(|p| p.into_inner())
802 .clear();
803 inner
804 .cluster_event_subscriptions
805 .lock()
806 .unwrap_or_else(|p| p.into_inner())
807 .clear();
808 inner
809 .metric_subscriptions
810 .lock()
811 .unwrap_or_else(|p| p.into_inner())
812 .clear();
813}
814
815fn set_disconnect_error(inner: &ClientInner, error: DcpError) {
816 *inner
817 .disconnect_error
818 .lock()
819 .unwrap_or_else(|p| p.into_inner()) = Some(error.to_string());
820}
821
822async fn handle_inbound_frame(frame: DcpFrame, inner: &Arc<ClientInner>) {
823 match frame.frame {
824 Some(dcp_frame::Frame::Response(response)) => {
825 if let Some(sender) = inner
826 .pending
827 .lock()
828 .expect("DCP pending map poisoned")
829 .remove(&response.request_id)
830 {
831 let _ = sender.send(Ok(response));
832 }
833 }
834 Some(dcp_frame::Frame::Event(event_frame)) => {
835 if let Some(event) = event_frame.event {
836 let sender = inner
837 .event_subscriptions
838 .lock()
839 .expect("DCP event subscriptions poisoned")
840 .get(&event_frame.subscription_id)
841 .cloned();
842 if let Some(sender) = sender {
843 let _ = sender.send(event).await;
844 }
845 }
846 }
847 Some(dcp_frame::Frame::ClusterEvent(event_frame)) => {
848 if let Some(event) = event_frame.event {
849 let sender = inner
850 .cluster_event_subscriptions
851 .lock()
852 .expect("DCP cluster event subscriptions poisoned")
853 .get(&event_frame.subscription_id)
854 .cloned();
855 if let Some(sender) = sender {
856 let _ = sender.send(event).await;
857 }
858 }
859 }
860 Some(dcp_frame::Frame::Metric(MetricFrame {
861 subscription_id,
862 sample: Some(sample),
863 })) => {
864 let sender = inner
865 .metric_subscriptions
866 .lock()
867 .expect("DCP metric subscriptions poisoned")
868 .get(&subscription_id)
869 .cloned();
870 if let Some(sender) = sender {
871 let _ = sender.send(sample).await;
872 }
873 }
874 _ => {}
875 }
876}
877
878fn fail_pending(inner: &ClientInner, error: DcpError) {
879 let pending = std::mem::take(&mut *inner.pending.lock().expect("DCP pending map poisoned"));
880 let message = error.to_string();
881 for (_, sender) in pending {
882 let _ = sender.send(Err(DcpError::Protocol(message.clone())));
883 }
884}
885
886async fn write_loop<W>(mut writer: W, mut outbound: mpsc::Receiver<DcpFrame>) -> DcpResult<()>
887where
888 W: AsyncWrite + Unpin,
889{
890 while let Some(frame) = outbound.recv().await {
891 write_frame(&mut writer, &frame).await?;
892 }
893 let _ = writer.shutdown().await;
894 Ok(())
895}
896
897async fn open_shard_pipe<R, W>(reader: &mut R, writer: &mut W, hello: Hello) -> DcpResult<()>
898where
899 R: AsyncRead + Unpin,
900 W: AsyncWrite + Unpin,
901{
902 write_frame(writer, &DcpFrame::hello(hello)).await?;
903 let Some(response) = read_frame(reader).await? else {
904 return Err(DcpError::Closed);
905 };
906 match response.frame {
907 Some(dcp_frame::Frame::Response(response)) => {
908 ensure_ok(response)?;
909 }
910 _ => {
911 return Err(DcpError::Protocol(
912 "DCP shard pipe hello did not receive a response".to_owned(),
913 ));
914 }
915 }
916
917 let request = Request {
918 request_id: 1,
919 deadline_ms: 0,
920 command: Some(request::Command::OpenShardPipe(OpenShardPipe {})),
921 };
922 write_frame(writer, &DcpFrame::request(request)).await?;
923 let Some(response) = read_frame(reader).await? else {
924 return Err(DcpError::Closed);
925 };
926 match response.frame {
927 Some(dcp_frame::Frame::Response(response)) => ensure_ok(response).map(|_| ()),
928 _ => Err(DcpError::Protocol(
929 "DCP shard pipe open did not receive a response".to_owned(),
930 )),
931 }
932}
933
934async fn read_shard_pipe_loop<R>(mut reader: R)
935where
936 R: AsyncRead + Unpin,
937{
938 while let Ok(Some(_frame)) = read_frame(&mut reader).await {}
939}
940
941async fn write_shard_pipe_loop<W>(
942 mut writer: W,
943 mut outbound: mpsc::Receiver<ShardPipeFrame>,
944) -> DcpResult<()>
945where
946 W: AsyncWrite + Unpin,
947{
948 while let Some(frame) = outbound.recv().await {
949 write_frame(&mut writer, &DcpFrame::shard_pipe(frame)).await?;
950 }
951 let _ = writer.shutdown().await;
952 Ok(())
953}
954
955fn ensure_ok(response: Response) -> DcpResult<Response> {
956 let status = response.response_status();
957 if status == ResponseStatus::Ok {
958 Ok(response)
959 } else {
960 Err(DcpError::response(status, response.message))
961 }
962}
963
964async fn timeout_cluster_request<F>(timeout_ms: u64, request: F) -> DcpResult<Response>
965where
966 F: std::future::Future<Output = DcpResult<Response>>,
967{
968 if timeout_ms == 0 {
969 return request.await;
970 }
971 tokio::time::timeout(
972 Duration::from_millis(timeout_ms).saturating_add(Duration::from_millis(250)),
973 request,
974 )
975 .await
976 .map_err(|_| {
977 DcpError::response(
978 ResponseStatus::DeadlineExceeded,
979 "cluster request timed out",
980 )
981 })?
982}
983
984fn cluster_request_deadline(timeout_ms: u64) -> u64 {
985 if timeout_ms == 0 {
986 0
987 } else {
988 timeout_ms.saturating_add(250)
989 }
990}
991
992#[must_use]
993pub fn default_hello(node_id: impl Into<String>, client_kind: ClientKind) -> Hello {
994 Hello::new(node_id, client_kind)
995}