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