use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use crate::binder::SIBinder;
use crate::hub::{BnClientCallback, IClientCallback};
use crate::status::Status;
use crate::{Interface, Strong};
pub type ActiveServicesCallback = Arc<dyn Fn(bool) -> bool + Send + Sync>;
trait Registry: Send + Sync {
fn add_lazy_service(&self, name: &str, binder: &SIBinder) -> std::result::Result<(), Status>;
fn register_client_callback(
&self,
name: &str,
binder: &SIBinder,
callback: &Strong<dyn IClientCallback>,
) -> std::result::Result<(), Status>;
fn try_unregister_service(
&self,
name: &str,
binder: &SIBinder,
) -> std::result::Result<(), Status>;
}
struct HubRegistry;
impl Registry for HubRegistry {
fn add_lazy_service(&self, name: &str, binder: &SIBinder) -> std::result::Result<(), Status> {
crate::hub::add_lazy_service(name, binder.clone())
}
fn register_client_callback(
&self,
name: &str,
binder: &SIBinder,
callback: &Strong<dyn IClientCallback>,
) -> std::result::Result<(), Status> {
crate::hub::register_client_callback_status(name, binder, callback)
}
fn try_unregister_service(
&self,
name: &str,
binder: &SIBinder,
) -> std::result::Result<(), Status> {
crate::hub::try_unregister_service_status(name, binder)
}
}
#[derive(Clone)]
struct RegisteredService {
name: String,
binder: SIBinder,
has_clients: bool,
registered: bool,
}
struct Inner {
services: BTreeMap<String, RegisteredService>,
num_connected_services: usize,
previous_has_clients: Option<bool>,
active_services_callback: Option<ActiveServicesCallback>,
callback: Option<Strong<dyn IClientCallback>>,
}
impl Inner {
fn update_cache_client_count(&mut self) {
self.num_connected_services = self.services.values().filter(|e| e.has_clients).count();
}
}
struct Shared {
ops: Mutex<()>,
inner: Mutex<Inner>,
force_persist: AtomicBool,
registry: Arc<dyn Registry>,
#[cfg(test)]
exited: AtomicBool,
}
struct ClientCounterCallback {
shared: Weak<Shared>,
}
impl Interface for ClientCounterCallback {}
impl IClientCallback for ClientCounterCallback {
fn onClients(&self, registered: &SIBinder, has_clients: bool) -> crate::BinderResult<()> {
if let Some(shared) = self.shared.upgrade() {
shared.on_clients_binder(registered, has_clients);
}
Ok(())
}
}
impl Shared {
fn lock(&self) -> std::sync::MutexGuard<'_, Inner> {
self.inner.lock().unwrap_or_else(|e| e.into_inner())
}
fn name_of(&self, binder: &SIBinder) -> Option<String> {
self.lock()
.services
.values()
.find(|e| e.binder == *binder)
.map(|e| e.name.clone())
}
fn on_clients_binder(&self, binder: &SIBinder, has_clients: bool) {
match self.name_of(binder) {
Some(name) => {
self.on_clients(&name, has_clients);
}
None => log::error!("onClients for a service this registrar did not register"),
}
}
fn on_clients(&self, name: &str, has_clients: bool) -> bool {
{
let mut inner = self.lock();
let Some(entry) = inner.services.get_mut(name) else {
return false;
};
if entry.has_clients == has_clients {
log::debug!("{name}: onClients({has_clients}) matched what we already believed");
}
entry.has_clients = has_clients;
inner.update_cache_client_count();
log::info!(
"Process has {} (of {} available) client(s) in use after notification {name} has clients: {has_clients}",
inner.num_connected_services,
inner.services.len(),
);
}
self.maybe_try_shutdown();
true
}
fn maybe_try_shutdown(&self) {
if self.force_persist.load(Ordering::Acquire) {
log::info!("Shutdown prevented by force_persist override flag.");
return;
}
let _ops = self.ops.lock().unwrap_or_else(|e| e.into_inner());
let to_fire = {
let mut inner = self.lock();
let has_clients = inner.num_connected_services != 0;
match &inner.active_services_callback {
Some(cb) if inner.previous_has_clients != Some(has_clients) => {
let cb = cb.clone();
inner.previous_has_clients = Some(has_clients);
Some((cb, has_clients))
}
_ => None,
}
};
let handled = match to_fire {
Some((cb, has_clients)) => cb(has_clients),
None => false,
};
if !handled && self.lock().num_connected_services == 0 {
self.try_shutdown();
}
}
fn try_shutdown(&self) {
log::info!(
"Trying to shut down the service. No clients in use for any service in process."
);
if self.try_unregister() {
log::info!("Unregistered all clients and exiting");
self.exit_process();
return;
}
self.re_register();
}
#[cfg(not(test))]
fn exit_process(&self) {
std::process::exit(0);
}
#[cfg(test)]
fn exit_process(&self) {
self.exited.store(true, Ordering::Release);
}
fn try_unregister(&self) -> bool {
if self.force_persist.load(Ordering::Acquire) {
return false;
}
let candidates: Vec<(String, SIBinder)> = {
let inner = self.lock();
inner
.services
.values()
.filter(|e| e.registered)
.map(|e| (e.name.clone(), e.binder.clone()))
.collect()
};
if candidates.is_empty() {
return false;
}
for (name, binder) in candidates {
match self.registry.try_unregister_service(&name, &binder) {
Ok(()) => {
if let Some(entry) = self.lock().services.get_mut(&name) {
entry.registered = false;
}
}
Err(e) => {
log::info!("Failed to unregister service {name} ({e:?})");
return false;
}
}
}
true
}
fn restore(&self, name: &str, previous: Option<RegisteredService>) {
let mut inner = self.lock();
match previous {
Some(mut entry) => {
if let Some(current) = inner.services.get(name) {
entry.has_clients = current.has_clients;
if !current.registered {
entry.registered = false;
}
}
inner.services.insert(name.to_string(), entry)
}
None => inner.services.remove(name),
};
inner.update_cache_client_count();
}
fn re_register(&self) {
let pending: Vec<(String, SIBinder)> = {
let inner = self.lock();
inner
.services
.values()
.filter(|e| !e.registered)
.map(|e| (e.name.clone(), e.binder.clone()))
.collect()
};
for (name, binder) in pending {
match self.registry.add_lazy_service(&name, &binder) {
Ok(()) => {
if let Some(entry) = self.lock().services.get_mut(&name) {
entry.registered = true;
}
}
Err(e) => log::error!("Bad state: could not re-register {name} ({e:?})"),
}
}
}
}
#[derive(Clone)]
pub struct LazyServiceRegistrar {
shared: Arc<Shared>,
}
impl LazyServiceRegistrar {
pub fn instance() -> &'static LazyServiceRegistrar {
static INSTANCE: std::sync::OnceLock<LazyServiceRegistrar> = std::sync::OnceLock::new();
INSTANCE.get_or_init(LazyServiceRegistrar::new)
}
pub fn new() -> Self {
Self::with_registry(Arc::new(HubRegistry))
}
fn with_registry(registry: Arc<dyn Registry>) -> Self {
LazyServiceRegistrar {
shared: Arc::new(Shared {
ops: Mutex::new(()),
inner: Mutex::new(Inner {
services: BTreeMap::new(),
num_connected_services: 0,
previous_has_clients: None,
active_services_callback: None,
callback: None,
}),
force_persist: AtomicBool::new(false),
registry,
#[cfg(test)]
exited: AtomicBool::new(false),
}),
}
}
fn callback(&self) -> Strong<dyn IClientCallback> {
let mut inner = self.shared.lock();
inner
.callback
.get_or_insert_with(|| {
BnClientCallback::new_binder(ClientCounterCallback {
shared: Arc::downgrade(&self.shared),
})
})
.clone()
}
pub fn register_service(
&self,
name: &str,
binder: impl Into<SIBinder>,
) -> std::result::Result<(), Status> {
let binder = binder.into();
let _ops = self.shared.ops.lock().unwrap_or_else(|e| e.into_inner());
let previous = {
let mut inner = self.shared.lock();
let previous = inner.services.get(name).cloned();
inner.services.insert(
name.to_string(),
RegisteredService {
name: name.to_string(),
binder: binder.clone(),
has_clients: previous.as_ref().is_some_and(|e| e.has_clients),
registered: true,
},
);
inner.update_cache_client_count();
previous
};
let tracked = previous.is_some();
log::info!(
"{} service {name}",
if tracked {
"Re-registering"
} else {
"Registering"
}
);
if let Err(e) = self.shared.registry.add_lazy_service(name, &binder) {
log::error!("Failed to register service {name} ({e:?})");
self.shared.restore(name, previous);
return Err(e);
}
if !tracked {
let callback = self.callback();
if let Err(e) = self
.shared
.registry
.register_client_callback(name, &binder, &callback)
{
log::error!("Failed to add client callback for service {name} ({e:?})");
self.shared.restore(name, previous);
return Err(e);
}
}
Ok(())
}
pub fn force_persist(&self, persist: bool) {
self.shared.force_persist.store(persist, Ordering::Release);
if !persist {
self.shared.maybe_try_shutdown();
}
}
pub fn is_force_persisted(&self) -> bool {
self.shared.force_persist.load(Ordering::Acquire)
}
pub fn set_active_services_callback(&self, callback: ActiveServicesCallback) {
self.shared.lock().active_services_callback = Some(callback);
}
pub fn on_clients(&self, name: &str, has_clients: bool) -> bool {
self.shared.on_clients(name, has_clients)
}
pub fn try_unregister(&self) -> bool {
self.shared.try_unregister()
}
pub fn re_register(&self) {
self.shared.re_register()
}
pub fn snapshot(&self) -> Vec<(String, bool, bool)> {
let inner = self.shared.lock();
inner
.services
.values()
.map(|e| (e.name.clone(), e.has_clients, e.registered))
.collect()
}
pub fn registered_count(&self) -> usize {
let inner = self.shared.lock();
inner.services.values().filter(|e| e.registered).count()
}
pub fn binder_for(&self, name: &str) -> Option<SIBinder> {
let inner = self.shared.lock();
inner.services.get(name).map(|e| e.binder.clone())
}
}
impl Default for LazyServiceRegistrar {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binder::Stability;
use crate::native::Binder;
use crate::{Parcel, Remotable, Result as RsResult, TransactionCode};
use std::sync::atomic::AtomicUsize;
use std::thread::JoinHandle;
use std::time::Duration;
fn spin_until(mut f: impl FnMut() -> bool) {
for _ in 0..1_000_000 {
if f() {
return;
}
std::thread::yield_now();
}
panic!("the notification never landed");
}
struct Dummy;
impl Remotable for Dummy {
fn descriptor() -> &'static str {
"test.lazy_service.Dummy"
}
fn on_transact(&self, _: TransactionCode, _: &mut Parcel, _: &mut Parcel) -> RsResult<()> {
Ok(())
}
fn on_dump(&self, _: &mut dyn std::io::Write, _: &[String]) -> RsResult<()> {
Ok(())
}
}
fn fresh_binder() -> SIBinder {
let b = Binder::new_with_stability(Dummy, Stability::Local);
Interface::as_binder(&b)
}
#[derive(Default)]
struct FakeRegistry {
calls: Mutex<Vec<String>>,
binders: Mutex<Vec<(String, SIBinder)>>,
callbacks: Mutex<Vec<Strong<dyn IClientCallback>>>,
refuse_unregister: Mutex<Vec<String>>,
}
impl FakeRegistry {
fn calls(&self) -> Vec<String> {
self.calls.lock().unwrap().clone()
}
fn last_callback(&self) -> Strong<dyn IClientCallback> {
self.callbacks.lock().unwrap().last().unwrap().clone()
}
fn last_binder(&self, call: &str) -> SIBinder {
self.binders
.lock()
.unwrap()
.iter()
.rev()
.find(|(c, _)| c == call)
.unwrap_or_else(|| panic!("no {call} call recorded"))
.1
.clone()
}
fn record(&self, call: String, binder: &SIBinder) {
self.binders
.lock()
.unwrap()
.push((call.clone(), binder.clone()));
self.calls.lock().unwrap().push(call);
}
fn refuse(&self, name: &str) {
self.refuse_unregister
.lock()
.unwrap()
.push(name.to_string());
}
}
impl Registry for FakeRegistry {
fn add_lazy_service(
&self,
name: &str,
binder: &SIBinder,
) -> std::result::Result<(), Status> {
self.record(format!("add:{name}"), binder);
Ok(())
}
fn register_client_callback(
&self,
name: &str,
binder: &SIBinder,
callback: &Strong<dyn IClientCallback>,
) -> std::result::Result<(), Status> {
self.callbacks.lock().unwrap().push(callback.clone());
self.record(format!("cb:{name}"), binder);
Ok(())
}
fn try_unregister_service(
&self,
name: &str,
binder: &SIBinder,
) -> std::result::Result<(), Status> {
self.record(format!("unreg:{name}"), binder);
if self
.refuse_unregister
.lock()
.unwrap()
.iter()
.any(|n| n == name)
{
return Err(Status::new_service_specific_error(-1, None));
}
Ok(())
}
}
fn fixture() -> (LazyServiceRegistrar, Arc<FakeRegistry>) {
let registry = Arc::new(FakeRegistry::default());
(
LazyServiceRegistrar::with_registry(registry.clone()),
registry,
)
}
#[test]
fn register_calls_add_service_then_client_callback() {
let (reg, registry) = fixture();
reg.register_service("foo", fresh_binder()).unwrap();
assert_eq!(registry.calls(), vec!["add:foo", "cb:foo"]);
let snap = reg.snapshot();
assert_eq!(snap.len(), 1);
assert_eq!(snap[0].0, "foo");
assert!(snap[0].2, "registered=true after register_service");
assert_eq!(reg.registered_count(), 1);
}
#[test]
fn re_registering_same_binder_does_not_re_register_the_callback() {
let (reg, registry) = fixture();
let binder = fresh_binder();
reg.register_service("foo", binder.clone()).unwrap();
reg.register_service("foo", binder).unwrap();
assert_eq!(registry.calls(), vec!["add:foo", "cb:foo", "add:foo"]);
}
#[test]
fn re_registering_same_binder_keeps_the_reported_client_state() {
let (reg, _registry) = fixture();
let binder = fresh_binder();
reg.register_service("keep", binder.clone()).unwrap();
reg.on_clients("keep", true);
reg.register_service("keep", binder).unwrap();
assert!(
reg.snapshot()[0].1,
"a re-register must not throw away the reported client"
);
assert!(
!reg.shared.exited.load(Ordering::Acquire),
"a service with a client is not idle"
);
reg.on_clients("keep", false);
assert!(reg.shared.exited.load(Ordering::Acquire));
}
#[test]
fn re_registering_different_binder_does_not_register_a_second_callback() {
let (reg, registry) = fixture();
let second = fresh_binder();
reg.register_service("dup", fresh_binder()).unwrap();
reg.register_service("dup", second.clone()).unwrap();
assert_eq!(registry.calls(), vec!["add:dup", "cb:dup", "add:dup"]);
assert_eq!(
registry.last_binder("add:dup"),
second,
"the re-`addService` must carry the new binder"
);
let got = reg.binder_for("dup").unwrap();
assert!(
std::sync::Arc::ptr_eq(got.as_arc(), second.as_arc()),
"second register_service wins"
);
reg.shared.on_clients_binder(&second, true);
assert!(
reg.snapshot()[0].1,
"onClients for the new binder must resolve to this entry"
);
}
#[test]
fn last_client_leaving_unregisters_and_exits() {
let (reg, registry) = fixture();
let binder = fresh_binder();
reg.register_service("solo", binder.clone()).unwrap();
reg.on_clients("solo", true);
assert!(!reg.shared.exited.load(Ordering::Acquire), "still in use");
reg.on_clients("solo", false);
assert!(registry.calls().contains(&"unreg:solo".to_string()));
assert_eq!(
registry.last_binder("unreg:solo"),
binder,
"tryUnregisterService must name the binder it registered"
);
assert!(reg.shared.exited.load(Ordering::Acquire), "process exits");
assert_eq!(reg.registered_count(), 0);
}
#[test]
fn shutdown_waits_for_every_service() {
let (reg, registry) = fixture();
reg.register_service("a", fresh_binder()).unwrap();
reg.register_service("b", fresh_binder()).unwrap();
reg.on_clients("a", true);
reg.on_clients("b", true);
reg.on_clients("b", false);
assert!(
!registry.calls().iter().any(|c| c.starts_with("unreg:")),
"`a` still has clients -> nothing unregistered"
);
assert!(!reg.shared.exited.load(Ordering::Acquire));
assert_eq!(reg.registered_count(), 2);
reg.on_clients("a", false);
assert!(reg.shared.exited.load(Ordering::Acquire));
assert_eq!(reg.registered_count(), 0);
}
#[test]
fn a_refused_unregister_re_registers_and_does_not_exit() {
let (reg, registry) = fixture();
reg.register_service("a", fresh_binder()).unwrap();
reg.register_service("b", fresh_binder()).unwrap();
reg.on_clients("a", true);
reg.on_clients("b", true);
registry.refuse("b");
reg.on_clients("a", false);
reg.on_clients("b", false);
assert!(!reg.shared.exited.load(Ordering::Acquire), "no exit");
assert_eq!(reg.registered_count(), 2, "`a` is put back");
assert!(registry.calls().contains(&"unreg:a".to_string()));
assert_eq!(
registry.calls().iter().filter(|c| *c == "add:a").count(),
2,
"`a` re-registered"
);
assert_eq!(
registry.calls().iter().filter(|c| *c == "cb:a").count(),
1,
"callback registered once"
);
}
#[test]
fn re_register_does_not_invent_clients() {
let (reg, _registry) = fixture();
reg.register_service("z", fresh_binder()).unwrap();
reg.on_clients("z", true);
reg.force_persist(true);
reg.on_clients("z", false);
assert!(!reg.try_unregister(), "force_persist blocks");
reg.force_persist(false);
assert!(reg.shared.exited.load(Ordering::Acquire));
reg.re_register();
assert_eq!(reg.registered_count(), 1);
assert!(!reg.snapshot()[0].1, "has_clients stays false");
let (reg, registry) = fixture();
reg.register_service("a", fresh_binder()).unwrap();
reg.register_service("b", fresh_binder()).unwrap();
reg.on_clients("a", true);
reg.on_clients("b", true);
registry.refuse("b");
assert!(!reg.try_unregister(), "the service manager refused `b`");
reg.re_register();
assert_eq!(reg.registered_count(), 2);
assert!(
reg.snapshot().iter().all(|s| s.1),
"re_register must not clear the clients the service manager reported"
);
}
#[test]
fn force_persist_blocks_unregister() {
let (reg, registry) = fixture();
reg.register_service("x", fresh_binder()).unwrap();
reg.force_persist(true);
assert!(reg.is_force_persisted());
reg.on_clients("x", false);
assert!(!reg.try_unregister(), "force_persist blocks");
assert!(!registry.calls().iter().any(|c| c.starts_with("unreg:")));
assert!(!reg.shared.exited.load(Ordering::Acquire));
assert!(reg.snapshot()[0].2, "still registered");
}
#[test]
fn active_services_callback_can_take_over() {
let (reg, registry) = fixture();
let seen = Arc::new(Mutex::new(Vec::new()));
let sink = seen.clone();
reg.set_active_services_callback(Arc::new(move |has_clients| {
sink.lock().unwrap().push(has_clients);
true
}));
reg.register_service("x", fresh_binder()).unwrap();
reg.on_clients("x", false);
assert_eq!(*seen.lock().unwrap(), vec![false]);
assert!(
!reg.shared.exited.load(Ordering::Acquire),
"callback owns it"
);
assert!(!registry.calls().iter().any(|c| c.starts_with("unreg:")));
assert_eq!(reg.registered_count(), 1);
}
#[test]
fn active_services_callback_fires_only_on_change() {
let (reg, _registry) = fixture();
let seen = Arc::new(Mutex::new(Vec::new()));
let sink = seen.clone();
reg.set_active_services_callback(Arc::new(move |has_clients| {
sink.lock().unwrap().push(has_clients);
true
}));
reg.register_service("x", fresh_binder()).unwrap();
reg.on_clients("x", false);
reg.on_clients("x", true);
reg.on_clients("x", true); reg.on_clients("x", false);
assert_eq!(*seen.lock().unwrap(), vec![false, true, false]);
}
#[test]
fn on_clients_for_unknown_service_is_silent() {
let (reg, _registry) = fixture();
assert!(!reg.on_clients("nope", true));
}
#[test]
fn empty_registrar_never_reports_safe_to_exit() {
let (reg, _registry) = fixture();
assert!(!reg.try_unregister());
}
#[test]
fn client_callback_routes_by_binder_identity() {
let (reg, registry) = fixture();
let a = fresh_binder();
let b = fresh_binder();
reg.register_service("a", a.clone()).unwrap();
reg.register_service("b", b.clone()).unwrap();
reg.shared.on_clients_binder(&a, true);
reg.shared.on_clients_binder(&b, true);
reg.shared.on_clients_binder(&b, false);
assert!(!reg.snapshot().iter().find(|s| s.0 == "b").unwrap().1);
assert!(reg.snapshot().iter().find(|s| s.0 == "a").unwrap().1);
assert!(!registry.calls().iter().any(|c| c.starts_with("unreg:")));
reg.shared.on_clients_binder(&fresh_binder(), false);
assert!(!reg.shared.exited.load(Ordering::Acquire));
}
#[derive(Default)]
struct ReentrantRegistry {
shared: Mutex<Option<Weak<Shared>>>,
report: bool,
notifier: Mutex<Option<JoinHandle<()>>>,
}
impl ReentrantRegistry {
fn join(&self) {
if let Some(h) = self.notifier.lock().unwrap().take() {
h.join().unwrap();
}
}
}
impl Registry for ReentrantRegistry {
fn add_lazy_service(
&self,
_name: &str,
_binder: &SIBinder,
) -> std::result::Result<(), Status> {
Ok(())
}
fn register_client_callback(
&self,
name: &str,
binder: &SIBinder,
callback: &Strong<dyn IClientCallback>,
) -> std::result::Result<(), Status> {
let shared = self.shared.lock().unwrap().clone();
let Some(shared) = shared.as_ref().and_then(Weak::upgrade) else {
return Ok(());
};
let (cb, b, want) = (callback.clone(), binder.clone(), self.report);
*self.notifier.lock().unwrap() =
Some(std::thread::spawn(move || cb.onClients(&b, want).unwrap()));
let name = name.to_string();
spin_until(|| shared.lock().services.get(&name).map(|e| e.has_clients) == Some(want));
Ok(())
}
fn try_unregister_service(
&self,
_name: &str,
_binder: &SIBinder,
) -> std::result::Result<(), Status> {
Ok(())
}
}
#[test]
fn a_notification_arriving_during_registration_is_not_dropped() {
let registry = Arc::new(ReentrantRegistry {
report: true,
..Default::default()
});
let reg = LazyServiceRegistrar::with_registry(registry.clone());
*registry.shared.lock().unwrap() = Some(Arc::downgrade(®.shared));
reg.register_service("nested", fresh_binder()).unwrap();
registry.join();
assert!(
reg.snapshot()[0].1,
"the notification sent from inside registerClientCallback was dropped"
);
}
#[test]
fn one_client_callback_serves_the_whole_registrar() {
let (reg, registry) = fixture();
reg.register_service("a", fresh_binder()).unwrap();
let first = registry.last_callback();
reg.register_service("b", fresh_binder()).unwrap();
assert_eq!(
first,
registry.last_callback(),
"one callback per registrar"
);
assert!(
reg.shared.lock().callback.is_some(),
"the registrar must hold the callback itself"
);
first
.onClients(®.binder_for("a").unwrap(), true)
.unwrap();
assert!(reg.snapshot().iter().find(|s| s.0 == "a").unwrap().1);
}
#[test]
fn a_service_nobody_used_does_not_block_shutdown() {
let (reg, registry) = fixture();
reg.register_service("used", fresh_binder()).unwrap();
reg.register_service("never_used", fresh_binder()).unwrap();
reg.on_clients("used", true);
assert!(!reg.shared.exited.load(Ordering::Acquire), "still in use");
reg.on_clients("used", false);
assert!(
reg.shared.exited.load(Ordering::Acquire),
"`never_used` was never reported on, so it must not count as in use"
);
assert!(registry.calls().contains(&"unreg:never_used".to_string()));
}
#[test]
fn registrations_do_not_overlap() {
const CALLERS: usize = 4;
#[derive(Default)]
struct DepthProbe {
depth: AtomicUsize,
max_depth: AtomicUsize,
arrived: AtomicUsize,
}
impl Registry for DepthProbe {
fn add_lazy_service(&self, _: &str, _: &SIBinder) -> std::result::Result<(), Status> {
let depth = self.depth.fetch_add(1, Ordering::AcqRel) + 1;
self.max_depth.fetch_max(depth, Ordering::AcqRel);
spin_until(|| self.arrived.load(Ordering::Acquire) == CALLERS);
std::thread::sleep(Duration::from_millis(20));
self.max_depth
.fetch_max(self.depth.load(Ordering::Acquire), Ordering::AcqRel);
self.depth.fetch_sub(1, Ordering::AcqRel);
Ok(())
}
fn register_client_callback(
&self,
_: &str,
_: &SIBinder,
_: &Strong<dyn IClientCallback>,
) -> std::result::Result<(), Status> {
Ok(())
}
fn try_unregister_service(
&self,
_: &str,
_: &SIBinder,
) -> std::result::Result<(), Status> {
Ok(())
}
}
let registry = Arc::new(DepthProbe::default());
let reg = LazyServiceRegistrar::with_registry(registry.clone());
std::thread::scope(|scope| {
for i in 0..CALLERS {
let (reg, registry) = (®, ®istry);
scope.spawn(move || {
registry.arrived.fetch_add(1, Ordering::AcqRel);
reg.register_service(&format!("svc{i}"), fresh_binder())
.unwrap()
});
}
});
assert_eq!(reg.registered_count(), CALLERS);
assert_eq!(
registry.max_depth.load(Ordering::Acquire),
1,
"two register_service calls were inside addService at once"
);
}
#[test]
fn a_failed_re_register_keeps_the_notification_that_raced_it() {
#[derive(Default)]
struct NotifyThenFail {
shared: Mutex<Option<Weak<Shared>>>,
armed: AtomicBool,
notifier: Mutex<Option<JoinHandle<bool>>>,
}
impl Registry for NotifyThenFail {
fn add_lazy_service(
&self,
name: &str,
_: &SIBinder,
) -> std::result::Result<(), Status> {
if !self.armed.load(Ordering::Acquire) {
return Ok(());
}
let shared = self.shared.lock().unwrap().clone();
let Some(shared) = shared.as_ref().and_then(Weak::upgrade) else {
return Ok(());
};
let (s2, n2) = (shared.clone(), name.to_string());
*self.notifier.lock().unwrap() =
Some(std::thread::spawn(move || s2.on_clients(&n2, false)));
let name = name.to_string();
spin_until(|| {
shared.lock().services.get(&name).map(|e| e.has_clients) == Some(false)
});
Err(Status::new_service_specific_error(-1, None))
}
fn register_client_callback(
&self,
_: &str,
_: &SIBinder,
_: &Strong<dyn IClientCallback>,
) -> std::result::Result<(), Status> {
Ok(())
}
fn try_unregister_service(
&self,
_: &str,
_: &SIBinder,
) -> std::result::Result<(), Status> {
Ok(())
}
}
let registry = Arc::new(NotifyThenFail::default());
let reg = LazyServiceRegistrar::with_registry(registry.clone());
*registry.shared.lock().unwrap() = Some(Arc::downgrade(®.shared));
let binder = fresh_binder();
reg.register_service("racy", binder.clone()).unwrap();
reg.on_clients("racy", true);
registry.armed.store(true, Ordering::Release);
assert!(reg.register_service("racy", binder).is_err());
registry
.notifier
.lock()
.unwrap()
.take()
.unwrap()
.join()
.unwrap();
assert!(
!reg.snapshot()[0].1,
"the rollback put back a client the service manager had retracted"
);
}
#[test]
fn a_failed_registration_does_not_leave_an_entry() {
struct Failing;
impl Registry for Failing {
fn add_lazy_service(&self, _: &str, _: &SIBinder) -> std::result::Result<(), Status> {
Err(Status::new_service_specific_error(-1, None))
}
fn register_client_callback(
&self,
_: &str,
_: &SIBinder,
_: &Strong<dyn IClientCallback>,
) -> std::result::Result<(), Status> {
unreachable!("add_lazy_service fails first")
}
fn try_unregister_service(
&self,
_: &str,
_: &SIBinder,
) -> std::result::Result<(), Status> {
Ok(())
}
}
let reg = LazyServiceRegistrar::with_registry(Arc::new(Failing));
assert!(reg.register_service("gone", fresh_binder()).is_err());
assert!(
reg.snapshot().is_empty(),
"the published entry was rolled back"
);
assert_eq!(reg.registered_count(), 0);
}
#[test]
fn clones_share_state() {
let (reg, _registry) = fixture();
let other = reg.clone();
reg.register_service("shared", fresh_binder()).unwrap();
assert_eq!(other.registered_count(), 1);
assert!(other.binder_for("shared").is_some());
}
}