Skip to content

Commit 2c246a9

Browse files
committed
Evict ended telemetry streams from the client actor cache
The per-component stream task exits once it sees no receivers and reports `StreamStatus::Ended`, but the actor only removed the component from the retry map — the now-dead `broadcast::Sender` stayed cached in `component_streams`. Any later subscription for that component was handed `tx.subscribe()` on that dead sender without a new tonic stream being started, so it never received telemetry. With the telemetry trackers now exiting cleanly instead of leaking (which used to hold receivers forever and mask this), every drop-pool/rebuild cycle hit it: the rebuilt pool saw its components as permanently silent. On `Ended`, drop the cached sender so the next subscription starts a fresh stream. If a subscriber arrived in the window between the stream task's no-receivers check and the actor processing `Ended`, the entry still has receivers — restart the stream for them instead of evicting it, since the old task is already gone either way. Fixes #42. Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
1 parent 649cbc9 commit 2c246a9

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

src/client/microgrid_client_actor.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,29 @@ impl<T: MicrogridApiClient> MicrogridClientActor<T> {
8282
}
8383
Some(StreamStatus::Ended(component_id)) => {
8484
components_to_retry.remove(&component_id);
85+
match component_streams.get(&component_id) {
86+
// A subscriber arrived between the stream
87+
// task's no-receivers check and this message;
88+
// the task is gone, so restart the stream for
89+
// the new subscriber.
90+
Some(tx) if tx.receiver_count() > 0 => {
91+
let tx = tx.clone();
92+
start_electrical_component_telemetry_stream(
93+
&mut self.client,
94+
component_id,
95+
tx,
96+
stream_status_tx.clone(),
97+
)
98+
.await;
99+
}
100+
// Drop the cached sender: nothing writes to it
101+
// anymore, so handing out further subscriptions
102+
// on it would never yield data. The next
103+
// subscription starts a fresh stream instead.
104+
_ => {
105+
component_streams.remove(&component_id);
106+
}
107+
}
85108
}
86109
None => {
87110
tracing::error!("MicrogridClientActor: Stream status channel closed, exiting.");

src/client/microgrid_client_handle.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,4 +405,32 @@ mod tests {
405405
]
406406
);
407407
}
408+
409+
#[tokio::test(start_paused = true)]
410+
async fn test_resubscribing_after_all_receivers_dropped_restarts_stream() {
411+
let handle = new_client_handle();
412+
413+
let mut telemetry_rx = handle
414+
.receive_electrical_component_telemetry_stream(2)
415+
.await
416+
.unwrap();
417+
telemetry_rx.recv().await.unwrap();
418+
drop(telemetry_rx);
419+
420+
// Let the stream task notice that all receivers are gone (it checks
421+
// before each sample) and the actor process its Ended status, which
422+
// must evict the cached sender.
423+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
424+
425+
// A fresh subscription must get a live stream again, not a
426+
// subscription on the dead cached channel.
427+
let mut telemetry_rx = handle
428+
.receive_electrical_component_telemetry_stream(2)
429+
.await
430+
.unwrap();
431+
tokio::time::timeout(std::time::Duration::from_secs(10), telemetry_rx.recv())
432+
.await
433+
.expect("no telemetry after resubscribing; stale stream cache?")
434+
.unwrap();
435+
}
408436
}

0 commit comments

Comments
 (0)