Skip to content

Commit ed338ea

Browse files
authored
feat(output): change resubscription method (#1011)
The new way for output components to resubscribe the broadcast channel distributing events is to send a oneshot sender back to the main task which will resubscribe from the broadcast sender and forward the receiver back. While this approach might seem overly complicated, it ensures there will be no lingering receivers laying around, which in turn allows for Arc::unwrap_or_clone to properly unwrap all messages when only one output is in use.
1 parent e2cbbc9 commit ed338ea

3 files changed

Lines changed: 42 additions & 44 deletions

File tree

fact/src/output/grpc.rs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use native_tls::{Certificate, Identity};
99
use openssl::{ec::EcKey, pkey::PKey};
1010
use tokio::{
1111
fs,
12-
sync::{broadcast, watch},
12+
sync::{mpsc, oneshot, watch},
1313
task::JoinHandle,
1414
time::sleep,
1515
};
@@ -21,8 +21,8 @@ use tonic::transport::Channel;
2121

2222
use crate::{
2323
config::{BackoffConfig, GrpcConfig},
24-
event::Event,
2524
metrics::EventCounter,
25+
output::EventReceiver,
2626
};
2727

2828
struct Backoff {
@@ -104,21 +104,21 @@ impl From<&BackoffConfig> for Backoff {
104104
}
105105

106106
pub struct Client {
107-
rx: broadcast::Receiver<Arc<Event>>,
107+
subscriber: mpsc::Sender<oneshot::Sender<EventReceiver>>,
108108
running: watch::Receiver<bool>,
109109
config: watch::Receiver<GrpcConfig>,
110110
metrics: EventCounter,
111111
}
112112

113113
impl Client {
114114
pub fn new(
115-
rx: broadcast::Receiver<Arc<Event>>,
115+
subscriber: mpsc::Sender<oneshot::Sender<EventReceiver>>,
116116
running: watch::Receiver<bool>,
117117
metrics: EventCounter,
118118
config: watch::Receiver<GrpcConfig>,
119119
) -> Self {
120120
Client {
121-
rx,
121+
subscriber,
122122
running,
123123
config,
124124
metrics,
@@ -230,19 +230,21 @@ impl Client {
230230
let mut client = FileActivityServiceClient::new(channel);
231231

232232
let metrics = self.metrics.clone();
233-
let rx =
234-
BroadcastStream::new(self.rx.resubscribe()).filter_map(move |event| match event {
235-
Ok(event) => {
236-
metrics.added();
237-
let event = Arc::unwrap_or_clone(event);
238-
Some(event.into())
239-
}
240-
Err(BroadcastStreamRecvError::Lagged(n)) => {
241-
warn!("gRPC stream lagged, dropped {n} events");
242-
metrics.dropped_n(n);
243-
None
244-
}
245-
});
233+
let (tx, rx) = oneshot::channel();
234+
self.subscriber.send(tx).await?;
235+
let rx = rx.await?;
236+
let rx = BroadcastStream::new(rx).filter_map(move |event| match event {
237+
Ok(event) => {
238+
metrics.added();
239+
let event = Arc::unwrap_or_clone(event);
240+
Some(event.into())
241+
}
242+
Err(BroadcastStreamRecvError::Lagged(n)) => {
243+
warn!("gRPC stream lagged, dropped {n} events");
244+
metrics.dropped_n(n);
245+
None
246+
}
247+
});
246248

247249
tokio::select! {
248250
res = client.communicate(rx) => {

fact/src/output/mod.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::{config::GrpcConfig, event::Event, metrics::OutputMetrics};
1212
mod grpc;
1313
mod stdout;
1414

15+
type EventReceiver = broadcast::Receiver<Arc<Event>>;
16+
1517
/// Starts all the output tasks.
1618
///
1719
/// Each task is responsible for managing its lifetime, handling
@@ -24,10 +26,11 @@ pub fn start(
2426
stdout_enabled: bool,
2527
) -> JoinHandle<anyhow::Result<()>> {
2628
let (broad_tx, broad_rx) = broadcast::channel(100);
29+
let (subs_req, mut subs_rx) = mpsc::channel(10);
2730
let mut run = running.clone();
2831

2932
let grpc_client = grpc::Client::new(
30-
broad_rx.resubscribe(),
33+
subs_req.clone(),
3134
running.clone(),
3235
metrics.grpc.clone(),
3336
config.clone(),
@@ -36,40 +39,42 @@ pub fn start(
3639
// JSON client will only start if explicitly enabled or no other
3740
// output is active at startup
3841
if !grpc_client.is_enabled() || stdout_enabled {
39-
stdout::Client::new(
40-
broad_rx.resubscribe(),
41-
running.clone(),
42-
metrics.stdout.clone(),
43-
)
44-
.start();
42+
stdout::Client::new(broad_rx, running.clone(), metrics.stdout.clone()).start();
4543
}
4644

4745
let mut grpc_handle = grpc_client.start();
4846

4947
tokio::spawn(async move {
5048
debug!("Starting output component...");
51-
loop {
49+
let res = loop {
5250
tokio::select! {
5351
event = rx.recv() => {
5452
let Some(event) = event else {
55-
break;
53+
break Ok(());
5654
};
5755

5856
if let Err(e) = broad_tx.send(Arc::new(event)) {
5957
warn!("Failed to forward output event: {e}");
6058
}
6159
}
60+
req = subs_rx.recv() => {
61+
let Some(req) = req else { break Ok(()); };
62+
let rx = broad_tx.subscribe();
63+
if let Err(e) = req.send(rx) {
64+
break Err(anyhow::anyhow!("Failed to subscribe worker: {e:?}"));
65+
}
66+
}
6267
res = grpc_handle.borrow_mut() => {
6368
match res {
64-
Ok(Ok(_)) => break,
69+
Ok(Ok(_)) => break Ok(()),
6570
Ok(Err(e)) => bail!("gRPC worker errored out: {e:?}"),
6671
Err(e) => bail!("gRPC task errored out: {e:?}"),
6772
}
6873
}
69-
_ = run.changed() => if !*run.borrow() { break; }
74+
_ = run.changed() => if !*run.borrow() { break Ok(()); }
7075
}
71-
}
76+
};
7277
debug!("Stopping output component...");
73-
Ok(())
78+
res
7479
})
7580
}

fact/src/output/stdout.rs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
1-
use std::sync::Arc;
2-
31
use log::{info, warn};
4-
use tokio::sync::{
5-
broadcast::{self, error::RecvError},
6-
watch,
7-
};
2+
use tokio::sync::{broadcast::error::RecvError, watch};
83

9-
use crate::{event::Event, metrics::EventCounter};
4+
use crate::{metrics::EventCounter, output::EventReceiver};
105

116
pub struct Client {
12-
rx: broadcast::Receiver<Arc<Event>>,
7+
rx: EventReceiver,
138
running: watch::Receiver<bool>,
149
metrics: EventCounter,
1510
}
1611

1712
impl Client {
18-
pub fn new(
19-
rx: broadcast::Receiver<Arc<Event>>,
20-
running: watch::Receiver<bool>,
21-
metrics: EventCounter,
22-
) -> Self {
13+
pub fn new(rx: EventReceiver, running: watch::Receiver<bool>, metrics: EventCounter) -> Self {
2314
Client {
2415
rx,
2516
running,

0 commit comments

Comments
 (0)