use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use crate::binder::{DeathRecipient, FromIBinder, Interface, SIBinder, Strong, WIBinder};
pub struct Rewrap<I: FromIBinder + ?Sized> {
make_local: Box<dyn Fn(Strong<I>) -> Strong<I> + Send + Sync>,
entries: Arc<Mutex<HashMap<usize, Entry>>>,
}
struct Entry {
remote: WIBinder,
local: WIBinder,
reaper: Arc<Reaper>,
}
impl Entry {
fn live(&self) -> Option<(SIBinder, SIBinder)> {
Some((self.remote.upgrade().ok()?, self.local.upgrade().ok()?))
}
fn unlink(&self) {
if let Ok(remote) = self.remote.upgrade() {
let _ = remote.unlink_to_death_arc(&self.reaper);
}
}
}
fn take_removed(entries: &mut HashMap<usize, Entry>, insert: Option<(usize, Entry)>) -> Vec<Entry> {
let keys: Vec<usize> = entries
.iter()
.filter(|(_, e)| e.live().is_none())
.map(|(k, _)| *k)
.collect();
let mut removed: Vec<Entry> = keys.iter().filter_map(|k| entries.remove(k)).collect();
if let Some((key, entry)) = insert {
removed.extend(entries.insert(key, entry));
}
removed
}
fn unlink_all(dead: &[Entry], resume: bool) {
let mut first: Option<Box<dyn std::any::Any + Send>> = None;
for e in dead {
if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| e.unlink()))
{
log::error!("Rewrap: unlink panicked; a death link is left registered on the remote");
if first.is_none() {
first = Some(payload);
}
}
}
if let Some(payload) = first {
if resume {
std::panic::resume_unwind(payload);
}
}
}
struct Reaper {
table: std::sync::Weak<Mutex<HashMap<usize, Entry>>>,
key: usize,
}
impl DeathRecipient for Reaper {
fn binder_died(&self, who: &WIBinder) {
let Some(entries) = self.table.upgrade() else {
return;
};
let mut entries = entries.lock().expect("rewrap table poisoned");
if entries.get(&self.key).is_some_and(|e| e.remote == *who) {
entries.remove(&self.key);
}
}
}
impl<I: FromIBinder + ?Sized> Rewrap<I> {
pub fn new(make_local: impl Fn(Strong<I>) -> Strong<I> + Send + Sync + 'static) -> Self {
Self {
make_local: Box::new(make_local),
entries: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn wrap(&self, remote: &Strong<I>) -> Strong<I> {
let remote_binder = remote.as_binder();
let key = key_of(&remote_binder);
if let Some(hit) = self.lookup(key, &remote_binder) {
return hit;
}
let local = (self.make_local)(remote.clone());
let reaper = Arc::new(Reaper {
table: Arc::downgrade(&self.entries),
key,
});
let _ = remote_binder.link_to_death_arc(&reaper);
let mut entries = self.entries.lock().expect("rewrap table poisoned");
if let Some((r, l)) = entries.get(&key).and_then(Entry::live) {
if r == remote_binder {
if let Ok(existing) = <I as FromIBinder>::try_from(l) {
drop(entries);
let _ = remote_binder.unlink_to_death_arc(&reaper);
return existing;
}
}
}
let dead = take_removed(
&mut entries,
Some((
key,
Entry {
remote: SIBinder::downgrade(&remote_binder),
local: SIBinder::downgrade(&local.as_binder()),
reaper,
},
)),
);
drop(entries);
unlink_all(&dead, true);
local
}
pub fn purge_dead(&self) -> usize {
let dead = {
let mut entries = self.entries.lock().expect("rewrap table poisoned");
take_removed(&mut entries, None)
};
unlink_all(&dead, true);
dead.len()
}
pub fn len(&self) -> usize {
self.entries.lock().expect("rewrap table poisoned").len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn lookup(&self, key: usize, remote_binder: &SIBinder) -> Option<Strong<I>> {
let entries = self.entries.lock().expect("rewrap table poisoned");
let (r, l) = entries.get(&key).and_then(Entry::live)?;
(&r == remote_binder)
.then(|| <I as FromIBinder>::try_from(l).ok())
.flatten()
}
}
impl<I: FromIBinder + ?Sized> Drop for Rewrap<I> {
fn drop(&mut self) {
let dead: Vec<Entry> = {
let mut entries = self
.entries
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
entries.drain().map(|(_, e)| e).collect()
};
unlink_all(&dead, false);
}
}
impl<I: FromIBinder + ?Sized> std::fmt::Debug for Rewrap<I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Rewrap").field("len", &self.len()).finish()
}
}
fn key_of(binder: &SIBinder) -> usize {
Arc::as_ptr(binder.as_arc()) as *const () as usize
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binder::{IBinder, Stability};
use std::mem::ManuallyDrop;
use std::sync::atomic::{AtomicUsize, Ordering};
struct Unlinkable {
panics: bool,
unlinked: Arc<AtomicUsize>,
}
impl IBinder for Unlinkable {
fn link_to_death(&self, _: std::sync::Weak<dyn DeathRecipient>) -> crate::Result<()> {
Ok(())
}
fn unlink_to_death(&self, _: std::sync::Weak<dyn DeathRecipient>) -> crate::Result<()> {
assert!(!self.panics, "injected unlink panic");
self.unlinked.fetch_add(1, Ordering::SeqCst);
Ok(())
}
fn ping_binder(&self) -> crate::Result<()> {
Ok(())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_transactable(&self) -> Option<&dyn crate::Transactable> {
None
}
fn descriptor(&self) -> &str {
"rsbinder.test.IUnlinkable"
}
fn is_remote(&self) -> bool {
true
}
fn stability(&self) -> Stability {
Stability::default()
}
fn inc_strong(&self, _: &SIBinder) -> crate::Result<()> {
Ok(())
}
fn attempt_inc_strong(&self) -> bool {
true
}
fn dec_strong(&self, _: Option<ManuallyDrop<SIBinder>>) -> crate::Result<()> {
Ok(())
}
fn inc_weak(&self, _: &WIBinder) -> crate::Result<()> {
Ok(())
}
fn dec_weak(&self) -> crate::Result<()> {
Ok(())
}
}
fn entry(panics: bool, unlinked: &Arc<AtomicUsize>) -> (SIBinder, Entry) {
let b = SIBinder::new(Arc::new(Unlinkable {
panics,
unlinked: unlinked.clone(),
}))
.expect("SIBinder::new");
let e = Entry {
remote: SIBinder::downgrade(&b),
local: SIBinder::downgrade(&b),
reaper: Arc::new(Reaper {
table: std::sync::Weak::new(),
key: 0,
}),
};
(b, e)
}
#[test]
fn unlink_all_finishes_the_list_after_a_panic() {
let unlinked = Arc::new(AtomicUsize::new(0));
let (_k0, e0) = entry(false, &unlinked);
let (_k1, e1) = entry(true, &unlinked); let (_k2, e2) = entry(false, &unlinked);
let dead = vec![e0, e1, e2];
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let out =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unlink_all(&dead, true)));
std::panic::set_hook(prev);
assert!(out.is_err(), "resume=true must re-raise the entry's panic");
assert_eq!(
unlinked.load(Ordering::SeqCst),
2,
"the entries either side of the panicking one must still be unlinked"
);
}
#[test]
fn unlink_all_swallows_the_panic_for_drop() {
let unlinked = Arc::new(AtomicUsize::new(0));
let (_k0, e0) = entry(true, &unlinked); let (_k1, e1) = entry(false, &unlinked);
let dead = vec![e0, e1];
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let out =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unlink_all(&dead, false)));
std::panic::set_hook(prev);
assert!(out.is_ok(), "resume=false must not propagate");
assert_eq!(unlinked.load(Ordering::SeqCst), 1);
}
}