1use std::{
2 any::Any,
3 collections::{HashMap, HashSet, VecDeque},
4 future::Future,
5 panic::{AssertUnwindSafe, catch_unwind},
6 rc::Rc,
7 sync::{
8 Arc, Mutex,
9 atomic::{AtomicU64, Ordering},
10 },
11 thread::JoinHandle,
12 time::Duration,
13};
14
15use futures::{FutureExt, future::BoxFuture};
16use phoenix_channel_client::{
17 Channel, ChannelEvent, ChannelStatus, ClientError, ConnectContext, ConnectionConfig, Endpoint,
18 EndpointError, JoinContext, Options, PresenceEvent, ReconnectAction, ReconnectContext, Reply,
19 Socket, SocketEvent, SocketStatus, SubscriptionEvent, TelemetryEvent,
20};
21use phoenix_channel_runtime::{
22 Payload, PresenceError, PresenceState, PresenceTracker, PresenceUpdate, ProtocolEvent,
23};
24use serde::{Serialize, de::DeserializeOwned};
25use serde_json::Value;
26use thiserror::Error;
27use tokio::sync::{broadcast, mpsc, oneshot, watch};
28
29use crate::{NativeConnector, NativeTimer, NativeTransportOptions};
30
31pub type NativeConnectionConfigLoader = Arc<
33 dyn Fn(ConnectContext) -> BoxFuture<'static, Result<ConnectionConfig, String>> + Send + Sync,
34>;
35
36pub type NativeJoinPayloadLoader =
38 Arc<dyn Fn(JoinContext) -> BoxFuture<'static, Result<Value, String>> + Send + Sync>;
39
40pub type NativeTelemetryHook = Arc<dyn Fn(&TelemetryEvent) + Send + Sync>;
42pub type NativeReconnectPolicy = Arc<dyn Fn(ReconnectContext) -> ReconnectAction + Send + Sync>;
44
45pub fn native_static_connection_config(config: ConnectionConfig) -> NativeConnectionConfigLoader {
47 Arc::new(move |_| {
48 let config = config.clone();
49 async move { Ok(config) }.boxed()
50 })
51}
52
53pub fn native_static_join_payload(payload: Value) -> NativeJoinPayloadLoader {
55 Arc::new(move |_| {
56 let payload = payload.clone();
57 async move { Ok(payload) }.boxed()
58 })
59}
60
61#[derive(Clone)]
63pub struct NativeOptions {
64 heartbeat_interval: Duration,
65 heartbeat_timeout: Duration,
66 connect_timeout: Duration,
67 join_timeout: Duration,
68 call_timeout: Duration,
69 leave_timeout: Duration,
70 reconnect_delays: Vec<Duration>,
71 rejoin_delays: Vec<Duration>,
72 command_capacity: usize,
73 push_buffer_capacity: usize,
74 event_capacity: usize,
75 transport: NativeTransportOptions,
76 telemetry: Option<NativeTelemetryHook>,
77 reconnect_policy: Option<NativeReconnectPolicy>,
78}
79
80impl Default for NativeOptions {
81 fn default() -> Self {
82 let retry_delays = vec![
83 Duration::ZERO,
84 Duration::from_secs(1),
85 Duration::from_secs(2),
86 Duration::from_secs(5),
87 Duration::from_secs(10),
88 ];
89 Self {
90 heartbeat_interval: Duration::from_secs(30),
91 heartbeat_timeout: Duration::from_secs(30),
92 connect_timeout: Duration::from_secs(10),
93 join_timeout: Duration::from_secs(10),
94 call_timeout: Duration::from_secs(10),
95 leave_timeout: Duration::from_secs(10),
96 reconnect_delays: retry_delays.clone(),
97 rejoin_delays: retry_delays,
98 command_capacity: 64,
99 push_buffer_capacity: 64,
100 event_capacity: 256,
101 transport: NativeTransportOptions::default(),
102 telemetry: None,
103 reconnect_policy: None,
104 }
105 }
106}
107
108impl NativeOptions {
109 pub fn heartbeat_interval(mut self, value: Duration) -> Self {
111 self.heartbeat_interval = value;
112 self
113 }
114
115 pub fn heartbeat_timeout(mut self, value: Duration) -> Self {
117 self.heartbeat_timeout = value;
118 self
119 }
120
121 pub fn connect_timeout(mut self, value: Duration) -> Self {
123 self.connect_timeout = value;
124 self
125 }
126
127 pub fn request_timeout(mut self, value: Duration) -> Self {
129 self.join_timeout = value;
130 self.call_timeout = value;
131 self.leave_timeout = value;
132 self
133 }
134
135 pub fn join_timeout(mut self, value: Duration) -> Self {
137 self.join_timeout = value;
138 self
139 }
140
141 pub fn call_timeout(mut self, value: Duration) -> Self {
143 self.call_timeout = value;
144 self
145 }
146
147 pub fn leave_timeout(mut self, value: Duration) -> Self {
149 self.leave_timeout = value;
150 self
151 }
152
153 pub fn reconnect_delays(mut self, values: impl IntoIterator<Item = Duration>) -> Self {
155 self.reconnect_delays = normalized_delays(values);
156 self
157 }
158
159 pub fn rejoin_delays(mut self, values: impl IntoIterator<Item = Duration>) -> Self {
161 self.rejoin_delays = normalized_delays(values);
162 self
163 }
164
165 pub fn command_capacity(mut self, value: usize) -> Self {
167 self.command_capacity = value.max(1);
168 self
169 }
170
171 pub fn push_buffer_capacity(mut self, value: usize) -> Self {
173 self.push_buffer_capacity = value;
174 self
175 }
176
177 pub fn event_capacity(mut self, value: usize) -> Self {
179 self.event_capacity = value.max(1);
180 self
181 }
182
183 pub fn telemetry(mut self, hook: NativeTelemetryHook) -> Self {
185 self.telemetry = Some(hook);
186 self
187 }
188
189 pub fn reconnect_policy(mut self, policy: NativeReconnectPolicy) -> Self {
191 self.reconnect_policy = Some(policy);
192 self
193 }
194
195 pub fn transport(mut self, options: NativeTransportOptions) -> Self {
197 self.transport = options;
198 self
199 }
200
201 fn client_options(&self) -> Options {
202 let reconnect_delays = self.reconnect_delays.clone();
203 let rejoin_delays = self.rejoin_delays.clone();
204 let mut options = Options::default()
205 .connect_on_start(false)
206 .heartbeat_interval(self.heartbeat_interval)
207 .heartbeat_timeout(self.heartbeat_timeout)
208 .connect_timeout(self.connect_timeout)
209 .join_timeout(self.join_timeout)
210 .call_timeout(self.call_timeout)
211 .leave_timeout(self.leave_timeout)
212 .event_capacity(self.event_capacity)
213 .reconnect_delay(move |attempt| retry_delay(&reconnect_delays, attempt))
214 .rejoin_delay(move |attempt| retry_delay(&rejoin_delays, attempt))
215 .command_capacity(self.command_capacity)
216 .push_buffer_capacity(self.push_buffer_capacity);
217 if let Some(hook) = &self.telemetry {
218 let hook = hook.clone();
219 options = options.telemetry(Rc::new(move |event| hook(event)));
220 }
221 if let Some(policy) = &self.reconnect_policy {
222 let policy = policy.clone();
223 options = options.reconnect_policy(move |context| policy(context));
224 }
225 options
226 }
227}
228
229fn normalized_delays(values: impl IntoIterator<Item = Duration>) -> Vec<Duration> {
230 let values = values.into_iter().collect::<Vec<_>>();
231 if values.is_empty() {
232 vec![Duration::ZERO]
233 } else {
234 values
235 }
236}
237
238fn retry_delay(delays: &[Duration], attempt: u32) -> Duration {
239 delays
240 .get(attempt as usize)
241 .or_else(|| delays.last())
242 .copied()
243 .unwrap_or(Duration::ZERO)
244}
245
246#[derive(Debug, Error)]
248pub enum NativeRuntimeError {
249 #[error(transparent)]
251 Endpoint(#[from] EndpointError),
252 #[error(transparent)]
254 Client(#[from] ClientError),
255 #[error("failed to start native client worker: {0}")]
257 WorkerStart(String),
258 #[error("native client worker stopped")]
260 WorkerStopped,
261 #[error("native client worker failed: {0}")]
263 WorkerFailed(String),
264 #[error("failed to join native client worker: {0}")]
266 WorkerJoin(String),
267}
268
269#[derive(Debug, Error)]
271pub enum NativeCallJsonError {
272 #[error(transparent)]
274 Runtime(#[from] NativeRuntimeError),
275 #[error("failed to encode request payload: {0}")]
277 Encode(serde_json::Error),
278 #[error(transparent)]
280 Reply(#[from] phoenix_channel_client::ReplyError),
281}
282
283#[derive(Clone, Debug, Eq, PartialEq)]
285pub enum NativeWorkerStatus {
286 Running,
288 Stopped,
290 Failed(String),
292}
293
294#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
296pub enum NativeEventError {
297 #[error("event receiver lagged by {0} messages")]
299 Lagged(u64),
300 #[error("event stream closed")]
302 Closed,
303}
304
305struct WorkerOwner {
306 commands: mpsc::Sender<HostCommand>,
307 control: mpsc::UnboundedSender<ControlCommand>,
308 next_request: AtomicU64,
309 worker: Mutex<Option<JoinHandle<()>>>,
310 status: watch::Receiver<NativeWorkerStatus>,
311}
312
313impl WorkerOwner {
314 fn next_request_id(&self) -> u64 {
315 let id = self.next_request.fetch_add(1, Ordering::Relaxed);
316 if id == 0 {
317 self.next_request.store(2, Ordering::Relaxed);
318 1
319 } else {
320 id
321 }
322 }
323
324 fn join(&self) -> Result<(), NativeRuntimeError> {
325 let Some(worker) = self
326 .worker
327 .lock()
328 .map_err(|error| NativeRuntimeError::WorkerJoin(error.to_string()))?
329 .take()
330 else {
331 return self.worker_result();
332 };
333 worker
334 .join()
335 .map_err(|panic| NativeRuntimeError::WorkerJoin(panic_message(panic.as_ref())))?;
336 self.worker_result()
337 }
338
339 fn worker_result(&self) -> Result<(), NativeRuntimeError> {
340 match self.status.borrow().clone() {
341 NativeWorkerStatus::Failed(message) => Err(NativeRuntimeError::WorkerFailed(message)),
342 NativeWorkerStatus::Running | NativeWorkerStatus::Stopped => Ok(()),
343 }
344 }
345}
346
347impl Drop for WorkerOwner {
348 fn drop(&mut self) {
349 let _ = self.control.send(ControlCommand::Shutdown);
350 if let Ok(worker) = self.worker.get_mut() {
351 if let Some(worker) = worker.take() {
352 let _ = worker.join();
353 }
354 }
355 }
356}
357
358#[derive(Clone)]
360pub struct NativeSocket {
361 owner: Arc<WorkerOwner>,
362 events: broadcast::Sender<SocketEvent>,
363 status: watch::Receiver<SocketStatus>,
364}
365
366impl NativeSocket {
367 pub fn spawn(
369 endpoint: impl Into<String>,
370 config: ConnectionConfig,
371 ) -> Result<Self, NativeRuntimeError> {
372 Self::spawn_with_loader(
373 endpoint,
374 native_static_connection_config(config),
375 NativeOptions::default(),
376 )
377 }
378
379 pub fn spawn_with_options(
381 endpoint: impl Into<String>,
382 config: ConnectionConfig,
383 options: NativeOptions,
384 ) -> Result<Self, NativeRuntimeError> {
385 Self::spawn_with_loader(endpoint, native_static_connection_config(config), options)
386 }
387
388 pub fn spawn_with_loader(
390 endpoint: impl Into<String>,
391 config_loader: NativeConnectionConfigLoader,
392 options: NativeOptions,
393 ) -> Result<Self, NativeRuntimeError> {
394 let endpoint = endpoint.into();
395 Endpoint::new(&endpoint)?;
396 let (commands, command_rx) = mpsc::channel(options.command_capacity);
397 let (control, control_rx) = mpsc::unbounded_channel();
398 let worker_control = control.clone();
399 let (events, _) = broadcast::channel(options.event_capacity);
400 let (status_tx, status) = watch::channel(SocketStatus::Disconnected);
401 let (worker_status_tx, worker_status) = watch::channel(NativeWorkerStatus::Running);
402 let (ready_tx, ready_rx) = std::sync::mpsc::sync_channel(1);
403 let worker_events = events.clone();
404 let worker = std::thread::Builder::new()
405 .name("phoenix-channel-runtime".into())
406 .spawn(move || {
407 let fallback_ready = ready_tx.clone();
408 let result = catch_unwind(AssertUnwindSafe(|| {
409 run_worker(WorkerBootstrap {
410 endpoint_url: endpoint,
411 config_loader,
412 options,
413 commands: command_rx,
414 control: control_rx,
415 control_tx: worker_control,
416 events: worker_events,
417 status: status_tx,
418 ready: ready_tx,
419 })
420 }));
421 let final_status = match result {
422 Ok(Ok(())) => NativeWorkerStatus::Stopped,
423 Ok(Err(message)) => {
424 let _ = fallback_ready.send(Err(message.clone()));
425 NativeWorkerStatus::Failed(message)
426 }
427 Err(panic) => {
428 let message = panic_message(panic.as_ref());
429 let _ = fallback_ready.send(Err(message.clone()));
430 NativeWorkerStatus::Failed(message)
431 }
432 };
433 worker_status_tx.send_replace(final_status);
434 })
435 .map_err(|error| NativeRuntimeError::WorkerStart(error.to_string()))?;
436 match ready_rx.recv() {
437 Ok(Ok(())) => {}
438 Ok(Err(error)) => {
439 let _ = worker.join();
440 return Err(NativeRuntimeError::WorkerStart(error));
441 }
442 Err(error) => {
443 let _ = worker.join();
444 return Err(NativeRuntimeError::WorkerStart(error.to_string()));
445 }
446 }
447 Ok(Self {
448 owner: Arc::new(WorkerOwner {
449 commands,
450 control,
451 next_request: AtomicU64::new(1),
452 worker: Mutex::new(Some(worker)),
453 status: worker_status,
454 }),
455 events,
456 status,
457 })
458 }
459
460 pub fn status(&self) -> SocketStatus {
462 *self.status.borrow()
463 }
464
465 pub fn events(&self) -> NativeSocketEvents {
467 NativeSocketEvents {
468 receiver: self.events.subscribe(),
469 }
470 }
471
472 pub fn status_changes(&self) -> NativeSocketStatusChanges {
474 NativeSocketStatusChanges {
475 receiver: self.status.clone(),
476 }
477 }
478
479 pub fn worker_status(&self) -> NativeWorkerStatus {
481 self.owner.status.borrow().clone()
482 }
483
484 pub fn worker_status_changes(&self) -> NativeWorkerStatusChanges {
486 NativeWorkerStatusChanges {
487 receiver: self.owner.status.clone(),
488 }
489 }
490
491 pub async fn connect(&self) -> Result<(), NativeRuntimeError> {
493 self.unit_request(|request_id, response| HostCommand::Connect {
494 request_id,
495 response,
496 })
497 .await
498 }
499
500 pub async fn ping(&self) -> Result<Duration, NativeRuntimeError> {
502 self.request(|request_id, response| HostCommand::Ping {
503 request_id,
504 timeout: None,
505 response,
506 })
507 .await
508 }
509
510 pub async fn ping_with_timeout(
512 &self,
513 timeout: Duration,
514 ) -> Result<Duration, NativeRuntimeError> {
515 self.request(|request_id, response| HostCommand::Ping {
516 request_id,
517 timeout: Some(timeout),
518 response,
519 })
520 .await
521 }
522
523 pub async fn disconnect(&self) -> Result<(), NativeRuntimeError> {
525 self.unit_request(|request_id, response| HostCommand::Disconnect {
526 request_id,
527 response,
528 })
529 .await
530 }
531
532 pub async fn disconnect_with(
534 &self,
535 code: u16,
536 reason: impl Into<String>,
537 ) -> Result<(), NativeRuntimeError> {
538 let reason = reason.into();
539 self.unit_request(|request_id, response| HostCommand::DisconnectWith {
540 request_id,
541 code,
542 reason,
543 response,
544 })
545 .await
546 }
547
548 pub async fn channel(
550 &self,
551 topic: impl Into<String>,
552 payload: Value,
553 ) -> Result<NativeChannel, NativeRuntimeError> {
554 self.channel_with_loader(topic, native_static_join_payload(payload))
555 .await
556 }
557
558 pub async fn channel_with_loader(
560 &self,
561 topic: impl Into<String>,
562 payload_loader: NativeJoinPayloadLoader,
563 ) -> Result<NativeChannel, NativeRuntimeError> {
564 let registration = self
565 .request(|request_id, response| HostCommand::Channel {
566 request_id,
567 topic: topic.into(),
568 payload_loader,
569 response,
570 })
571 .await?;
572 Ok(NativeChannel {
573 inner: Arc::new(NativeChannelInner {
574 id: registration.id,
575 topic: registration.topic,
576 owner: self.owner.clone(),
577 events: registration.events,
578 status: registration.status,
579 }),
580 })
581 }
582
583 pub async fn shutdown(&self) -> Result<(), NativeRuntimeError> {
585 let command_result = self
586 .unit_command(|response| HostCommand::Shutdown { response })
587 .await;
588 match self.owner.join() {
589 Err(error) => Err(error),
590 Ok(()) => command_result,
591 }
592 }
593
594 pub fn join_worker(&self) -> Result<(), NativeRuntimeError> {
598 self.owner.join()
599 }
600
601 async fn unit_request(
602 &self,
603 build: impl FnOnce(u64, oneshot::Sender<Result<(), ClientError>>) -> HostCommand,
604 ) -> Result<(), NativeRuntimeError> {
605 self.request(build).await
606 }
607
608 async fn unit_command(
609 &self,
610 build: impl FnOnce(oneshot::Sender<Result<(), ClientError>>) -> HostCommand,
611 ) -> Result<(), NativeRuntimeError> {
612 let (response, receiver) = oneshot::channel();
613 if self.owner.commands.send(build(response)).await.is_err() {
614 return Err(wait_for_worker_error(&self.owner).await);
615 }
616 let result = match receiver.await {
617 Ok(result) => result,
618 Err(_) => return Err(wait_for_worker_error(&self.owner).await),
619 };
620 result?;
621 Ok(())
622 }
623
624 async fn request<T: Send + 'static>(
625 &self,
626 build: impl FnOnce(u64, oneshot::Sender<Result<T, ClientError>>) -> HostCommand,
627 ) -> Result<T, NativeRuntimeError> {
628 request_worker(&self.owner, build).await
629 }
630}
631
632pub struct NativeSocketEvents {
634 receiver: broadcast::Receiver<SocketEvent>,
635}
636
637impl NativeSocketEvents {
638 pub async fn next(&mut self) -> Result<SocketEvent, NativeEventError> {
640 receive_event(&mut self.receiver).await
641 }
642}
643
644pub struct NativeSocketStatusChanges {
646 receiver: watch::Receiver<SocketStatus>,
647}
648
649pub struct NativeWorkerStatusChanges {
651 receiver: watch::Receiver<NativeWorkerStatus>,
652}
653
654impl NativeWorkerStatusChanges {
655 pub fn current(&self) -> NativeWorkerStatus {
657 self.receiver.borrow().clone()
658 }
659
660 pub async fn changed(&mut self) -> Result<NativeWorkerStatus, NativeEventError> {
662 self.receiver
663 .changed()
664 .await
665 .map_err(|_| NativeEventError::Closed)?;
666 Ok(self.receiver.borrow_and_update().clone())
667 }
668}
669
670impl NativeSocketStatusChanges {
671 pub fn current(&self) -> SocketStatus {
673 *self.receiver.borrow()
674 }
675
676 pub async fn changed(&mut self) -> Result<SocketStatus, NativeEventError> {
678 self.receiver
679 .changed()
680 .await
681 .map_err(|_| NativeEventError::Closed)?;
682 Ok(*self.receiver.borrow_and_update())
683 }
684}
685
686#[derive(Clone)]
688pub struct NativeChannel {
689 inner: Arc<NativeChannelInner>,
690}
691
692struct NativeChannelInner {
693 id: u64,
694 topic: String,
695 owner: Arc<WorkerOwner>,
696 events: broadcast::Sender<ChannelEvent>,
697 status: watch::Receiver<ChannelStatus>,
698}
699
700impl Drop for NativeChannelInner {
701 fn drop(&mut self) {
702 let _ = self
703 .owner
704 .control
705 .send(ControlCommand::RemoveChannel(self.id));
706 }
707}
708
709impl NativeChannel {
710 pub fn topic(&self) -> &str {
712 &self.inner.topic
713 }
714
715 pub fn status(&self) -> ChannelStatus {
717 *self.inner.status.borrow()
718 }
719
720 pub fn events(&self) -> NativeChannelEvents {
722 NativeChannelEvents {
723 receiver: self.inner.events.subscribe(),
724 }
725 }
726
727 pub fn status_changes(&self) -> NativeChannelStatusChanges {
729 NativeChannelStatusChanges {
730 receiver: self.inner.status.clone(),
731 }
732 }
733
734 pub fn presence(&self) -> NativeChannelPresence {
736 NativeChannelPresence {
737 channel: self.clone(),
738 events: self.events(),
739 tracker: PresenceTracker::new(),
740 pending: VecDeque::new(),
741 desynchronized: false,
742 }
743 }
744
745 pub fn subscribe(&self, event: impl Into<String>) -> NativeEventSubscription {
747 NativeEventSubscription {
748 event: event.into(),
749 events: self.events(),
750 }
751 }
752
753 pub async fn join(&self) -> Result<Payload, NativeRuntimeError> {
755 self.request(|request_id, response| HostCommand::Join {
756 request_id,
757 id: self.inner.id,
758 timeout: None,
759 response,
760 })
761 .await
762 }
763
764 pub async fn join_with_timeout(
766 &self,
767 timeout: Duration,
768 ) -> Result<Payload, NativeRuntimeError> {
769 self.request(|request_id, response| HostCommand::Join {
770 request_id,
771 id: self.inner.id,
772 timeout: Some(timeout),
773 response,
774 })
775 .await
776 }
777
778 pub async fn call(
780 &self,
781 event: impl Into<String>,
782 payload: impl Into<Payload>,
783 ) -> Result<Reply, NativeRuntimeError> {
784 self.request(|request_id, response| HostCommand::Call {
785 request_id,
786 id: self.inner.id,
787 event: event.into(),
788 payload: payload.into(),
789 timeout: None,
790 response,
791 })
792 .await
793 }
794
795 pub async fn call_with_timeout(
797 &self,
798 event: impl Into<String>,
799 payload: impl Into<Payload>,
800 timeout: Duration,
801 ) -> Result<Reply, NativeRuntimeError> {
802 self.request(|request_id, response| HostCommand::Call {
803 request_id,
804 id: self.inner.id,
805 event: event.into(),
806 payload: payload.into(),
807 timeout: Some(timeout),
808 response,
809 })
810 .await
811 }
812
813 pub async fn call_json<Request, Response>(
815 &self,
816 event: impl Into<String>,
817 request: &Request,
818 ) -> Result<Response, NativeCallJsonError>
819 where
820 Request: Serialize + ?Sized,
821 Response: DeserializeOwned,
822 {
823 let payload = serde_json::to_value(request).map_err(NativeCallJsonError::Encode)?;
824 self.call(event, payload)
825 .await?
826 .deserialize_ok()
827 .map_err(Into::into)
828 }
829
830 pub async fn cast(
832 &self,
833 event: impl Into<String>,
834 payload: impl Into<Payload>,
835 ) -> Result<(), NativeRuntimeError> {
836 self.request(|request_id, response| HostCommand::Cast {
837 request_id,
838 id: self.inner.id,
839 event: event.into(),
840 payload: payload.into(),
841 timeout: None,
842 response,
843 })
844 .await
845 }
846
847 pub async fn cast_with_timeout(
849 &self,
850 event: impl Into<String>,
851 payload: impl Into<Payload>,
852 timeout: Duration,
853 ) -> Result<(), NativeRuntimeError> {
854 self.request(|request_id, response| HostCommand::Cast {
855 request_id,
856 id: self.inner.id,
857 event: event.into(),
858 payload: payload.into(),
859 timeout: Some(timeout),
860 response,
861 })
862 .await
863 }
864
865 pub async fn leave(&self) -> Result<Payload, NativeRuntimeError> {
867 self.request(|request_id, response| HostCommand::Leave {
868 request_id,
869 id: self.inner.id,
870 timeout: None,
871 response,
872 })
873 .await
874 }
875
876 pub async fn leave_with_timeout(
878 &self,
879 timeout: Duration,
880 ) -> Result<Payload, NativeRuntimeError> {
881 self.request(|request_id, response| HostCommand::Leave {
882 request_id,
883 id: self.inner.id,
884 timeout: Some(timeout),
885 response,
886 })
887 .await
888 }
889
890 async fn request<T: Send + 'static>(
891 &self,
892 build: impl FnOnce(u64, oneshot::Sender<Result<T, ClientError>>) -> HostCommand,
893 ) -> Result<T, NativeRuntimeError> {
894 request_worker(&self.inner.owner, build).await
895 }
896}
897
898async fn request_worker<T: Send + 'static>(
899 owner: &Arc<WorkerOwner>,
900 build: impl FnOnce(u64, oneshot::Sender<Result<T, ClientError>>) -> HostCommand,
901) -> Result<T, NativeRuntimeError> {
902 let permit = match owner.commands.reserve().await {
903 Ok(permit) => permit,
904 Err(_) => return Err(wait_for_worker_error(owner).await),
905 };
906 let request_id = owner.next_request_id();
907 let (response, receiver) = oneshot::channel();
908 if owner
909 .control
910 .send(ControlCommand::Register(request_id))
911 .is_err()
912 {
913 return Err(wait_for_worker_error(owner).await);
914 }
915 let mut guard = HostRequestGuard {
916 request_id,
917 control: owner.control.clone(),
918 armed: true,
919 };
920 permit.send(build(request_id, response));
921 let result = match receiver.await {
922 Ok(result) => result?,
923 Err(_) => return Err(wait_for_worker_error(owner).await),
924 };
925 guard.armed = false;
926 Ok(result)
927}
928
929fn worker_error(owner: &WorkerOwner) -> NativeRuntimeError {
930 match owner.status.borrow().clone() {
931 NativeWorkerStatus::Failed(message) => NativeRuntimeError::WorkerFailed(message),
932 NativeWorkerStatus::Running | NativeWorkerStatus::Stopped => {
933 NativeRuntimeError::WorkerStopped
934 }
935 }
936}
937
938async fn wait_for_worker_error(owner: &WorkerOwner) -> NativeRuntimeError {
939 let mut status = owner.status.clone();
940 if matches!(*status.borrow(), NativeWorkerStatus::Running) {
941 let _ = status.changed().await;
942 }
943 worker_error(owner)
944}
945
946struct HostRequestGuard {
947 request_id: u64,
948 control: mpsc::UnboundedSender<ControlCommand>,
949 armed: bool,
950}
951
952impl Drop for HostRequestGuard {
953 fn drop(&mut self) {
954 if self.armed {
955 let _ = self.control.send(ControlCommand::Cancel(self.request_id));
956 }
957 }
958}
959
960pub struct NativeChannelEvents {
962 receiver: broadcast::Receiver<ChannelEvent>,
963}
964
965pub struct NativeChannelPresence {
967 channel: NativeChannel,
968 events: NativeChannelEvents,
969 tracker: PresenceTracker,
970 pending: VecDeque<PresenceEvent>,
971 desynchronized: bool,
972}
973
974pub struct NativeEventSubscription {
976 event: String,
977 events: NativeChannelEvents,
978}
979
980impl NativeEventSubscription {
981 pub fn event(&self) -> &str {
983 &self.event
984 }
985
986 pub async fn next(&mut self) -> Result<SubscriptionEvent, NativeEventError> {
988 loop {
989 match self.events.next().await? {
990 ChannelEvent::Protocol(ProtocolEvent::Message(frame))
991 if frame.event == self.event =>
992 {
993 return Ok(SubscriptionEvent::Message(frame.payload));
994 }
995 ChannelEvent::Protocol(ProtocolEvent::ChannelError { payload, .. }) => {
996 return Ok(SubscriptionEvent::ChannelError(payload));
997 }
998 ChannelEvent::Protocol(ProtocolEvent::ChannelClosed { payload, .. }) => {
999 return Ok(SubscriptionEvent::ChannelClosed(payload));
1000 }
1001 ChannelEvent::Disconnected => return Ok(SubscriptionEvent::Disconnected),
1002 ChannelEvent::Lagged { dropped } => {
1003 return Ok(SubscriptionEvent::Lagged { dropped });
1004 }
1005 ChannelEvent::Protocol(_) | ChannelEvent::JoinPayloadError(_) => {}
1006 }
1007 }
1008 }
1009}
1010
1011#[derive(Debug, Error)]
1013pub enum NativePresenceError {
1014 #[error(transparent)]
1016 Events(#[from] NativeEventError),
1017 #[error(transparent)]
1019 Decode(#[from] PresenceError),
1020 #[error("presence event stream dropped {dropped} events and must be resynchronized")]
1022 Desynchronized {
1023 dropped: u64,
1025 },
1026 #[error("presence state must be resynchronized before consuming more events")]
1028 ResyncRequired,
1029}
1030
1031impl NativeChannelPresence {
1032 pub fn state(&self) -> &PresenceState {
1034 self.tracker.state()
1035 }
1036
1037 pub fn requires_resync(&self) -> bool {
1039 self.desynchronized
1040 }
1041
1042 pub async fn resync(&mut self) -> Result<(), NativeRuntimeError> {
1044 self.tracker.reset();
1045 self.pending.clear();
1046 self.channel.leave().await?;
1047 self.events = self.channel.events();
1048 self.channel.join().await?;
1049 self.desynchronized = false;
1050 Ok(())
1051 }
1052
1053 pub async fn next(&mut self) -> Result<PresenceEvent, NativePresenceError> {
1055 if self.desynchronized {
1056 return Err(NativePresenceError::ResyncRequired);
1057 }
1058 loop {
1059 if let Some(event) = self.pending.pop_front() {
1060 return Ok(event);
1061 }
1062 match self.events.next().await {
1063 Ok(ChannelEvent::Protocol(ProtocolEvent::Message(frame))) => {
1064 let previous = self.tracker.state().clone();
1065 match self.tracker.apply(&frame)? {
1066 PresenceUpdate::Synced(diff) => {
1067 for (key, joined) in diff.joins.0 {
1068 self.pending.push_back(PresenceEvent::Joined {
1069 current: previous.get(&key).cloned(),
1070 key,
1071 joined,
1072 });
1073 }
1074 for (key, left) in diff.leaves.0 {
1075 if let Some(current) = previous.get(&key).cloned() {
1076 self.pending.push_back(PresenceEvent::Left {
1077 key,
1078 current,
1079 left,
1080 });
1081 }
1082 }
1083 self.pending.push_back(PresenceEvent::Synced);
1084 }
1085 PresenceUpdate::Ignored | PresenceUpdate::Pending => {}
1086 }
1087 }
1088 Ok(ChannelEvent::Disconnected) => {
1089 self.tracker.reset();
1090 return Ok(PresenceEvent::Disconnected);
1091 }
1092 Ok(ChannelEvent::Lagged { dropped }) | Err(NativeEventError::Lagged(dropped)) => {
1093 self.tracker.reset();
1094 self.pending.clear();
1095 self.desynchronized = true;
1096 return Err(NativePresenceError::Desynchronized { dropped });
1097 }
1098 Ok(ChannelEvent::Protocol(ProtocolEvent::Left { .. })) => {
1099 self.tracker.reset();
1100 return Ok(PresenceEvent::ChannelLeft);
1101 }
1102 Ok(ChannelEvent::Protocol(ProtocolEvent::ChannelClosed { .. })) => {
1103 self.tracker.reset();
1104 return Ok(PresenceEvent::ChannelClosed);
1105 }
1106 Ok(ChannelEvent::Protocol(ProtocolEvent::ChannelError { .. })) => {
1107 self.tracker.reset();
1108 return Ok(PresenceEvent::ChannelError);
1109 }
1110 Ok(ChannelEvent::Protocol(_) | ChannelEvent::JoinPayloadError(_)) => {}
1111 Err(error) => return Err(error.into()),
1112 }
1113 }
1114 }
1115}
1116
1117impl NativeChannelEvents {
1118 pub async fn next(&mut self) -> Result<ChannelEvent, NativeEventError> {
1120 receive_event(&mut self.receiver).await
1121 }
1122}
1123
1124pub struct NativeChannelStatusChanges {
1126 receiver: watch::Receiver<ChannelStatus>,
1127}
1128
1129impl NativeChannelStatusChanges {
1130 pub fn current(&self) -> ChannelStatus {
1132 *self.receiver.borrow()
1133 }
1134
1135 pub async fn changed(&mut self) -> Result<ChannelStatus, NativeEventError> {
1137 self.receiver
1138 .changed()
1139 .await
1140 .map_err(|_| NativeEventError::Closed)?;
1141 Ok(*self.receiver.borrow_and_update())
1142 }
1143}
1144
1145async fn receive_event<T: Clone>(
1146 receiver: &mut broadcast::Receiver<T>,
1147) -> Result<T, NativeEventError> {
1148 receiver.recv().await.map_err(|error| match error {
1149 broadcast::error::RecvError::Closed => NativeEventError::Closed,
1150 broadcast::error::RecvError::Lagged(count) => NativeEventError::Lagged(count),
1151 })
1152}
1153
1154struct ChannelRegistration {
1155 id: u64,
1156 topic: String,
1157 events: broadcast::Sender<ChannelEvent>,
1158 status: watch::Receiver<ChannelStatus>,
1159}
1160
1161enum HostCommand {
1162 Connect {
1163 request_id: u64,
1164 response: oneshot::Sender<Result<(), ClientError>>,
1165 },
1166 Disconnect {
1167 request_id: u64,
1168 response: oneshot::Sender<Result<(), ClientError>>,
1169 },
1170 DisconnectWith {
1171 request_id: u64,
1172 code: u16,
1173 reason: String,
1174 response: oneshot::Sender<Result<(), ClientError>>,
1175 },
1176 Ping {
1177 request_id: u64,
1178 timeout: Option<Duration>,
1179 response: oneshot::Sender<Result<Duration, ClientError>>,
1180 },
1181 Shutdown {
1182 response: oneshot::Sender<Result<(), ClientError>>,
1183 },
1184 Channel {
1185 request_id: u64,
1186 topic: String,
1187 payload_loader: NativeJoinPayloadLoader,
1188 response: oneshot::Sender<Result<ChannelRegistration, ClientError>>,
1189 },
1190 Join {
1191 request_id: u64,
1192 id: u64,
1193 timeout: Option<Duration>,
1194 response: oneshot::Sender<Result<Payload, ClientError>>,
1195 },
1196 Call {
1197 request_id: u64,
1198 id: u64,
1199 event: String,
1200 payload: Payload,
1201 timeout: Option<Duration>,
1202 response: oneshot::Sender<Result<Reply, ClientError>>,
1203 },
1204 Cast {
1205 request_id: u64,
1206 id: u64,
1207 event: String,
1208 payload: Payload,
1209 timeout: Option<Duration>,
1210 response: oneshot::Sender<Result<(), ClientError>>,
1211 },
1212 Leave {
1213 request_id: u64,
1214 id: u64,
1215 timeout: Option<Duration>,
1216 response: oneshot::Sender<Result<Payload, ClientError>>,
1217 },
1218}
1219
1220enum ControlCommand {
1221 Register(u64),
1222 Cancel(u64),
1223 Finished(u64),
1224 RemoveChannel(u64),
1225 Shutdown,
1226}
1227
1228struct WorkerBootstrap {
1229 endpoint_url: String,
1230 config_loader: NativeConnectionConfigLoader,
1231 options: NativeOptions,
1232 commands: mpsc::Receiver<HostCommand>,
1233 control: mpsc::UnboundedReceiver<ControlCommand>,
1234 control_tx: mpsc::UnboundedSender<ControlCommand>,
1235 events: broadcast::Sender<SocketEvent>,
1236 status: watch::Sender<SocketStatus>,
1237 ready: std::sync::mpsc::SyncSender<Result<(), String>>,
1238}
1239
1240fn run_worker(bootstrap: WorkerBootstrap) -> Result<(), String> {
1241 let runtime = tokio::runtime::Builder::new_current_thread()
1242 .enable_all()
1243 .build()
1244 .map_err(|error| error.to_string())?;
1245 tokio::task::LocalSet::new().block_on(&runtime, worker_main(bootstrap))
1246}
1247
1248async fn worker_main(bootstrap: WorkerBootstrap) -> Result<(), String> {
1249 let WorkerBootstrap {
1250 endpoint_url,
1251 config_loader,
1252 options,
1253 mut commands,
1254 mut control,
1255 control_tx,
1256 events,
1257 status,
1258 ready,
1259 } = bootstrap;
1260 let local_loader = Rc::new(move |context| {
1261 let config_loader = config_loader.clone();
1262 async move { config_loader(context).await }.boxed_local()
1263 });
1264 let endpoint = Endpoint::new(endpoint_url)
1265 .map_err(|error| error.to_string())?
1266 .connection_config_loader(local_loader);
1267 let event_capacity = options.event_capacity;
1268 let transport_options = options.transport.clone();
1269 let (socket, driver) = Socket::new(
1270 NativeConnector::from_endpoint(endpoint).options(transport_options),
1271 NativeTimer,
1272 options.client_options(),
1273 );
1274 let mut socket_events = socket.events().map_err(|error| error.to_string())?;
1275 let mut socket_statuses = socket.status_changes();
1276 let mut driver_task = tokio::task::spawn_local(driver);
1277 let next_id = AtomicU64::new(1);
1278 let mut channels: HashMap<u64, Rc<Channel>> = HashMap::new();
1279 let mut operations: HashMap<u64, tokio::task::JoinHandle<()>> = HashMap::new();
1280 let mut known_requests = HashSet::new();
1281 let mut cancelled = HashSet::new();
1282 let _ = ready.send(Ok(()));
1283
1284 loop {
1285 tokio::select! {
1286 driver_result = &mut driver_task => match driver_result {
1287 Ok(()) => break,
1288 Err(error) => return Err(format!("client driver task failed: {error}")),
1289 },
1290 control_command = control.recv() => match control_command {
1291 Some(ControlCommand::Register(request_id)) => {
1292 known_requests.insert(request_id);
1293 }
1294 Some(ControlCommand::Cancel(request_id)) => {
1295 if let Some(operation) = operations.remove(&request_id) {
1296 operation.abort();
1297 } else if known_requests.contains(&request_id) {
1298 cancelled.insert(request_id);
1299 }
1300 }
1301 Some(ControlCommand::Finished(request_id)) => {
1302 operations.remove(&request_id);
1303 known_requests.remove(&request_id);
1304 cancelled.remove(&request_id);
1305 }
1306 Some(ControlCommand::RemoveChannel(id)) => {
1307 channels.remove(&id);
1308 }
1309 Some(ControlCommand::Shutdown) | None => {
1310 let _ = socket.shutdown().await;
1311 break;
1312 }
1313 },
1314 event = socket_events.next() => match event {
1315 Some(event) => {
1316 let _ = events.send(event);
1317 }
1318 None => break,
1319 },
1320 changed = socket_statuses.changed() => match changed {
1321 Some(changed) => {
1322 status.send_replace(changed);
1323 }
1324 None => break,
1325 },
1326 command = commands.recv() => {
1327 let Some(command) = command else {
1328 let _ = socket.shutdown().await;
1329 break;
1330 };
1331 let mut host = HostState {
1332 channels: &mut channels,
1333 next_id: &next_id,
1334 event_capacity,
1335 control: &control_tx,
1336 operations: &mut operations,
1337 known_requests: &mut known_requests,
1338 cancelled: &mut cancelled,
1339 };
1340 if handle_host_command(command, &socket, &mut host).await {
1341 break;
1342 }
1343 }
1344 }
1345 }
1346 status.send_replace(SocketStatus::Closed);
1347 for (_, operation) in operations {
1348 operation.abort();
1349 }
1350 if driver_task.is_finished() {
1351 driver_task
1352 .await
1353 .map_err(|error| format!("client driver task failed: {error}"))?;
1354 } else {
1355 driver_task.abort();
1356 }
1357 Ok(())
1358}
1359
1360struct HostState<'a> {
1361 channels: &'a mut HashMap<u64, Rc<Channel>>,
1362 next_id: &'a AtomicU64,
1363 event_capacity: usize,
1364 control: &'a mpsc::UnboundedSender<ControlCommand>,
1365 operations: &'a mut HashMap<u64, tokio::task::JoinHandle<()>>,
1366 known_requests: &'a mut HashSet<u64>,
1367 cancelled: &'a mut HashSet<u64>,
1368}
1369
1370async fn handle_host_command(
1371 command: HostCommand,
1372 socket: &Socket,
1373 state: &mut HostState<'_>,
1374) -> bool {
1375 let HostState {
1376 channels,
1377 next_id,
1378 event_capacity,
1379 control,
1380 operations,
1381 known_requests,
1382 cancelled,
1383 } = state;
1384 match command {
1385 HostCommand::Connect {
1386 request_id,
1387 response,
1388 } => {
1389 known_requests.remove(&request_id);
1390 if cancelled.remove(&request_id) {
1391 return false;
1392 }
1393 let socket = socket.clone();
1394 track_operation(request_id, control, operations, async move {
1395 let _ = response.send(socket.connect().await);
1396 });
1397 }
1398 HostCommand::Disconnect {
1399 request_id,
1400 response,
1401 } => {
1402 known_requests.remove(&request_id);
1403 if cancelled.remove(&request_id) {
1404 return false;
1405 }
1406 let socket = socket.clone();
1407 track_operation(request_id, control, operations, async move {
1408 let _ = response.send(socket.disconnect().await);
1409 });
1410 }
1411 HostCommand::DisconnectWith {
1412 request_id,
1413 code,
1414 reason,
1415 response,
1416 } => {
1417 known_requests.remove(&request_id);
1418 if cancelled.remove(&request_id) {
1419 return false;
1420 }
1421 let socket = socket.clone();
1422 track_operation(request_id, control, operations, async move {
1423 let _ = response.send(socket.disconnect_with(code, reason).await);
1424 });
1425 }
1426 HostCommand::Ping {
1427 request_id,
1428 timeout,
1429 response,
1430 } => {
1431 known_requests.remove(&request_id);
1432 if cancelled.remove(&request_id) {
1433 return false;
1434 }
1435 let socket = socket.clone();
1436 track_operation(request_id, control, operations, async move {
1437 let result = match timeout {
1438 Some(timeout) => socket.ping_with_timeout(timeout).await,
1439 None => socket.ping().await,
1440 };
1441 let _ = response.send(result);
1442 });
1443 }
1444 HostCommand::Shutdown { response } => {
1445 let result = socket.shutdown().await;
1446 let _ = response.send(result);
1447 return true;
1448 }
1449 HostCommand::Channel {
1450 request_id,
1451 topic,
1452 payload_loader,
1453 response,
1454 } => {
1455 known_requests.remove(&request_id);
1456 if cancelled.remove(&request_id) {
1457 return false;
1458 }
1459 let registration_topic = topic.clone();
1460 let local_loader = Rc::new(move |context| {
1461 let payload_loader = payload_loader.clone();
1462 async move { payload_loader(context).await }.boxed_local()
1463 });
1464 match socket.channel(topic, local_loader) {
1465 Ok(channel) => {
1466 let id = next_id.fetch_add(1, Ordering::Relaxed);
1467 let channel = Rc::new(channel);
1468 let (event_tx, _) = broadcast::channel(*event_capacity);
1469 let (status_tx, status_rx) = watch::channel(channel.status());
1470 let mut status_changes = channel.status_changes();
1471 let mut event_rx = match channel.events() {
1472 Ok(events) => events,
1473 Err(error) => {
1474 let _ = response.send(Err(error));
1475 return false;
1476 }
1477 };
1478 let pump_tx = event_tx.clone();
1479 tokio::task::spawn_local(async move {
1480 while let Some(event) = event_rx.next().await {
1481 let _ = pump_tx.send(event);
1482 }
1483 });
1484 tokio::task::spawn_local(async move {
1485 while let Some(changed) = status_changes.changed().await {
1486 status_tx.send_replace(changed);
1487 }
1488 });
1489 channels.insert(id, channel);
1490 let _ = response.send(Ok(ChannelRegistration {
1491 id,
1492 topic: registration_topic,
1493 events: event_tx,
1494 status: status_rx,
1495 }));
1496 }
1497 Err(error) => {
1498 let _ = response.send(Err(error));
1499 }
1500 }
1501 }
1502 HostCommand::Join {
1503 request_id,
1504 id,
1505 timeout,
1506 response,
1507 } => {
1508 known_requests.remove(&request_id);
1509 if cancelled.remove(&request_id) {
1510 return false;
1511 }
1512 let channel = channels.get(&id).cloned();
1513 track_operation(request_id, control, operations, async move {
1514 send_channel_response(channel, response, move |channel| async move {
1515 match timeout {
1516 Some(timeout) => channel.join_with_timeout(timeout).await,
1517 None => channel.join().await,
1518 }
1519 })
1520 .await;
1521 });
1522 }
1523 HostCommand::Call {
1524 request_id,
1525 id,
1526 event,
1527 payload,
1528 timeout,
1529 response,
1530 } => {
1531 known_requests.remove(&request_id);
1532 if cancelled.remove(&request_id) {
1533 return false;
1534 }
1535 let channel = channels.get(&id).cloned();
1536 track_operation(request_id, control, operations, async move {
1537 send_channel_response(channel, response, move |channel| async move {
1538 match timeout {
1539 Some(timeout) => channel.call_with_timeout(event, payload, timeout).await,
1540 None => channel.call(event, payload).await,
1541 }
1542 })
1543 .await;
1544 });
1545 }
1546 HostCommand::Cast {
1547 request_id,
1548 id,
1549 event,
1550 payload,
1551 timeout,
1552 response,
1553 } => {
1554 known_requests.remove(&request_id);
1555 if cancelled.remove(&request_id) {
1556 return false;
1557 }
1558 let channel = channels.get(&id).cloned();
1559 track_operation(request_id, control, operations, async move {
1560 send_channel_response(channel, response, move |channel| async move {
1561 match timeout {
1562 Some(timeout) => channel.cast_with_timeout(event, payload, timeout).await,
1563 None => channel.cast(event, payload).await,
1564 }
1565 })
1566 .await;
1567 });
1568 }
1569 HostCommand::Leave {
1570 request_id,
1571 id,
1572 timeout,
1573 response,
1574 } => {
1575 known_requests.remove(&request_id);
1576 if cancelled.remove(&request_id) {
1577 return false;
1578 }
1579 let channel = channels.get(&id).cloned();
1580 track_operation(request_id, control, operations, async move {
1581 send_channel_response(channel, response, move |channel| async move {
1582 match timeout {
1583 Some(timeout) => channel.leave_with_timeout(timeout).await,
1584 None => channel.leave().await,
1585 }
1586 })
1587 .await;
1588 });
1589 }
1590 }
1591 false
1592}
1593
1594async fn send_channel_response<T, F, Fut>(
1595 channel: Option<Rc<Channel>>,
1596 response: oneshot::Sender<Result<T, ClientError>>,
1597 call: F,
1598) where
1599 T: 'static,
1600 F: FnOnce(Rc<Channel>) -> Fut + 'static,
1601 Fut: Future<Output = Result<T, ClientError>> + 'static,
1602{
1603 let Some(channel) = channel else {
1604 let _ = response.send(Err(ClientError::DriverStopped));
1605 return;
1606 };
1607 let result = call(channel).await;
1608 let _ = response.send(result);
1609}
1610
1611fn track_operation(
1612 request_id: u64,
1613 control: &mpsc::UnboundedSender<ControlCommand>,
1614 operations: &mut HashMap<u64, tokio::task::JoinHandle<()>>,
1615 operation: impl Future<Output = ()> + 'static,
1616) {
1617 let control = control.clone();
1618 let handle = tokio::task::spawn_local(async move {
1619 operation.await;
1620 let _ = control.send(ControlCommand::Finished(request_id));
1621 });
1622 operations.insert(request_id, handle);
1623}
1624
1625fn panic_message(panic: &(dyn Any + Send)) -> String {
1626 panic
1627 .downcast_ref::<&str>()
1628 .map(|message| (*message).to_owned())
1629 .or_else(|| panic.downcast_ref::<String>().cloned())
1630 .unwrap_or_else(|| "worker panicked without a string payload".to_owned())
1631}
1632
1633#[cfg(test)]
1634mod tests {
1635 use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
1636
1637 use futures::StreamExt;
1638
1639 use super::*;
1640
1641 fn assert_send_sync<T: Send + Sync>() {}
1642
1643 #[test]
1644 fn native_handles_are_send_and_sync() {
1645 assert_send_sync::<NativeSocket>();
1646 assert_send_sync::<NativeChannel>();
1647 assert_send_sync::<NativeChannelPresence>();
1648 assert_send_sync::<NativeEventSubscription>();
1649 assert_send_sync::<NativeOptions>();
1650 }
1651
1652 #[test]
1653 fn retry_schedules_hold_the_last_delay() {
1654 let delays = [Duration::from_secs(1), Duration::from_secs(3)];
1655 assert_eq!(retry_delay(&delays, 0), Duration::from_secs(1));
1656 assert_eq!(retry_delay(&delays, 8), Duration::from_secs(3));
1657 }
1658
1659 #[tokio::test(flavor = "current_thread")]
1660 async fn event_subscriptions_report_broadcast_lag() {
1661 let (events, receiver) = broadcast::channel(1);
1662 let mut subscription = NativeEventSubscription {
1663 event: "notice".into(),
1664 events: NativeChannelEvents { receiver },
1665 };
1666 events.send(ChannelEvent::Disconnected).unwrap();
1667 events.send(ChannelEvent::Disconnected).unwrap();
1668 assert!(matches!(
1669 subscription.next().await,
1670 Err(NativeEventError::Lagged(1))
1671 ));
1672 }
1673
1674 #[tokio::test(flavor = "current_thread")]
1675 async fn reconnect_policy_runs_on_the_native_worker() {
1676 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1677 let port = listener.local_addr().unwrap().port();
1678 let server = tokio::spawn(async move {
1679 let (stream, _) = listener.accept().await.unwrap();
1680 drop(stream);
1681 });
1682 let attempts = Arc::new(AtomicUsize::new(0));
1683 let options = NativeOptions::default().reconnect_policy({
1684 let attempts = attempts.clone();
1685 Arc::new(move |_| {
1686 attempts.fetch_add(1, AtomicOrdering::SeqCst);
1687 ReconnectAction::Stop
1688 })
1689 });
1690 let socket = NativeSocket::spawn_with_options(
1691 format!("ws://127.0.0.1:{port}/socket"),
1692 ConnectionConfig::default(),
1693 options,
1694 )
1695 .unwrap();
1696 socket.connect().await.unwrap();
1697
1698 tokio::time::timeout(Duration::from_secs(5), async {
1699 while attempts.load(AtomicOrdering::SeqCst) == 0 {
1700 tokio::task::yield_now().await;
1701 }
1702 })
1703 .await
1704 .expect("native reconnect policy was not invoked");
1705 server.await.unwrap();
1706 socket.shutdown().await.unwrap();
1707 }
1708
1709 #[tokio::test(flavor = "current_thread")]
1710 async fn native_ping_timeout_cancels_the_host_request() {
1711 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1712 let port = listener.local_addr().unwrap().port();
1713 let server = tokio::spawn(async move {
1714 let (stream, _) = listener.accept().await.unwrap();
1715 let mut websocket = tokio_tungstenite::accept_async(stream).await.unwrap();
1716 while websocket.next().await.is_some() {}
1717 });
1718 let socket = NativeSocket::spawn(
1719 format!("ws://127.0.0.1:{port}/socket"),
1720 ConnectionConfig::default(),
1721 )
1722 .unwrap();
1723 socket.connect().await.unwrap();
1724 let mut statuses = socket.status_changes();
1725 tokio::time::timeout(Duration::from_secs(2), async {
1726 while socket.status() != SocketStatus::Connected {
1727 statuses.changed().await.unwrap();
1728 }
1729 })
1730 .await
1731 .expect("native test socket did not connect");
1732
1733 assert!(matches!(
1734 socket.ping_with_timeout(Duration::from_millis(20)).await,
1735 Err(NativeRuntimeError::Client(ClientError::Timeout {
1736 operation: phoenix_channel_client::ClientOperation::Ping,
1737 ..
1738 }))
1739 ));
1740 socket.shutdown().await.unwrap();
1741 server.abort();
1742 let _ = server.await;
1743 }
1744
1745 #[tokio::test(flavor = "current_thread")]
1746 async fn cancelled_host_requests_do_not_prevent_shutdown() {
1747 let socket =
1748 NativeSocket::spawn("ws://127.0.0.1:9/socket", ConnectionConfig::default()).unwrap();
1749 let channel = socket
1750 .channel("room:lobby", serde_json::json!({}))
1751 .await
1752 .unwrap();
1753 assert_eq!(channel.topic(), "room:lobby");
1754
1755 let pending = tokio::spawn({
1756 let channel = channel.clone();
1757 async move { channel.call("queued", serde_json::json!({})).await }
1758 });
1759 tokio::task::yield_now().await;
1760 pending.abort();
1761 let _ = pending.await;
1762
1763 socket.shutdown().await.unwrap();
1764 assert_eq!(socket.worker_status(), NativeWorkerStatus::Stopped);
1765 }
1766
1767 #[tokio::test(flavor = "current_thread")]
1768 async fn reports_driver_panics_as_worker_failures() {
1769 let options = NativeOptions::default().telemetry(Arc::new(|_| {
1770 panic!("telemetry failure");
1771 }));
1772 let socket = NativeSocket::spawn_with_options(
1773 "ws://127.0.0.1:9/socket",
1774 ConnectionConfig::default(),
1775 options,
1776 )
1777 .unwrap();
1778 let _ = socket.connect().await;
1779 assert!(matches!(
1780 socket.shutdown().await,
1781 Err(NativeRuntimeError::WorkerFailed(_))
1782 ));
1783 assert!(matches!(
1784 socket.join_worker(),
1785 Err(NativeRuntimeError::WorkerFailed(message)) if message.contains("client driver task failed")
1786 ));
1787 assert!(matches!(
1788 socket.worker_status(),
1789 NativeWorkerStatus::Failed(_)
1790 ));
1791 }
1792}