use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::SystemTime;
use tokio::sync::broadcast;
use crate::error::AsynStatus;
use crate::interfaces::InterfaceType;
use crate::param::ParamValue;
#[derive(Debug, Clone, Default)]
pub struct InterruptFilter {
pub reason: Option<usize>,
pub addr: Option<i32>,
pub uint32_mask: Option<u32>,
pub iface: Option<InterfaceType>,
}
impl InterruptFilter {
fn matches(&self, iv: &InterruptValue) -> bool {
if let Some(r) = self.reason {
if iv.reason != r {
return false;
}
}
if let Some(a) = self.addr {
if iv.addr != a {
return false;
}
}
if let Some(m) = self.uint32_mask {
if iv.uint32_changed_mask & m == 0 {
return false;
}
}
if let (Some(want), Some(got)) = (self.iface, iv.iface) {
if want != got {
return false;
}
}
true
}
fn accepts_octet(&self, rule: OctetFanOut) -> bool {
if let OctetFanOut::ByAddr(addr) = rule {
if let Some(a) = self.addr {
if addr != a {
return false;
}
}
}
!matches!(self.iface, Some(want) if want != InterfaceType::Octet)
}
fn accepts(&self, reason: usize, addr: i32, iface: InterfaceType) -> bool {
if let Some(r) = self.reason {
if reason != r {
return false;
}
}
if let Some(a) = self.addr {
if addr != a {
return false;
}
}
if let Some(want) = self.iface {
if want != iface {
return false;
}
}
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OctetFanOut {
ByAddr(i32),
EveryUser,
}
#[derive(Debug, Clone)]
pub struct InterruptValue {
pub reason: usize,
pub addr: i32,
pub value: ParamValue,
pub timestamp: SystemTime,
pub uint32_changed_mask: u32,
pub aux_status: AsynStatus,
pub alarm_status: u16,
pub alarm_severity: u16,
pub iface: Option<InterfaceType>,
}
impl Default for InterruptValue {
fn default() -> Self {
Self {
reason: 0,
addr: 0,
value: ParamValue::Undefined,
timestamp: SystemTime::UNIX_EPOCH,
uint32_changed_mask: 0,
aux_status: AsynStatus::Success,
alarm_status: 0,
alarm_severity: 0,
iface: None,
}
}
}
struct SubscriptionMailbox {
filter: InterruptFilter,
latest: parking_lot::Mutex<Option<InterruptValue>>,
wakeup: tokio::sync::Notify,
active: AtomicBool,
}
struct SyncCallback {
filter: InterruptFilter,
callback: Box<dyn Fn(&InterruptValue) + Send + Sync>,
active: AtomicBool,
}
pub struct SyncCallbackSubscription {
callback: Arc<SyncCallback>,
state: Arc<InterruptSharedState>,
}
impl Drop for SyncCallbackSubscription {
fn drop(&mut self) {
self.callback.active.store(false, Ordering::Release);
self.state
.sync_callbacks
.lock()
.retain(|c| c.active.load(Ordering::Relaxed));
}
}
pub struct InterruptReceiver {
mailbox: Arc<SubscriptionMailbox>,
}
impl InterruptReceiver {
pub async fn recv(&mut self) -> Option<InterruptValue> {
loop {
let notified = self.mailbox.wakeup.notified();
if let Some(value) = self.mailbox.latest.lock().take() {
return Some(value);
}
if !self.mailbox.active.load(Ordering::Acquire) {
return None;
}
notified.await;
}
}
}
pub struct InterruptSubscription {
mailbox: Arc<SubscriptionMailbox>,
state: Arc<InterruptSharedState>,
}
impl Drop for InterruptSubscription {
fn drop(&mut self) {
self.mailbox.active.store(false, Ordering::Release);
self.mailbox.wakeup.notify_one();
self.state
.mailboxes
.lock()
.retain(|s| s.active.load(Ordering::Relaxed));
}
}
pub struct InterruptSharedState {
async_tx: broadcast::Sender<InterruptValue>,
mailboxes: parking_lot::Mutex<Vec<Arc<SubscriptionMailbox>>>,
sync_callbacks: parking_lot::Mutex<Vec<Arc<SyncCallback>>>,
notify_count: AtomicU64,
coalesce_count: AtomicU64,
}
pub struct InterruptManager {
state: Arc<InterruptSharedState>,
}
impl InterruptManager {
pub fn new(async_capacity: usize) -> Self {
let (async_tx, _) = broadcast::channel(async_capacity);
Self {
state: Arc::new(InterruptSharedState {
async_tx,
mailboxes: parking_lot::Mutex::new(Vec::new()),
sync_callbacks: parking_lot::Mutex::new(Vec::new()),
notify_count: AtomicU64::new(0),
coalesce_count: AtomicU64::new(0),
}),
}
}
pub fn from_shared_state(state: Arc<InterruptSharedState>) -> Self {
Self { state }
}
pub fn shared_state(&self) -> Arc<InterruptSharedState> {
self.state.clone()
}
pub fn from_broadcast_sender(sender: broadcast::Sender<InterruptValue>) -> Self {
Self {
state: Arc::new(InterruptSharedState {
async_tx: sender,
mailboxes: parking_lot::Mutex::new(Vec::new()),
sync_callbacks: parking_lot::Mutex::new(Vec::new()),
notify_count: AtomicU64::new(0),
coalesce_count: AtomicU64::new(0),
}),
}
}
pub fn subscribe_async(&self) -> broadcast::Receiver<InterruptValue> {
self.state.async_tx.subscribe()
}
pub fn broadcast_sender(&self) -> broadcast::Sender<InterruptValue> {
self.state.async_tx.clone()
}
pub fn register_sync_callback<F>(
&self,
filter: InterruptFilter,
callback: F,
) -> SyncCallbackSubscription
where
F: Fn(&InterruptValue) + Send + Sync + 'static,
{
let cb = Arc::new(SyncCallback {
filter,
callback: Box::new(callback),
active: AtomicBool::new(true),
});
self.state.sync_callbacks.lock().push(cb.clone());
SyncCallbackSubscription {
callback: cb,
state: self.state.clone(),
}
}
pub fn notify(&self, value: InterruptValue) {
self.dispatch(value, |f, v| f.matches(v));
}
pub fn notify_octet(&self, rule: OctetFanOut, value: InterruptValue) {
self.dispatch(value, |f, _| f.accepts_octet(rule));
}
fn dispatch(
&self,
value: InterruptValue,
pass: impl Fn(&InterruptFilter, &InterruptValue) -> bool,
) {
self.state.notify_count.fetch_add(1, Ordering::Relaxed);
let sync_cbs: Vec<Arc<SyncCallback>> = {
let guard = self.state.sync_callbacks.lock();
guard
.iter()
.filter(|c| c.active.load(Ordering::Relaxed))
.cloned()
.collect()
};
for cb in &sync_cbs {
if pass(&cb.filter, &value) {
(cb.callback)(&value);
}
}
let subs = self.state.mailboxes.lock();
for sub in subs.iter() {
if !sub.active.load(Ordering::Relaxed) {
continue;
}
if !pass(&sub.filter, &value) {
continue;
}
let mut slot = sub.latest.lock();
if slot.is_some() {
self.state.coalesce_count.fetch_add(1, Ordering::Relaxed);
}
*slot = Some(value.clone());
drop(slot);
sub.wakeup.notify_one();
}
drop(subs);
let _ = self.state.async_tx.send(value);
}
pub fn register_interrupt_user(
&self,
filter: InterruptFilter,
) -> (InterruptSubscription, InterruptReceiver) {
let mailbox = Arc::new(SubscriptionMailbox {
filter,
latest: parking_lot::Mutex::new(None),
wakeup: tokio::sync::Notify::new(),
active: AtomicBool::new(true),
});
self.state.mailboxes.lock().push(mailbox.clone());
(
InterruptSubscription {
mailbox: mailbox.clone(),
state: self.state.clone(),
},
InterruptReceiver { mailbox },
)
}
pub fn clients(&self) -> Vec<InterruptFilter> {
self.state
.mailboxes
.lock()
.iter()
.filter(|m| m.active.load(Ordering::Acquire))
.map(|m| m.filter.clone())
.collect()
}
pub fn has_subscriber(&self, reason: usize, addr: i32, iface: InterfaceType) -> bool {
self.state
.mailboxes
.lock()
.iter()
.any(|s| s.active.load(Ordering::Relaxed) && s.filter.accepts(reason, addr, iface))
}
pub fn subscribed_bindings(&self) -> Vec<(usize, i32)> {
let concrete = |f: &InterruptFilter| match (f.reason, f.addr) {
(Some(r), Some(a)) => Some((r, a)),
_ => None,
};
let mailbox_pairs: Vec<(usize, i32)> = {
let g = self.state.mailboxes.lock();
g.iter()
.filter(|s| s.active.load(Ordering::Relaxed))
.filter_map(|s| concrete(&s.filter))
.collect()
};
let sync_pairs: Vec<(usize, i32)> = {
let g = self.state.sync_callbacks.lock();
g.iter()
.filter(|c| c.active.load(Ordering::Relaxed))
.filter_map(|c| concrete(&c.filter))
.collect()
};
let mut out: Vec<(usize, i32)> = Vec::new();
for p in mailbox_pairs.into_iter().chain(sync_pairs) {
if !out.contains(&p) {
out.push(p);
}
}
out
}
pub fn notify_count(&self) -> u64 {
self.state.notify_count.load(Ordering::Relaxed)
}
pub fn coalesce_count(&self) -> u64 {
self.state.coalesce_count.load(Ordering::Relaxed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_async_subscribe_receive() {
let im = InterruptManager::new(16);
let mut rx = im.subscribe_async();
im.notify(InterruptValue {
reason: 1,
addr: 0,
value: ParamValue::Float64(3.14),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
let v = rx.recv().await.unwrap();
assert_eq!(v.reason, 1);
}
#[tokio::test]
async fn test_async_multiple_subscribers() {
let im = InterruptManager::new(16);
let mut rx1 = im.subscribe_async();
let mut rx2 = im.subscribe_async();
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(99),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
let v1 = rx1.recv().await.unwrap();
let v2 = rx2.recv().await.unwrap();
assert_eq!(v1.reason, 0);
assert_eq!(v2.reason, 0);
}
#[tokio::test]
async fn test_register_interrupt_user_filter_by_reason() {
let im = InterruptManager::new(16);
let (_sub, mut rx) = im.register_interrupt_user(InterruptFilter {
reason: Some(1),
addr: None,
..Default::default()
});
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(10),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
im.notify(InterruptValue {
reason: 1,
addr: 0,
value: ParamValue::Int32(20),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
let v = tokio::time::timeout(std::time::Duration::from_millis(100), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(v.reason, 1);
if let ParamValue::Int32(n) = v.value {
assert_eq!(n, 20);
} else {
panic!("expected Int32");
}
}
#[tokio::test]
async fn test_register_interrupt_user_filter_by_addr() {
let im = InterruptManager::new(16);
let (_sub, mut rx) = im.register_interrupt_user(InterruptFilter {
reason: None,
addr: Some(3),
..Default::default()
});
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(1),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
im.notify(InterruptValue {
reason: 0,
addr: 3,
value: ParamValue::Int32(2),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
let v = tokio::time::timeout(std::time::Duration::from_millis(100), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(v.addr, 3);
}
#[tokio::test]
async fn test_register_interrupt_user_no_filter() {
let im = InterruptManager::new(16);
let (_sub, mut rx) = im.register_interrupt_user(InterruptFilter::default());
im.notify(InterruptValue {
reason: 5,
addr: 2,
value: ParamValue::Float64(1.5),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
let v = tokio::time::timeout(std::time::Duration::from_millis(100), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(v.reason, 5);
assert_eq!(v.addr, 2);
}
#[tokio::test]
async fn test_register_interrupt_user_drop_unsubscribes() {
let im = InterruptManager::new(16);
let (sub, mut rx) = im.register_interrupt_user(InterruptFilter::default());
drop(sub);
let result = tokio::time::timeout(std::time::Duration::from_millis(50), rx.recv()).await;
match result {
Ok(None) => {} Err(_) => {} Ok(Some(_)) => panic!("should not receive after unsubscribe"),
}
}
#[tokio::test]
async fn test_register_interrupt_user_multiple_subscribers() {
let im = InterruptManager::new(16);
let (_sub1, mut rx1) = im.register_interrupt_user(InterruptFilter {
reason: Some(0),
addr: None,
..Default::default()
});
let (_sub2, mut rx2) = im.register_interrupt_user(InterruptFilter {
reason: Some(1),
addr: None,
..Default::default()
});
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(10),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
im.notify(InterruptValue {
reason: 1,
addr: 0,
value: ParamValue::Int32(20),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
let v1 = tokio::time::timeout(std::time::Duration::from_millis(100), rx1.recv())
.await
.unwrap()
.unwrap();
assert_eq!(v1.reason, 0);
let v2 = tokio::time::timeout(std::time::Duration::from_millis(100), rx2.recv())
.await
.unwrap()
.unwrap();
assert_eq!(v2.reason, 1);
}
#[test]
fn test_notify_no_subscribers_no_panic() {
let im = InterruptManager::new(16);
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(1),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
}
#[tokio::test]
async fn test_coalescing() {
let im = InterruptManager::new(16);
let (_sub, mut rx) = im.register_interrupt_user(InterruptFilter {
reason: Some(0),
..Default::default()
});
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(1),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(2),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(3),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
let v = tokio::time::timeout(std::time::Duration::from_millis(100), rx.recv())
.await
.unwrap()
.unwrap();
if let ParamValue::Int32(n) = v.value {
assert_eq!(n, 3);
} else {
panic!("expected Int32");
}
assert_eq!(im.coalesce_count(), 2);
}
#[tokio::test]
async fn test_shared_state_between_managers() {
let im1 = InterruptManager::new(16);
let shared = im1.shared_state();
let im2 = InterruptManager::from_shared_state(shared);
let (_sub, mut rx) = im2.register_interrupt_user(InterruptFilter {
reason: Some(0),
..Default::default()
});
im1.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(42),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
let v = tokio::time::timeout(std::time::Duration::from_millis(100), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(v.reason, 0);
if let ParamValue::Int32(n) = v.value {
assert_eq!(n, 42);
} else {
panic!("expected Int32");
}
}
#[tokio::test]
async fn mailbox_burst_coalesces_no_drop() {
let im = InterruptManager::new(16);
let (_sub, mut rx) = im.register_interrupt_user(InterruptFilter::default());
let n: usize = 1000;
for i in 0..n {
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(i as i32),
timestamp: SystemTime::now(),
uint32_changed_mask: 0,
..Default::default()
});
}
let v = tokio::time::timeout(std::time::Duration::from_millis(100), rx.recv())
.await
.expect("recv must complete within 100ms")
.expect("active subscription must yield a value");
match v.value {
ParamValue::Int32(x) => {
assert_eq!(x, (n - 1) as i32, "must coalesce to the latest event")
}
other => panic!("expected Int32, got {other:?}"),
}
assert_eq!(im.notify_count(), n as u64);
assert_eq!(im.coalesce_count(), (n - 1) as u64);
}
#[tokio::test]
async fn notify_routes_typed_values_per_interface() {
use crate::interfaces::InterfaceType;
let im = InterruptManager::new(16);
let (_si, mut rx_int) = im.register_interrupt_user(InterruptFilter {
reason: Some(0),
addr: Some(0),
iface: Some(InterfaceType::Int32),
..Default::default()
});
let (_su, mut rx_uint) = im.register_interrupt_user(InterruptFilter {
reason: Some(0),
addr: Some(0),
iface: Some(InterfaceType::UInt32Digital),
uint32_mask: Some(0xFF),
});
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(7),
iface: Some(InterfaceType::Int32),
..Default::default()
});
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::UInt32Digital(0xAB),
iface: Some(InterfaceType::UInt32Digital),
uint32_changed_mask: !0,
..Default::default()
});
let dur = std::time::Duration::from_millis(100);
let vi = tokio::time::timeout(dur, rx_int.recv())
.await
.unwrap()
.unwrap();
assert!(
matches!(vi.value, ParamValue::Int32(7)),
"got {:?}",
vi.value
);
let vu = tokio::time::timeout(dur, rx_uint.recv())
.await
.unwrap()
.unwrap();
assert!(
matches!(vu.value, ParamValue::UInt32Digital(0xAB)),
"got {:?}",
vu.value
);
let short = std::time::Duration::from_millis(30);
assert!(
tokio::time::timeout(short, rx_int.recv()).await.is_err(),
"Int32 subscriber must not receive the UInt32 fire"
);
assert!(
tokio::time::timeout(short, rx_uint.recv()).await.is_err(),
"UInt32 subscriber must not receive the Int32 fire"
);
im.notify(InterruptValue {
reason: 0,
addr: 0,
value: ParamValue::Int32(99),
iface: None,
..Default::default()
});
let vi = tokio::time::timeout(dur, rx_int.recv())
.await
.unwrap()
.unwrap();
assert!(matches!(vi.value, ParamValue::Int32(99)));
}
#[tokio::test]
async fn octet_fan_out_ignores_reason_and_honours_the_emitter_rule() {
use crate::interfaces::InterfaceType;
let im = InterruptManager::new(16);
let octet_sub = |reason: usize, addr: i32| {
im.register_interrupt_user(InterruptFilter {
reason: Some(reason),
addr: Some(addr),
iface: Some(InterfaceType::Octet),
..Default::default()
})
};
let (_s_other_reason, mut rx_other_reason) = octet_sub(7, 0);
let (_s_same, mut rx_same) = octet_sub(0, 0);
let (_s_other_addr, mut rx_other_addr) = octet_sub(0, 3);
let (_s_int32, mut rx_int32) = im.register_interrupt_user(InterruptFilter {
reason: Some(0),
addr: Some(0),
iface: Some(InterfaceType::Int32),
..Default::default()
});
let octet = |addr: i32, s: &str| InterruptValue {
reason: 0,
addr,
value: ParamValue::Octet(s.to_string()),
iface: Some(InterfaceType::Octet),
..Default::default()
};
let dur = std::time::Duration::from_millis(100);
let short = std::time::Duration::from_millis(30);
im.notify_octet(OctetFanOut::ByAddr(0), octet(0, "read"));
for rx in [&mut rx_same, &mut rx_other_reason] {
let v = tokio::time::timeout(dur, rx.recv()).await.unwrap().unwrap();
assert!(matches!(v.value, ParamValue::Octet(ref s) if s == "read"));
}
assert!(
tokio::time::timeout(short, rx_other_addr.recv())
.await
.is_err(),
"ByAddr must not reach a subscriber on another addr"
);
assert!(
tokio::time::timeout(short, rx_int32.recv()).await.is_err(),
"an octet value must not reach an Int32 subscriber"
);
im.notify_octet(OctetFanOut::EveryUser, octet(3, "srv:3"));
for rx in [&mut rx_same, &mut rx_other_reason, &mut rx_other_addr] {
let v = tokio::time::timeout(dur, rx.recv()).await.unwrap().unwrap();
assert!(matches!(v.value, ParamValue::Octet(ref s) if s == "srv:3"));
}
assert!(
tokio::time::timeout(short, rx_int32.recv()).await.is_err(),
"EveryUser is every OCTET user, not every subscriber"
);
}
#[tokio::test]
async fn has_subscriber_reports_presence_per_iface() {
use crate::interfaces::InterfaceType;
let im = InterruptManager::new(16);
assert!(!im.has_subscriber(0, 0, InterfaceType::Int32Array));
let (sub, _rx) = im.register_interrupt_user(InterruptFilter {
reason: Some(0),
addr: Some(0),
iface: Some(InterfaceType::Int32Array),
..Default::default()
});
assert!(im.has_subscriber(0, 0, InterfaceType::Int32Array));
assert!(!im.has_subscriber(0, 0, InterfaceType::Float64Array));
assert!(!im.has_subscriber(0, 1, InterfaceType::Int32Array));
assert!(!im.has_subscriber(1, 0, InterfaceType::Int32Array));
let (sub2, _rx2) = im.register_interrupt_user(InterruptFilter {
reason: Some(5),
..Default::default()
});
assert!(im.has_subscriber(5, 99, InterfaceType::Float64Array));
drop(sub);
assert!(!im.has_subscriber(0, 0, InterfaceType::Int32Array));
drop(sub2);
assert!(!im.has_subscriber(5, 99, InterfaceType::Float64Array));
}
#[tokio::test]
async fn subscribed_bindings_enumerates_distinct_concrete_pairs() {
use crate::interfaces::InterfaceType;
let im = InterruptManager::new(16);
assert!(im.subscribed_bindings().is_empty());
let (s_a, _r0) = im.register_interrupt_user(InterruptFilter {
reason: Some(2),
addr: Some(7),
iface: Some(InterfaceType::Int32Array),
..Default::default()
});
let (s_b, _r1) = im.register_interrupt_user(InterruptFilter {
reason: Some(2),
addr: Some(7),
iface: Some(InterfaceType::Float64Array),
..Default::default()
});
let (s_other, _r2) = im.register_interrupt_user(InterruptFilter {
reason: Some(2),
addr: Some(8),
iface: Some(InterfaceType::Int32Array),
..Default::default()
});
let mut got = im.subscribed_bindings();
got.sort();
assert_eq!(got, vec![(2, 7), (2, 8)]);
let (s_wild, _r3) = im.register_interrupt_user(InterruptFilter {
reason: Some(9),
..Default::default()
});
let mut got = im.subscribed_bindings();
got.sort();
assert_eq!(got, vec![(2, 7), (2, 8)]);
let sc_new = im.register_sync_callback(
InterruptFilter {
reason: Some(3),
addr: Some(1),
..Default::default()
},
|_| {},
);
let sc_dup = im.register_sync_callback(
InterruptFilter {
reason: Some(2),
addr: Some(8),
..Default::default()
},
|_| {},
);
let mut got = im.subscribed_bindings();
got.sort();
assert_eq!(got, vec![(2, 7), (2, 8), (3, 1)]);
drop(sc_new);
let mut got = im.subscribed_bindings();
got.sort();
assert_eq!(got, vec![(2, 7), (2, 8)]);
drop(sc_dup);
let mut got = im.subscribed_bindings();
got.sort();
assert_eq!(got, vec![(2, 7), (2, 8)]);
drop(s_a);
let mut got = im.subscribed_bindings();
got.sort();
assert_eq!(got, vec![(2, 7), (2, 8)]);
drop(s_b);
let mut got = im.subscribed_bindings();
got.sort();
assert_eq!(got, vec![(2, 8)]);
drop(s_other);
drop(s_wild);
assert!(im.subscribed_bindings().is_empty());
}
}