use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker, Proxy};
use fuchsia_component::client::connect_to_protocol_at_path;
use fuchsia_sync::RwLock;
use std::sync::Arc;
const SVC_DIR: &str = "/svc";
pub trait Connect {
type Proxy: Proxy;
fn connect(&self) -> Result<Self::Proxy, anyhow::Error>;
}
#[derive(Clone)]
pub struct ServiceReconnector<P>
where
P: DiscoverableProtocolMarker,
<P as ProtocolMarker>::Proxy: Clone,
{
inner: Arc<ServiceReconnectorInner<P>>,
}
impl<P> ServiceReconnector<P>
where
P: DiscoverableProtocolMarker,
<P as ProtocolMarker>::Proxy: Clone,
{
pub fn new() -> Self {
Self::with_service_at(SVC_DIR)
}
pub fn with_service_at(service_directory_path: &str) -> Self {
let service_path = format!("{}/{}", service_directory_path, P::PROTOCOL_NAME);
Self::with_service_at_path(service_path)
}
pub fn with_service_at_path<S: Into<String>>(service_path: S) -> Self {
let service_path = service_path.into();
Self { inner: Arc::new(ServiceReconnectorInner { proxy: RwLock::new(None), service_path }) }
}
}
impl<P> Connect for ServiceReconnector<P>
where
P: DiscoverableProtocolMarker,
<P as ProtocolMarker>::Proxy: Clone,
{
type Proxy = P::Proxy;
fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
self.inner.connect()
}
}
struct ServiceReconnectorInner<P>
where
P: ProtocolMarker,
<P as ProtocolMarker>::Proxy: Clone,
{
proxy: RwLock<Option<<P as ProtocolMarker>::Proxy>>,
service_path: String,
}
impl<P> Connect for ServiceReconnectorInner<P>
where
P: DiscoverableProtocolMarker,
<P as ProtocolMarker>::Proxy: Clone,
{
type Proxy = P::Proxy;
fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
if let Some(ref proxy) = *self.proxy.read() {
if !proxy.is_closed() {
return Ok(proxy.clone());
}
}
let mut proxy = self.proxy.write();
if let Some(ref proxy) = *proxy {
if !proxy.is_closed() {
return Ok(proxy.clone());
}
}
let p = connect_to_protocol_at_path::<P>(&self.service_path)?;
*proxy = Some(p.clone());
Ok(p)
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_test_fidl_connector::{TestMarker, TestRequest, TestRequestStream};
use fuchsia_async as fasync;
use fuchsia_component::server::ServiceFs;
use futures::prelude::*;
use std::cell::Cell;
#[fasync::run_singlethreaded(test)]
async fn test_service_reconnector() {
let ns = fdio::Namespace::installed().expect("installed namespace");
let service_device_path = "/test/service_connector/svc";
let c = ServiceReconnector::<TestMarker>::with_service_at(service_device_path);
let (service_channel, server_end) = fidl::endpoints::create_endpoints();
ns.bind(&service_device_path, service_channel).expect("bind test svc");
let gen = Cell::new(1);
let mut fs = ServiceFs::new_local();
fs.add_fidl_service(move |mut stream: TestRequestStream| {
let current_gen = gen.get();
gen.set(current_gen + 1);
fasync::Task::local(async move {
while let Some(req) = stream.try_next().await.unwrap_or(None) {
match req {
TestRequest::Ping { responder } => {
responder.send(current_gen).expect("patient client");
}
TestRequest::Disconnect { responder } => {
drop(responder);
}
}
}
})
.detach()
})
.serve_connection(server_end)
.expect("serve_connection");
fasync::Task::local(fs.collect()).detach();
let proxy = c.connect().expect("can connect");
assert_eq!(proxy.ping().await.expect("ping"), 1);
let proxy = c.connect().expect("can connect");
assert_eq!(proxy.ping().await.expect("ping"), 1);
proxy.disconnect().await.expect_err("oops");
let proxy = c.connect().expect("can connect");
assert_eq!(proxy.ping().await.expect("ping"), 2);
}
}