use std::any::Any;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
use std::thread::ThreadId;
use std::time::{Duration, Instant};
use crate::entry::uri::{Endpoint, Uri};
use crate::entry::{open_staged, Client, ClientOptions, OpenStage};
use crate::hub::{WaitEnd, WaiterState};
#[cfg(feature = "rpc")]
use crate::rpc::RpcSession;
use crate::{DeathRecipient, FromIBinder, Result, SIBinder, Status, StatusCode, Strong, WIBinder};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ReconnectPolicy {
pub initial: Duration,
pub multiplier: u32,
pub max: Duration,
pub jitter_percent: u8,
pub max_attempts: Option<u32>,
pub call_wait: Duration,
}
impl Default for ReconnectPolicy {
fn default() -> Self {
Self {
initial: Duration::from_millis(10),
multiplier: 2,
max: Duration::from_secs(1),
jitter_percent: 20,
max_attempts: None,
call_wait: Duration::from_secs(1),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConnectError {
code: StatusCode,
stop: bool,
}
impl ConnectError {
pub fn retry(code: StatusCode) -> Self {
Self { code, stop: false }
}
pub fn stop(code: StatusCode) -> Self {
Self { code, stop: true }
}
pub fn code(&self) -> StatusCode {
self.code
}
pub fn is_stop(&self) -> bool {
self.stop
}
}
impl From<StatusCode> for ConnectError {
fn from(code: StatusCode) -> Self {
Self::retry(code)
}
}
impl From<Status> for ConnectError {
fn from(status: Status) -> Self {
Self::retry(status.into())
}
}
pub struct Connection<'a, T: FromIBinder + ?Sized> {
proxy: &'a Strong<T>,
generation: u64,
#[cfg(feature = "rpc")]
session: Option<&'a RpcSession>,
}
impl<T: FromIBinder + ?Sized> Connection<'_, T> {
pub fn proxy(&self) -> &Strong<T> {
self.proxy
}
pub fn generation(&self) -> u64 {
self.generation
}
#[cfg(feature = "rpc")]
pub fn session(&self) -> Option<&RpcSession> {
self.session
}
}
type OptionsFn = dyn Fn(&mut ClientOptions, &Endpoint) + Send + Sync;
type OnConnectFn<T> =
dyn Fn(&Connection<'_, T>) -> std::result::Result<(), ConnectError> + Send + Sync;
pub struct ReconnectBuilder<T: FromIBinder + ?Sized> {
uri: Uri,
options: Option<Box<OptionsFn>>,
on_connect: Option<Box<OnConnectFn<T>>>,
policy: ReconnectPolicy,
}
impl<T: FromIBinder + ?Sized + 'static> ReconnectBuilder<T> {
pub fn options(
mut self,
f: impl Fn(&mut ClientOptions, &Endpoint) + Send + Sync + 'static,
) -> Self {
self.options = Some(Box::new(f));
self
}
pub fn on_connect(
mut self,
f: impl Fn(&Connection<'_, T>) -> std::result::Result<(), ConnectError> + Send + Sync + 'static,
) -> Self {
self.on_connect = Some(Box::new(f));
self
}
pub fn policy(mut self, policy: ReconnectPolicy) -> Self {
self.policy = policy;
self
}
pub fn build(self) -> Result<Reconnecting<T>> {
let mut uri = self.uri;
uri.validate()?;
let lookup = match uri.service.take() {
Some(name) => Lookup::Name(name),
None if uri.endpoint.is_kernel() => {
log::error!("Reconnecting: a kernel endpoint needs a service name ({uri})");
return Err(StatusCode::BadValue);
}
#[cfg(feature = "rpc")]
None => Lookup::Root,
#[cfg(not(feature = "rpc"))]
None => {
log::error!("Reconnecting: {uri} needs the `rpc` feature");
return Err(StatusCode::InvalidOperation);
}
};
let shared = Arc::new(Shared {
cfg: Config {
uri,
lookup,
options: self.options,
on_connect: self.on_connect,
policy: self.policy,
},
state: Mutex::new(State::new()),
cv: Condvar::new(),
#[cfg(feature = "tokio")]
watch: tokio::sync::watch::channel(()).0,
});
let attempt = shared.begin_attempt(&mut shared.lock());
match shared.attempt(attempt, Mode::Build) {
Ok(conn) => {
let _ = shared.finish_success(conn, false);
}
Err(f) if f.permanent => {
shared.close();
return Err(f.code);
}
Err(f) => {
log::info!(
"Reconnecting: {} not reachable yet ({:?})",
shared.cfg,
f.code
);
let mut st = shared.lock();
st.in_flight = None;
st.attempting = None;
st.last_error = Some(f.code);
shared.ensure_thread(&mut st);
}
}
Ok(Reconnecting { shared })
}
}
pub struct Reconnecting<T: FromIBinder + ?Sized + 'static> {
shared: Arc<Shared<T>>,
}
impl<T: FromIBinder + ?Sized + 'static> Reconnecting<T> {
pub fn builder(uri: impl Into<Uri>) -> ReconnectBuilder<T> {
ReconnectBuilder {
uri: uri.into(),
options: None,
on_connect: None,
policy: ReconnectPolicy::default(),
}
}
pub fn with<R, E: From<StatusCode>>(
&self,
f: impl FnOnce(&Strong<T>) -> std::result::Result<R, E>,
) -> std::result::Result<R, E> {
self.shared.with(f)
}
pub fn current(&self) -> Result<Strong<T>> {
self.shared.current()
}
pub fn wait_connected(&self, timeout: Option<Duration>) -> Result<Strong<T>> {
self.shared.wait_connected(timeout)
}
#[cfg(feature = "tokio")]
pub async fn connected(&self) -> Result<Strong<T>> {
if crate::is_handling_transaction() {
return Err(StatusCode::WouldBlock);
}
let mut published = self.shared.watch.subscribe();
loop {
match self.shared.acquire(Wait::Never) {
Ok(snap) => return Ok(snap.proxy),
Err(AcquireError::Closed(code)) => {
return Err(code.unwrap_or(StatusCode::DeadObject))
}
Err(AcquireError::NoThread) => return Err(StatusCode::Unknown),
Err(_) => {}
}
if self.shared.on_reconnect_thread(&self.shared.lock()) {
return Err(StatusCode::WouldBlock);
}
let _ = published.changed().await;
}
}
pub fn generation(&self) -> u64 {
self.shared.lock().gen
}
pub fn last_error(&self) -> Option<StatusCode> {
self.shared.lock().last_error
}
pub fn is_closed(&self) -> bool {
self.shared.lock().phase == Phase::Closed
}
pub fn downgrade(&self) -> WeakReconnecting<T> {
WeakReconnecting {
shared: Arc::downgrade(&self.shared),
}
}
pub fn close(self) {}
}
impl<T: FromIBinder + ?Sized + 'static> Drop for Reconnecting<T> {
fn drop(&mut self) {
self.shared.close();
}
}
pub struct WeakReconnecting<T: FromIBinder + ?Sized + 'static> {
shared: Weak<Shared<T>>,
}
impl<T: FromIBinder + ?Sized + 'static> Clone for WeakReconnecting<T> {
fn clone(&self) -> Self {
Self {
shared: self.shared.clone(),
}
}
}
impl<T: FromIBinder + ?Sized + 'static> WeakReconnecting<T> {
pub fn with<R, E: From<StatusCode>>(
&self,
f: impl FnOnce(&Strong<T>) -> std::result::Result<R, E>,
) -> std::result::Result<R, E> {
match self.shared.upgrade() {
Some(shared) => shared.with(f),
None => Err(StatusCode::DeadObject.into()),
}
}
pub fn current(&self) -> Result<Strong<T>> {
self.shared
.upgrade()
.ok_or(StatusCode::DeadObject)?
.current()
}
pub fn generation(&self) -> Option<u64> {
self.shared.upgrade().map(|s| s.lock().gen)
}
}
enum Lookup {
Name(String),
#[cfg(feature = "rpc")]
Root,
}
struct Config<T: FromIBinder + ?Sized> {
uri: Uri,
lookup: Lookup,
options: Option<Box<OptionsFn>>,
on_connect: Option<Box<OnConnectFn<T>>>,
policy: ReconnectPolicy,
}
impl<T: FromIBinder + ?Sized> std::fmt::Display for Config<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.lookup {
Lookup::Name(name) => write!(f, "{name:?} on {:?}", self.uri.endpoint),
#[cfg(feature = "rpc")]
Lookup::Root => write!(f, "the root of {:?}", self.uri.endpoint),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Connected,
Stale,
Closed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Watch {
Recipient,
#[cfg(feature = "rpc")]
Probe,
Never,
}
struct Conn<T: FromIBinder + ?Sized + 'static> {
proxy: Strong<T>,
binder: SIBinder,
attempt: u64,
#[cfg(feature = "rpc")]
watch: Watch,
recipient: Option<Arc<Recipient<T>>>,
#[cfg(feature = "rpc")]
session: Option<RpcSession>,
}
struct State<T: FromIBinder + ?Sized + 'static> {
phase: Phase,
conn: Option<Conn<T>>,
gen: u64,
next_attempt: u64,
in_flight: Option<u64>,
in_flight_died: bool,
thread_alive: bool,
thread_id: Option<ThreadId>,
attempting: Option<Instant>,
waiter: Option<Arc<WaiterState>>,
last_error: Option<StatusCode>,
}
impl<T: FromIBinder + ?Sized + 'static> State<T> {
fn new() -> Self {
Self {
phase: Phase::Stale,
conn: None,
gen: 0,
next_attempt: 0,
in_flight: None,
in_flight_died: false,
thread_alive: false,
thread_id: None,
attempting: None,
waiter: None,
last_error: None,
}
}
}
struct Shared<T: FromIBinder + ?Sized + 'static> {
cfg: Config<T>,
state: Mutex<State<T>>,
cv: Condvar,
#[cfg(feature = "tokio")]
watch: tokio::sync::watch::Sender<()>,
}
struct Recipient<T: FromIBinder + ?Sized + 'static> {
shared: Weak<Shared<T>>,
attempt: u64,
}
impl<T: FromIBinder + ?Sized + 'static> DeathRecipient for Recipient<T> {
fn binder_died(&self, _who: &WIBinder) {
if let Some(shared) = self.shared.upgrade() {
shared.on_death(self.attempt);
}
}
}
struct Snapshot<T: FromIBinder + ?Sized + 'static> {
proxy: Strong<T>,
#[cfg(feature = "rpc")]
attempt: u64,
#[cfg(feature = "rpc")]
watch: Watch,
#[cfg(feature = "rpc")]
session: Option<RpcSession>,
}
#[derive(Debug)]
struct Failure {
code: StatusCode,
permanent: bool,
}
impl Failure {
fn transient(code: StatusCode) -> Self {
Self {
code,
permanent: false,
}
}
fn permanent(code: StatusCode) -> Self {
Self {
code,
permanent: true,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Mode {
Build,
Thread,
}
#[derive(Clone, Copy)]
enum Wait {
Never,
Attempt,
Connected(Option<Instant>),
}
enum AcquireError {
NoConnection,
Closed(Option<StatusCode>),
WouldBlock,
TimedOut,
NoThread,
}
impl AcquireError {
fn dead(self) -> StatusCode {
match self {
AcquireError::WouldBlock => StatusCode::WouldBlock,
_ => StatusCode::DeadObject,
}
}
}
fn spawn_reconnect_thread(body: impl FnOnce() + Send + 'static) -> std::io::Result<()> {
#[cfg(test)]
if tests::FAIL_SPAWN.with(|fail| fail.get()) {
return Err(std::io::Error::from_raw_os_error(
rustix::io::Errno::AGAIN.raw_os_error(),
));
}
std::thread::Builder::new()
.name("rsbinder-reconnect".into())
.spawn(body)
.map(drop)
}
impl<T: FromIBinder + ?Sized + 'static> Shared<T> {
fn lock(&self) -> MutexGuard<'_, State<T>> {
self.state.lock().unwrap_or_else(|e| e.into_inner())
}
fn notify(&self) {
self.cv.notify_all();
#[cfg(feature = "tokio")]
self.watch.send_replace(());
}
fn on_death(self: &Arc<Self>, attempt: u64) {
let mut st = self.lock();
if st.phase == Phase::Closed {
return;
}
if st.in_flight == Some(attempt) {
st.in_flight_died = true;
}
if st.phase == Phase::Connected && st.conn.as_ref().is_some_and(|c| c.attempt == attempt) {
log::info!("Reconnecting: {} died; reconnecting", self.cfg);
st.phase = Phase::Stale;
self.ensure_thread(&mut st);
}
drop(st);
self.notify();
}
fn ensure_thread(self: &Arc<Self>, st: &mut State<T>) {
if st.phase != Phase::Stale || st.thread_alive {
return;
}
st.thread_alive = true;
st.attempting = Some(Instant::now());
let shared = Arc::clone(self);
if let Err(e) = spawn_reconnect_thread(move || shared.run()) {
log::error!(
"Reconnecting: cannot start the reconnect thread ({e}); a later call retries"
);
st.thread_alive = false;
st.attempting = None;
st.last_error = Some(StatusCode::Unknown);
}
}
fn begin_attempt(&self, st: &mut State<T>) -> u64 {
st.next_attempt += 1;
st.in_flight = Some(st.next_attempt);
st.in_flight_died = false;
st.next_attempt
}
fn run(self: Arc<Self>) {
self.lock().thread_id = Some(std::thread::current().id());
let mut failures: u32 = 0;
loop {
let attempt = {
let mut st = self.lock();
if st.phase == Phase::Closed {
st.thread_alive = false;
st.thread_id = None;
st.attempting = None;
drop(st);
self.notify();
return;
}
st.attempting.get_or_insert_with(Instant::now);
self.begin_attempt(&mut st)
};
let failure = match self.attempt(attempt, Mode::Thread) {
Ok(conn) => match self.finish_success(conn, true) {
None => return,
Some(f) => f,
},
Err(f) => f,
};
failures = failures.saturating_add(1);
let delay = {
let mut st = self.lock();
st.in_flight = None;
st.attempting = None;
st.last_error = Some(failure.code);
let exhausted = self
.cfg
.policy
.max_attempts
.is_some_and(|max| failures >= max);
if st.phase == Phase::Closed || failure.permanent || exhausted {
if st.phase != Phase::Closed {
log::warn!(
"Reconnecting: giving up on {} after {failures} attempt(s) ({:?})",
self.cfg,
failure.code
);
st.phase = Phase::Closed;
}
st.thread_alive = false;
st.thread_id = None;
let old = st.conn.take();
drop(st);
self.notify();
if let Some(old) = old {
self.discard(old);
}
return;
}
log::warn!(
"Reconnecting: attempt {attempt} for {} failed ({:?})",
self.cfg,
failure.code
);
self.backoff(failures)
};
self.notify();
let st = self.lock();
drop(
self.cv
.wait_timeout_while(st, delay, |st| st.phase != Phase::Closed)
.unwrap_or_else(|e| e.into_inner()),
);
}
}
fn finish_success(self: &Arc<Self>, conn: Conn<T>, thread: bool) -> Option<Failure> {
let mut st = self.lock();
if st.phase == Phase::Closed {
st.in_flight = None;
if thread {
st.thread_alive = false;
st.thread_id = None;
}
drop(st);
self.notify();
self.discard(conn);
return None;
}
if st.in_flight_died {
st.in_flight = None;
drop(st);
self.discard(conn);
if !thread {
let mut st = self.lock();
st.attempting = None;
st.last_error = Some(StatusCode::DeadObject);
self.ensure_thread(&mut st);
return None;
}
return Some(Failure::transient(StatusCode::DeadObject));
}
st.gen += 1;
st.in_flight = None;
st.attempting = None;
st.last_error = None;
st.phase = Phase::Connected;
if thread {
st.thread_alive = false;
st.thread_id = None;
}
let old = st.conn.replace(conn);
log::info!(
"Reconnecting: connected to {} (generation {})",
self.cfg,
st.gen
);
drop(st);
self.notify();
if let Some(old) = old {
self.discard(old);
}
None
}
fn backoff(&self, failures: u32) -> Duration {
let p = &self.cfg.policy;
let steps = failures.saturating_sub(1);
let base = p
.initial
.saturating_mul(p.multiplier.max(1).saturating_pow(steps))
.min(p.max);
let j = u128::from(p.jitter_percent.min(100));
if j == 0 {
return base;
}
let mut h = RandomState::new().build_hasher();
h.write_u32(failures);
let pct = 100 - j + u128::from(h.finish()) % (2 * j + 1);
let nanos = base.as_nanos() * pct / 100;
Duration::from_nanos(u64::try_from(nanos).unwrap_or(u64::MAX)).min(p.max)
}
fn discard(&self, conn: Conn<T>) {
if let Some(r) = &conn.recipient {
let _ = conn.binder.unlink_to_death_arc(r);
}
#[cfg(feature = "rpc")]
if let Some(s) = &conn.session {
if !s.is_ended() {
s.close_session();
}
}
drop(conn);
}
fn close(&self) {
let (conn, waiter) = {
let mut st = self.lock();
if st.phase == Phase::Closed {
return;
}
st.phase = Phase::Closed;
(st.conn.take(), st.waiter.take())
};
self.notify();
if let Some(w) = waiter {
w.cancel();
}
if let Some(c) = conn {
self.discard(c);
}
}
fn attempt(self: &Arc<Self>, id: u64, mode: Mode) -> std::result::Result<Conn<T>, Failure> {
let mut options = ClientOptions::default();
if let Some(f) = &self.cfg.options {
let ran = catch_unwind(AssertUnwindSafe(|| f(&mut options, &self.cfg.uri.endpoint)));
if let Err(payload) = ran {
return Err(self.user_panic(mode, payload));
}
}
let client =
open_staged(self.cfg.uri.clone(), options).map_err(|(stage, code)| Failure {
code,
permanent: stage == OpenStage::Setup,
})?;
#[cfg(feature = "rpc")]
let opened = client.session().cloned();
let binder = match self.lookup(&client, mode) {
Ok(b) => b,
Err(f) => {
#[cfg(feature = "rpc")]
end_if_live(opened.as_ref());
return Err(f);
}
};
drop(client);
#[cfg(feature = "rpc")]
let session = (*binder)
.as_any()
.downcast_ref::<crate::rpc::RpcProxy>()
.map(|p| p.session());
let watch = if !binder.is_remote() || (*binder).as_proxy().is_some_and(|p| p.handle() == 0)
{
Watch::Never
} else {
#[cfg(feature = "rpc")]
match &session {
Some(s) if !s.notices_connection_loss() => Watch::Probe,
_ => Watch::Recipient,
}
#[cfg(not(feature = "rpc"))]
Watch::Recipient
};
let mut conn_parts = Parts {
binder,
#[cfg(feature = "rpc")]
session,
recipient: None,
};
let proxy: Strong<T> = match FromIBinder::try_from(conn_parts.binder.clone()) {
Ok(p) => p,
Err(code) => {
self.end_parts(conn_parts);
return Err(Failure {
code,
permanent: code == StatusCode::BadType,
});
}
};
if watch == Watch::Recipient {
let recipient = Arc::new(Recipient {
shared: Arc::downgrade(self),
attempt: id,
});
if let Err(code) = conn_parts.binder.link_to_death_arc(&recipient) {
drop(proxy);
self.end_parts(conn_parts);
return Err(Failure::transient(code));
}
conn_parts.recipient = Some(recipient);
}
let generation = {
let st = self.lock();
if st.phase == Phase::Closed {
drop(st);
drop(proxy);
self.end_parts(conn_parts);
return Err(Failure::permanent(StatusCode::DeadObject));
}
st.gen + 1
};
if let Some(hook) = &self.cfg.on_connect {
let c = Connection {
proxy: &proxy,
generation,
#[cfg(feature = "rpc")]
session: conn_parts.session.as_ref(),
};
let failed = match catch_unwind(AssertUnwindSafe(|| hook(&c))) {
Ok(Ok(())) => None,
Ok(Err(e)) => Some(Ok(Failure {
code: e.code,
permanent: e.stop,
})),
Err(payload) => Some(Err(payload)),
};
if let Some(failed) = failed {
drop(proxy);
self.end_parts(conn_parts);
return Err(failed.unwrap_or_else(|payload| self.user_panic(mode, payload)));
}
}
Ok(Conn {
proxy,
binder: conn_parts.binder,
attempt: id,
#[cfg(feature = "rpc")]
watch,
recipient: conn_parts.recipient,
#[cfg(feature = "rpc")]
session: conn_parts.session,
})
}
fn user_panic(&self, mode: Mode, payload: Box<dyn Any + Send>) -> Failure {
if mode == Mode::Build {
resume_unwind(payload);
}
log::error!(
"Reconnecting: a user closure panicked for {}; closing",
self.cfg
);
Failure::permanent(StatusCode::Unknown)
}
fn end_parts(&self, parts: Parts<T>) {
if let Some(r) = &parts.recipient {
let _ = parts.binder.unlink_to_death_arc(r);
}
#[cfg(feature = "rpc")]
end_if_live(parts.session.as_ref());
}
fn lookup(&self, client: &Client, mode: Mode) -> std::result::Result<SIBinder, Failure> {
match &self.cfg.lookup {
Lookup::Name(name) => {
#[cfg(feature = "rpc")]
if let Some(s) = client.session() {
return s.get_service(name).map_err(lookup_failure);
}
let _ = client;
self.kernel_lookup(name, mode)
}
#[cfg(feature = "rpc")]
Lookup::Root => client
.session()
.ok_or(Failure::permanent(StatusCode::BadValue))?
.get_root()
.map_err(lookup_failure),
}
}
fn kernel_lookup(&self, name: &str, mode: Mode) -> std::result::Result<SIBinder, Failure> {
let sm = crate::hub::default().map_err(Failure::transient)?;
if let Some(b) = sm.try_get_service(name).map_err(Failure::transient)? {
return Ok(b);
}
#[cfg(all(target_os = "android", feature = "android_10"))]
if mode == Mode::Build && matches!(*sm, crate::hub::ServiceManager::Android10(_)) {
return Err(Failure::transient(StatusCode::NameNotFound));
}
if mode == Mode::Build {
let probe = crate::hub::BnServiceCallback::new_binder(Unwatched);
return match sm.register_for_notifications_status(name, &probe) {
Ok(()) => {
let _ = sm.unregister_for_notifications(name, &probe);
Err(Failure::transient(StatusCode::NameNotFound))
}
Err(status) => Err(wait_failure(WaitEnd::from_registration(status))),
};
}
let waiter = Arc::new(WaiterState::default());
{
let mut st = self.lock();
if st.phase == Phase::Closed {
return Err(Failure::permanent(StatusCode::DeadObject));
}
st.attempting = None;
st.waiter = Some(Arc::clone(&waiter));
}
self.notify();
let found = sm.wait_for_service_cancellable(name, waiter);
{
let mut st = self.lock();
st.waiter = None;
if st.phase != Phase::Closed {
st.attempting = Some(Instant::now());
}
}
found.map_err(wait_failure)
}
fn on_reconnect_thread(&self, st: &State<T>) -> bool {
st.thread_id == Some(std::thread::current().id())
}
fn acquire(self: &Arc<Self>, wait: Wait) -> std::result::Result<Snapshot<T>, AcquireError> {
let started = Instant::now();
let call_wait = self.cfg.policy.call_wait;
let mut st = self.lock();
let may_wait = !crate::is_handling_transaction() && !self.on_reconnect_thread(&st);
loop {
match st.phase {
Phase::Closed => return Err(AcquireError::Closed(st.last_error)),
Phase::Stale => {
self.ensure_thread(&mut st);
if matches!(wait, Wait::Connected(_)) && !may_wait {
return Err(AcquireError::WouldBlock);
}
if !st.thread_alive {
return Err(AcquireError::NoThread);
}
let deadline = match wait {
Wait::Never => return Err(AcquireError::NoConnection),
Wait::Attempt if !may_wait => return Err(AcquireError::NoConnection),
Wait::Attempt => match st.attempting {
Some(t0) if t0.elapsed() < call_wait => started.checked_add(call_wait),
_ => return Err(AcquireError::NoConnection),
},
Wait::Connected(deadline) => deadline,
};
st = match deadline {
None => self.cv.wait(st).unwrap_or_else(|e| e.into_inner()),
Some(d) => {
let now = Instant::now();
if now >= d {
return Err(match wait {
Wait::Connected(_) => AcquireError::TimedOut,
_ => AcquireError::NoConnection,
});
}
self.cv
.wait_timeout(st, d - now)
.unwrap_or_else(|e| e.into_inner())
.0
}
};
}
Phase::Connected => {
let conn = st.conn.as_ref().expect("Connected holds a connection");
let snap = Snapshot {
proxy: conn.proxy.clone(),
#[cfg(feature = "rpc")]
attempt: conn.attempt,
#[cfg(feature = "rpc")]
watch: conn.watch,
#[cfg(feature = "rpc")]
session: conn.session.clone(),
};
drop(st);
#[cfg(feature = "rpc")]
if self.ended_before_call(&snap) {
let attempt = snap.attempt;
drop(snap);
st = self.lock();
self.mark_stale(&mut st, attempt);
continue;
}
return Ok(snap);
}
}
}
}
#[cfg(feature = "rpc")]
fn ended_before_call(&self, snap: &Snapshot<T>) -> bool {
let Some(s) = &snap.session else {
return false;
};
let ended = s.is_ended() || (snap.watch == Watch::Probe && s.peer_closed());
if ended {
s.fail();
}
ended
}
#[cfg(feature = "rpc")]
fn mark_stale(self: &Arc<Self>, st: &mut State<T>, attempt: u64) {
if st.phase == Phase::Connected && st.conn.as_ref().is_some_and(|c| c.attempt == attempt) {
log::info!("Reconnecting: {} ended; reconnecting", self.cfg);
st.phase = Phase::Stale;
self.ensure_thread(st);
self.cv.notify_all();
}
}
fn with<R, E: From<StatusCode>>(
self: &Arc<Self>,
f: impl FnOnce(&Strong<T>) -> std::result::Result<R, E>,
) -> std::result::Result<R, E> {
let snap = self.acquire(Wait::Attempt).map_err(|e| E::from(e.dead()))?;
let result = f(&snap.proxy);
#[cfg(feature = "rpc")]
if result.is_err() && snap.session.as_ref().is_some_and(RpcSession::is_ended) {
let mut st = self.lock();
self.mark_stale(&mut st, snap.attempt);
}
result
}
fn current(self: &Arc<Self>) -> Result<Strong<T>> {
self.acquire(Wait::Never)
.map(|s| s.proxy)
.map_err(AcquireError::dead)
}
fn wait_connected(self: &Arc<Self>, timeout: Option<Duration>) -> Result<Strong<T>> {
let deadline = timeout.and_then(|t| Instant::now().checked_add(t));
self.acquire(Wait::Connected(deadline))
.map(|s| s.proxy)
.map_err(|e| match e {
AcquireError::Closed(code) => code.unwrap_or(StatusCode::DeadObject),
AcquireError::WouldBlock => StatusCode::WouldBlock,
AcquireError::TimedOut => StatusCode::TimedOut,
AcquireError::NoConnection => StatusCode::DeadObject,
AcquireError::NoThread => StatusCode::Unknown,
})
}
}
struct Parts<T: FromIBinder + ?Sized + 'static> {
binder: SIBinder,
#[cfg(feature = "rpc")]
session: Option<RpcSession>,
recipient: Option<Arc<Recipient<T>>>,
}
#[cfg(feature = "rpc")]
fn end_if_live(session: Option<&RpcSession>) {
if let Some(s) = session {
if !s.is_ended() {
s.close_session();
}
}
}
#[cfg(feature = "rpc")]
fn lookup_failure(code: StatusCode) -> Failure {
Failure {
code,
permanent: code == StatusCode::BadType,
}
}
fn wait_failure(end: WaitEnd) -> Failure {
match end {
WaitEnd::Transient(code) => Failure::transient(code),
WaitEnd::Refused(status) => {
log::error!("Reconnecting: the service manager refused to watch the name ({status})");
Failure::permanent(match status.exception_code() {
crate::ExceptionCode::Security => StatusCode::PermissionDenied,
_ => StatusCode::BadValue,
})
}
WaitEnd::Cancelled => Failure::permanent(StatusCode::DeadObject),
}
}
struct Unwatched;
impl crate::Interface for Unwatched {}
impl crate::hub::IServiceCallback for Unwatched {
fn onRegistration(&self, _name: &str, _service: &SIBinder) -> crate::status::BinderResult<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
thread_local! {
pub(super) static FAIL_SPAWN: Cell<bool> = const { Cell::new(false) };
}
struct Unreachable;
impl crate::Interface for Unreachable {}
impl FromIBinder for Unreachable {
fn try_from(_: SIBinder) -> Result<Strong<Self>> {
Err(StatusCode::BadType)
}
}
fn stale_helper() -> Reconnecting<Unreachable> {
let name = "rsbinder-reconnect-spawn-test";
Reconnecting {
shared: Arc::new(Shared {
cfg: Config {
uri: Uri::kernel().with_service(name),
lookup: Lookup::Name(name.into()),
options: None,
on_connect: None,
policy: ReconnectPolicy::default(),
},
state: Mutex::new(State::new()),
cv: Condvar::new(),
#[cfg(feature = "tokio")]
watch: tokio::sync::watch::channel(()).0,
}),
}
}
#[test]
fn a_reconnect_thread_that_cannot_spawn_ends_every_wait() {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
FAIL_SPAWN.with(|fail| fail.set(true));
let h = stale_helper();
let waited = h.wait_connected(None).err();
let current = h.current().err();
#[cfg(feature = "tokio")]
let connected = tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime")
.block_on(h.connected())
.err();
#[cfg(not(feature = "tokio"))]
let connected = waited;
let _ = tx.send((waited, current, connected));
});
let (waited, current, connected) = rx
.recv_timeout(Duration::from_secs(10))
.expect("a wait with no reconnect thread must not park");
assert_eq!(waited, Some(StatusCode::Unknown));
assert_eq!(current, Some(StatusCode::DeadObject));
assert_eq!(connected, Some(StatusCode::Unknown));
}
}