1use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker, Proxy};
6use fuchsia_component::client::connect_to_protocol_at_path;
7use fuchsia_sync::RwLock;
8use std::sync::Arc;
9
10const SVC_DIR: &str = "/svc";
11
12pub trait Connect {
14 type Proxy: Proxy;
16
17 fn connect(&self) -> Result<Self::Proxy, anyhow::Error>;
19}
20
21#[derive(Clone)]
25pub struct ServiceReconnector<P>
26where
27 P: DiscoverableProtocolMarker,
28 <P as ProtocolMarker>::Proxy: Clone,
29{
30 inner: Arc<ServiceReconnectorInner<P>>,
31}
32
33impl<P> ServiceReconnector<P>
34where
35 P: DiscoverableProtocolMarker,
36 <P as ProtocolMarker>::Proxy: Clone,
37{
38 pub fn new() -> Self {
41 Self::with_service_at(SVC_DIR)
42 }
43
44 pub fn with_service_at(service_directory_path: &str) -> Self {
49 let service_path = format!("{}/{}", service_directory_path, P::PROTOCOL_NAME);
50 Self::with_service_at_path(service_path)
51 }
52
53 pub fn with_service_at_path<S: Into<String>>(service_path: S) -> Self {
55 let service_path = service_path.into();
56 Self { inner: Arc::new(ServiceReconnectorInner { proxy: RwLock::new(None), service_path }) }
57 }
58}
59
60impl<P> Connect for ServiceReconnector<P>
61where
62 P: DiscoverableProtocolMarker,
63 <P as ProtocolMarker>::Proxy: Clone,
64{
65 type Proxy = P::Proxy;
66
67 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
68 self.inner.connect()
69 }
70}
71
72struct ServiceReconnectorInner<P>
73where
74 P: ProtocolMarker,
75 <P as ProtocolMarker>::Proxy: Clone,
76{
77 proxy: RwLock<Option<<P as ProtocolMarker>::Proxy>>,
78 service_path: String,
79}
80
81impl<P> Connect for ServiceReconnectorInner<P>
82where
83 P: DiscoverableProtocolMarker,
84 <P as ProtocolMarker>::Proxy: Clone,
85{
86 type Proxy = P::Proxy;
87
88 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
89 if let Some(ref proxy) = *self.proxy.read() {
90 if !proxy.is_closed() {
94 return Ok(proxy.clone());
95 }
96 }
97
98 let mut proxy = self.proxy.write();
102 if let Some(ref proxy) = *proxy {
103 if !proxy.is_closed() {
104 return Ok(proxy.clone());
105 }
106 }
107
108 let p = connect_to_protocol_at_path::<P>(&self.service_path)?;
109 *proxy = Some(p.clone());
110 Ok(p)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use fidl_test_fidl_connector::{TestMarker, TestRequest, TestRequestStream};
118 use fuchsia_async as fasync;
119 use fuchsia_component::server::ServiceFs;
120 use futures::prelude::*;
121 use std::cell::Cell;
122
123 #[fasync::run_singlethreaded(test)]
124 async fn test_service_reconnector() {
125 let ns = fdio::Namespace::installed().expect("installed namespace");
126 let service_device_path = "/test/service_connector/svc";
127 let c = ServiceReconnector::<TestMarker>::with_service_at(service_device_path);
128 let (service_channel, server_end) = fidl::endpoints::create_endpoints();
129 ns.bind(&service_device_path, service_channel).expect("bind test svc");
130
131 let gen = Cell::new(1);
135
136 let mut fs = ServiceFs::new_local();
137 fs.add_fidl_service(move |mut stream: TestRequestStream| {
138 let current_gen = gen.get();
139 gen.set(current_gen + 1);
140 fasync::Task::local(async move {
141 while let Some(req) = stream.try_next().await.unwrap_or(None) {
142 match req {
143 TestRequest::Ping { responder } => {
144 responder.send(current_gen).expect("patient client");
145 }
146 TestRequest::Disconnect { responder } => {
147 drop(responder);
149 }
150 }
151 }
152 })
153 .detach()
154 })
155 .serve_connection(server_end)
156 .expect("serve_connection");
157
158 fasync::Task::local(fs.collect()).detach();
159
160 let proxy = c.connect().expect("can connect");
161 assert_eq!(proxy.ping().await.expect("ping"), 1);
162
163 let proxy = c.connect().expect("can connect");
164 assert_eq!(proxy.ping().await.expect("ping"), 1);
165
166 proxy.disconnect().await.expect_err("oops");
167
168 let proxy = c.connect().expect("can connect");
169 assert_eq!(proxy.ping().await.expect("ping"), 2);
170 }
171}