pb_mapper_client/sdk/
handle.rs1use crate::diagnostics::{Diagnostics, TunnelDiagnostics, TunnelEvents};
8use crate::endpoint::RelayEndpoint;
9use std::time::Duration;
10
11use tokio::sync::{Mutex, watch};
12use tokio::task::JoinHandle;
13use tokio_util::sync::CancellationToken;
14
15use super::{Error, Result, TunnelStatus};
16
17pub(crate) struct LiveTunnel {
18 shutdown: CancellationToken,
19 join: Mutex<Option<JoinHandle<()>>>,
20 status: watch::Receiver<TunnelStatus>,
21 endpoint: RelayEndpoint,
22 diagnostics: Diagnostics,
23}
24
25impl LiveTunnel {
26 pub(crate) fn new(
27 shutdown: CancellationToken,
28 join: JoinHandle<()>,
29 status: watch::Receiver<TunnelStatus>,
30 endpoint: RelayEndpoint,
31 diagnostics: Diagnostics,
32 ) -> Self {
33 Self {
34 shutdown,
35 join: Mutex::new(Some(join)),
36 status,
37 endpoint,
38 diagnostics,
39 }
40 }
41
42 pub(crate) fn status(&self) -> TunnelStatus {
43 self.status.borrow().clone()
44 }
45
46 pub(crate) fn subscribe(&self) -> watch::Receiver<TunnelStatus> {
47 self.status.clone()
48 }
49
50 pub(crate) async fn wait_ready(&self) -> Result<()> {
51 wait_for_connected(&mut self.status.clone()).await
52 }
53
54 pub(crate) async fn wait_ready_timeout(&self, timeout: Duration) -> Result<()> {
55 match tokio::time::timeout(timeout, self.wait_ready()).await {
56 Ok(result) => result,
57 Err(_) => Err(Error::ReadyTimeout { timeout }),
58 }
59 }
60
61 pub(crate) async fn stop(&self) -> Result<()> {
62 self.shutdown.cancel();
63 let mut slot = self.join.lock().await;
66 if let Some(handle) = slot.as_mut() {
67 match tokio::time::timeout(Duration::from_secs(5), &mut *handle).await {
68 Ok(Ok(())) => {}
69 Ok(Err(join_error)) if join_error.is_cancelled() => {}
70 Ok(Err(join_error)) => {
71 slot.take();
72 self.diagnostics.stop();
73 return Err(Error::protocol(format!("tunnel task failed: {join_error}")));
74 }
75 Err(_) => {
76 handle.abort();
77 let _ = handle.await;
78 }
79 }
80 slot.take();
81 }
82 self.diagnostics.stop();
83 Ok(())
84 }
85}
86
87impl Drop for LiveTunnel {
88 fn drop(&mut self) {
89 self.shutdown.cancel();
90 if let Some(handle) = self.join.get_mut().take() {
91 handle.abort();
92 }
93 self.diagnostics.stop();
94 }
95}
96
97async fn wait_for_connected(status: &mut watch::Receiver<TunnelStatus>) -> Result<()> {
98 loop {
99 match status.borrow().clone() {
100 TunnelStatus::Connected => return Ok(()),
101 TunnelStatus::Failed(reason) => return Err(Error::TunnelFailed { reason }),
102 TunnelStatus::Stopped => return Err(Error::Stopped),
103 TunnelStatus::Starting | TunnelStatus::Retrying => {}
104 }
105 status.changed().await.map_err(|_| Error::Stopped)?;
106 }
107}
108
109macro_rules! tunnel_handle {
116 ($(#[$doc:meta])* $name:ident) => {
117 $(#[$doc])*
118 pub struct $name {
119 inner: LiveTunnel,
120 key: String,
121 }
122
123 impl $name {
124 pub(crate) fn new(inner: LiveTunnel, key: String) -> Self {
125 Self { inner, key }
126 }
127
128 pub fn key(&self) -> &str {
130 &self.key
131 }
132
133 pub fn status(&self) -> TunnelStatus {
135 self.inner.status()
136 }
137
138 pub fn diagnostics(&self) -> TunnelDiagnostics {
140 self.inner.diagnostics.snapshot(&self.inner.endpoint)
141 }
142
143 pub fn subscribe_events(&self) -> TunnelEvents {
146 self.inner.diagnostics.subscribe()
147 }
148
149 pub fn subscribe(&self) -> watch::Receiver<TunnelStatus> {
151 self.inner.subscribe()
152 }
153
154 pub async fn wait_ready(&self) -> Result<()> {
156 self.inner.wait_ready().await
157 }
158
159 pub async fn wait_ready_timeout(&self, timeout: Duration) -> Result<()> {
161 self.inner.wait_ready_timeout(timeout).await
162 }
163
164 pub async fn stop(&self) -> Result<()> {
166 self.inner.stop().await
167 }
168 }
169 };
170}
171
172tunnel_handle!(
173 Registration
175);
176
177tunnel_handle!(
178 Connection
180);
181
182#[cfg(test)]
183#[allow(clippy::unwrap_used)]
184mod tests {
185 use super::*;
186 use std::{future::Future, task::Poll};
187
188 async fn poll_pending(future: &mut std::pin::Pin<Box<impl Future>>) {
189 std::future::poll_fn(|cx| {
190 assert!(future.as_mut().poll(cx).is_pending());
191 Poll::Ready(())
192 })
193 .await;
194 }
195
196 #[tokio::test]
197 async fn cancelled_stop_keeps_worker_owned_until_handle_drop() {
198 let (tx, mut rx) = watch::channel(TunnelStatus::Starting);
199 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
200 let worker = tokio::spawn(async move {
201 let _tx = tx;
202 let _ = started_tx.send(());
203 std::future::pending::<()>().await;
204 });
205 let tunnel = LiveTunnel::new(
206 CancellationToken::new(),
207 worker,
208 rx.clone(),
209 RelayEndpoint::shared("127.0.0.1:1"),
210 Diagnostics::default(),
211 );
212 started_rx.await.unwrap();
213 let mut stopping = Box::pin(tunnel.stop());
214 poll_pending(&mut stopping).await;
215 drop(stopping);
216 drop(tunnel);
217 assert!(
218 tokio::time::timeout(Duration::from_secs(1), rx.changed())
219 .await
220 .unwrap()
221 .is_err()
222 );
223 }
224
225 #[tokio::test]
226 async fn concurrent_stop_calls_both_wait_for_cleanup() {
227 let (tx, rx) = watch::channel(TunnelStatus::Starting);
228 let (release, released) = tokio::sync::oneshot::channel();
229 let worker = tokio::spawn(async move {
230 let _tx = tx;
231 let _ = released.await;
232 });
233 let tunnel = LiveTunnel::new(
234 CancellationToken::new(),
235 worker,
236 rx,
237 RelayEndpoint::shared("127.0.0.1:1"),
238 Diagnostics::default(),
239 );
240 let mut first = Box::pin(tunnel.stop());
241 let mut second = Box::pin(tunnel.stop());
242 poll_pending(&mut first).await;
243 poll_pending(&mut second).await;
244 release.send(()).unwrap();
245 first.await.unwrap();
246 second.await.unwrap();
247 }
248}