Skip to main content

sea_streamer_stdio/
producer.rs

1use flume::{bounded, r#async::RecvFut, unbounded, Sender};
2use std::{collections::HashMap, fmt::Debug, future::Future, sync::Mutex};
3
4use sea_streamer_types::{
5    export::futures::FutureExt, Buffer, Message, MessageHeader, Producer as ProducerTrait, Receipt,
6    SeqNo, ShardId, SharedMessage, StreamErr, StreamKey, StreamResult, Timestamp,
7};
8
9use crate::{PartialHeader, StdioErr, StdioResult, BROADCAST, TIMESTAMP_FORMAT};
10
11lazy_static::lazy_static! {
12    static ref PRODUCERS: Mutex<Producers> = Default::default();
13    static ref THREAD: Mutex<Option<Sender<Signal>>> = Mutex::new(None);
14}
15
16#[derive(Debug, Default)]
17struct Producers {
18    sequences: HashMap<StreamKey, SeqNo>,
19}
20
21enum Signal {
22    SendRequest {
23        message: SharedMessage,
24        receipt: Sender<Receipt>,
25        loopback: bool,
26    },
27    Shutdown,
28}
29
30#[derive(Debug, Clone)]
31pub struct StdioProducer {
32    stream: Option<StreamKey>,
33    request: Sender<Signal>,
34    loopback: bool,
35}
36
37pub struct SendFuture {
38    fut: RecvFut<'static, Receipt>,
39}
40
41const ZERO: u64 = 0;
42
43pub(crate) fn init() {
44    let mut thread = THREAD.lock().expect("Failed to lock stdout thread");
45    if thread.is_none() {
46        let (sender, receiver) = unbounded();
47        let builder = std::thread::Builder::new().name("sea-streamer-stdio-stdout".into());
48        builder
49            .spawn(move || {
50                log::debug!("[{pid}] stdout thread spawned", pid = std::process::id());
51                // this thread locks the mutex forever
52                let mut producers = PRODUCERS
53                    .try_lock()
54                    .expect("Should have no other thread trying to access Producers");
55                while let Ok(signal) = receiver.recv() {
56                    match signal {
57                        Signal::SendRequest {
58                            mut message,
59                            receipt,
60                            loopback,
61                        } => {
62                            // we can time the difference from send() until now()
63                            message.touch(); // set timestamp to now
64
65                            // I believe println is atomic now, so we don't have to lock stdout
66                            // fn main() {
67                            //     std::thread::scope(|s| {
68                            //         for num in 0..100 {
69                            //             s.spawn(move || {
70                            //                 println!("Hello from thread number {}", num);
71                            //             });
72                            //         }
73                            //     });
74                            // }
75
76                            // don't print empty lines
77                            if message.message().size() != 0 {
78                                let stream_key = message.stream_key();
79                                let seq = producers.append(&stream_key);
80                                println!(
81                                    "[{timestamp} | {stream} | {seq}] {payload}",
82                                    timestamp = message
83                                        .timestamp()
84                                        .format(TIMESTAMP_FORMAT)
85                                        .expect("Timestamp format error"),
86                                    stream = stream_key,
87                                    seq = seq,
88                                    payload = message
89                                        .message()
90                                        .as_str()
91                                        .expect("Should have already checked is valid string"),
92                                );
93                                if loopback {
94                                    let payload = message.message();
95                                    super::consumer::dispatch(
96                                        PartialHeader {
97                                            timestamp: Some(message.timestamp()),
98                                            stream_key: Some(stream_key),
99                                            sequence: Some(seq),
100                                            shard_id: Some(message.shard_id()),
101                                        },
102                                        payload.into_bytes(),
103                                        0,
104                                    );
105                                }
106                            }
107                            let meta = message.take_header();
108                            // we don't care if the receipt can be delivered
109                            receipt.send(meta).ok();
110                        }
111                        Signal::Shutdown => break,
112                    }
113                }
114                log::debug!("[{pid}] stdout thread exit", pid = std::process::id());
115                {
116                    let mut thread = THREAD.lock().expect("Failed to lock stdout thread");
117                    thread.take(); // set to none
118                }
119            })
120            .unwrap();
121        thread.replace(sender);
122    }
123}
124
125pub(crate) fn shutdown() {
126    let thread = THREAD.lock().expect("Failed to lock stdout thread");
127    if let Some(sender) = thread.as_ref() {
128        sender
129            .send(Signal::Shutdown)
130            .expect("stdout thread might have been shutdown already");
131    }
132}
133
134pub(crate) fn shutdown_already() -> bool {
135    let thread = THREAD.lock().expect("Failed to lock stdout thread");
136    thread.is_none()
137}
138
139impl Producers {
140    // returns current Seq No
141    fn append(&mut self, stream: &StreamKey) -> SeqNo {
142        if let Some(val) = self.sequences.get_mut(stream) {
143            let seq = *val;
144            *val += 1;
145            seq
146        } else {
147            self.sequences.insert(stream.to_owned(), 1);
148            0
149        }
150    }
151}
152
153impl Future for SendFuture {
154    type Output = StreamResult<MessageHeader, StdioErr>;
155
156    fn poll(
157        mut self: std::pin::Pin<&mut Self>,
158        cx: &mut std::task::Context<'_>,
159    ) -> std::task::Poll<Self::Output> {
160        match self.fut.poll_unpin(cx) {
161            std::task::Poll::Ready(res) => std::task::Poll::Ready(match res {
162                Ok(res) => Ok(res),
163                Err(err) => Err(StreamErr::Backend(StdioErr::RecvError(err))),
164            }),
165            std::task::Poll::Pending => std::task::Poll::Pending,
166        }
167    }
168}
169
170impl Debug for SendFuture {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        f.debug_struct("SendFuture").finish()
173    }
174}
175
176impl ProducerTrait for StdioProducer {
177    type Error = StdioErr;
178    type SendFuture = SendFuture;
179
180    fn send_to<S: Buffer>(&self, stream: &StreamKey, payload: S) -> StdioResult<Self::SendFuture> {
181        let payload = payload.as_str().map_err(StreamErr::Utf8Error)?.to_owned();
182        // basically using this as oneshot
183        let (sender, receiver) = bounded(1);
184        let size = payload.len();
185        self.request
186            .send(Signal::SendRequest {
187                message: SharedMessage::new(
188                    MessageHeader::new(
189                        stream.to_owned(),
190                        ShardId::new(ZERO),
191                        ZERO,
192                        Timestamp::now_utc(),
193                    ),
194                    payload.into_bytes(),
195                    0,
196                    size,
197                ),
198                receipt: sender,
199                loopback: self.loopback,
200            })
201            .map_err(|_| StreamErr::Backend(StdioErr::Disconnected))?;
202        Ok(SendFuture {
203            fut: receiver.into_recv_async(),
204        })
205    }
206
207    #[inline]
208    async fn end(mut self) -> StdioResult<()> {
209        self.flush().await
210    }
211
212    #[inline]
213    async fn flush(&mut self) -> StdioResult<()> {
214        // the trick here is to send an empty message (that will be dropped) to the stdout thread
215        // and wait for the receipt. By the time it returns a receipt, everything before should
216        // have already been sent
217        self.send_to(&StreamKey::new(BROADCAST)?, "")?.await?;
218        Ok(())
219    }
220
221    fn anchor(&mut self, stream: StreamKey) -> StdioResult<()> {
222        if self.stream.is_none() {
223            self.stream = Some(stream);
224            Ok(())
225        } else {
226            Err(StreamErr::AlreadyAnchored)
227        }
228    }
229
230    fn anchored(&self) -> StdioResult<&StreamKey> {
231        if let Some(stream) = &self.stream {
232            Ok(stream)
233        } else {
234            Err(StreamErr::NotAnchored)
235        }
236    }
237}
238
239impl StdioProducer {
240    #[allow(clippy::new_without_default)]
241    pub fn new() -> Self {
242        Self::new_with(false)
243    }
244
245    pub(crate) fn new_with(loopback: bool) -> Self {
246        init();
247        let request = {
248            let thread = THREAD.lock().expect("Failed to lock stdout thread");
249            thread.as_ref().expect("Should have initialized").to_owned()
250        };
251        Self {
252            stream: None,
253            request,
254            loopback,
255        }
256    }
257}