1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
45use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker, Proxy};
6use fuchsia_component::client::connect_to_protocol_at_path;
7use fuchsia_sync::RwLock;
8use std::sync::Arc;
910const SVC_DIR: &str = "/svc";
1112/// A trait that manages connecting to service.
13pub trait Connect {
14/// Connect to this FIDL service.
15type Proxy: Proxy;
1617/// Connect to the proxy, or return an error.
18fn connect(&self) -> Result<Self::Proxy, anyhow::Error>;
19}
2021/// A `Connect` implementation that will try to reconnect to a FIDL service if the channel has
22/// received a peer closed signal. This means it is possible `ServiceReconnector` to return a
23/// closed channel, but it should eventually reconnect once the FIDL service is restarted.
24#[derive(Clone)]
25pub struct ServiceReconnector<P>
26where
27P: DiscoverableProtocolMarker,
28 <P as ProtocolMarker>::Proxy: Clone,
29{
30 inner: Arc<ServiceReconnectorInner<P>>,
31}
3233impl<P> ServiceReconnector<P>
34where
35P: DiscoverableProtocolMarker,
36 <P as ProtocolMarker>::Proxy: Clone,
37{
38/// Return a FIDL service connector at the default service directory in the
39 /// application's root namespace.
40pub fn new() -> Self {
41Self::with_service_at(SVC_DIR)
42 }
4344/// Return a FIDL service connector at the specified service directory in
45 /// the application's root namespace.
46 ///
47 /// The service directory path must be an absolute path.
48pub fn with_service_at(service_directory_path: &str) -> Self {
49let service_path = format!("{}/{}", service_directory_path, P::PROTOCOL_NAME);
50Self::with_service_at_path(service_path)
51 }
5253/// Return a FIDL service connector at the specified service path.
54pub fn with_service_at_path<S: Into<String>>(service_path: S) -> Self {
55let service_path = service_path.into();
56Self { inner: Arc::new(ServiceReconnectorInner { proxy: RwLock::new(None), service_path }) }
57 }
58}
5960impl<P> Connect for ServiceReconnector<P>
61where
62P: DiscoverableProtocolMarker,
63 <P as ProtocolMarker>::Proxy: Clone,
64{
65type Proxy = P::Proxy;
6667fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
68self.inner.connect()
69 }
70}
7172struct ServiceReconnectorInner<P>
73where
74P: ProtocolMarker,
75 <P as ProtocolMarker>::Proxy: Clone,
76{
77 proxy: RwLock<Option<<P as ProtocolMarker>::Proxy>>,
78 service_path: String,
79}
8081impl<P> Connect for ServiceReconnectorInner<P>
82where
83P: DiscoverableProtocolMarker,
84 <P as ProtocolMarker>::Proxy: Clone,
85{
86type Proxy = P::Proxy;
8788fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
89if let Some(ref proxy) = *self.proxy.read() {
90// Note: `.is_closed()` only returns true if we've observed a peer
91 // closed on the channel. So if the caller hasn't tried to interact
92 // with the proxy, we won't actually know if this proxy is closed.
93if !proxy.is_closed() {
94return Ok(proxy.clone());
95 }
96 }
9798// We didn't connect, so grab the write mutex. Note it's possible we've
99 // lost a race with another connection, so we need to re-check if the
100 // proxy was closed.
101let mut proxy = self.proxy.write();
102if let Some(ref proxy) = *proxy {
103if !proxy.is_closed() {
104return Ok(proxy.clone());
105 }
106 }
107108let p = connect_to_protocol_at_path::<P>(&self.service_path)?;
109*proxy = Some(p.clone());
110Ok(p)
111 }
112}
113114#[cfg(test)]
115mod tests {
116use super::*;
117use fidl_test_fidl_connector::{TestMarker, TestRequest, TestRequestStream};
118use fuchsia_async as fasync;
119use fuchsia_component::server::ServiceFs;
120use futures::prelude::*;
121use std::cell::Cell;
122123#[fasync::run_singlethreaded(test)]
124async fn test_service_reconnector() {
125let ns = fdio::Namespace::installed().expect("installed namespace");
126let service_device_path = "/test/service_connector/svc";
127let c = ServiceReconnector::<TestMarker>::with_service_at(service_device_path);
128let (service_channel, server_end) = fidl::endpoints::create_endpoints();
129 ns.bind(&service_device_path, service_channel).expect("bind test svc");
130131// In order to test that we reconnect, we create a mock service that
132 // closes the connection if the `disconnect` method is called in order
133 // to test if we created a new connection.
134let gen = Cell::new(1);
135136let mut fs = ServiceFs::new_local();
137 fs.add_fidl_service(move |mut stream: TestRequestStream| {
138let current_gen = gen.get();
139 gen.set(current_gen + 1);
140 fasync::Task::local(async move {
141while let Some(req) = stream.try_next().await.unwrap_or(None) {
142match req {
143 TestRequest::Ping { responder } => {
144 responder.send(current_gen).expect("patient client");
145 }
146 TestRequest::Disconnect { responder } => {
147// Close the response.
148drop(responder);
149 }
150 }
151 }
152 })
153 .detach()
154 })
155 .serve_connection(server_end)
156 .expect("serve_connection");
157158 fasync::Task::local(fs.collect()).detach();
159160let proxy = c.connect().expect("can connect");
161assert_eq!(proxy.ping().await.expect("ping"), 1);
162163let proxy = c.connect().expect("can connect");
164assert_eq!(proxy.ping().await.expect("ping"), 1);
165166 proxy.disconnect().await.expect_err("oops");
167168let proxy = c.connect().expect("can connect");
169assert_eq!(proxy.ping().await.expect("ping"), 2);
170 }
171}