objectstore_inventory_tracker/
producer.rs1use std::future::Future;
10use std::sync::Arc;
11use std::time::Duration;
12
13pub trait Producer {
15 type Error;
17
18 fn send(&self, key: &[u8], payload: Vec<u8>) -> Result<(), Self::Error>;
25
26 fn join_blocking(&self, timeout: Duration) -> Result<(), Self::Error>;
30
31 fn join(
37 &self,
38 timeout: Duration,
39 ) -> impl Future<Output = Result<(), Self::Error>> + Send + use<Self>
40 where
41 Self: Sized + Clone + Send + Sync + 'static,
42 Self::Error: Send + 'static,
43 {
44 let producer = self.clone();
45 async move {
46 if tokio::runtime::Handle::try_current().is_err() {
48 return producer.join_blocking(timeout);
49 }
50
51 match tokio::task::spawn_blocking(move || producer.join_blocking(timeout)).await {
52 Ok(result) => result,
53 Err(_) => Ok(()),
55 }
56 }
57 }
58
59 fn shared(self) -> SharedProducer
69 where
70 Self: Sized + Send + Sync + 'static,
71 Self::Error: std::error::Error + Send + Sync + 'static,
72 {
73 Arc::new(BoxErrors(self))
74 }
75}
76
77impl<P: Producer + ?Sized> Producer for Box<P> {
78 type Error = P::Error;
79
80 fn send(&self, key: &[u8], payload: Vec<u8>) -> Result<(), Self::Error> {
81 (**self).send(key, payload)
82 }
83
84 fn join_blocking(&self, timeout: Duration) -> Result<(), Self::Error> {
85 (**self).join_blocking(timeout)
86 }
87}
88
89impl<P: Producer + ?Sized> Producer for Arc<P> {
90 type Error = P::Error;
91
92 fn send(&self, key: &[u8], payload: Vec<u8>) -> Result<(), Self::Error> {
93 (**self).send(key, payload)
94 }
95
96 fn join_blocking(&self, timeout: Duration) -> Result<(), Self::Error> {
97 (**self).join_blocking(timeout)
98 }
99}
100
101pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
103
104pub type SharedProducer = Arc<dyn Producer<Error = BoxError> + Send + Sync>;
109
110struct BoxErrors<P>(P);
112
113impl<P: Producer> Producer for BoxErrors<P>
114where
115 P::Error: std::error::Error + Send + Sync + 'static,
116{
117 type Error = BoxError;
118
119 fn send(&self, key: &[u8], payload: Vec<u8>) -> Result<(), Self::Error> {
120 self.0.send(key, payload).map_err(Into::into)
121 }
122
123 fn join_blocking(&self, timeout: Duration) -> Result<(), Self::Error> {
124 self.0.join_blocking(timeout).map_err(Into::into)
125 }
126}
127
128#[derive(Clone, Copy, Debug, Default)]
130pub struct NoopProducer;
131
132impl Producer for NoopProducer {
133 type Error = std::convert::Infallible;
134
135 fn send(&self, _key: &[u8], _payload: Vec<u8>) -> Result<(), Self::Error> {
136 Ok(())
137 }
138
139 fn join_blocking(&self, _timeout: Duration) -> Result<(), Self::Error> {
140 Ok(())
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::test_utils::DummyProducer;
148
149 #[tokio::test]
150 async fn an_erased_producer_still_reaches_its_transport() {
151 let dummy = DummyProducer::default();
152 let producer = dummy.clone().shared();
153
154 producer.send(b"key", b"payload".to_vec()).unwrap();
155 producer.join(Duration::from_secs(1)).await.unwrap();
156
157 assert_eq!(dummy.raw(), [(b"key".to_vec(), b"payload".to_vec())]);
158 }
159
160 #[test]
161 fn joining_outside_a_runtime_still_drains() {
162 let dummy = DummyProducer::default();
163 let producer = dummy.clone().shared();
164
165 producer.send(b"key", b"payload".to_vec()).unwrap();
166 futures::executor::block_on(producer.join(Duration::from_secs(1))).unwrap();
167
168 assert_eq!(dummy.raw(), [(b"key".to_vec(), b"payload".to_vec())]);
169 }
170
171 #[test]
172 fn one_erased_producer_serves_many_trackers() {
173 let dummy = DummyProducer::default();
174 let producer = dummy.clone().shared();
175
176 for resource in ["bigtable_objectstore", "gcs_objectstore"] {
177 let tracker = crate::InventoryTracker::new(producer.clone(), resource, 1.0);
178 tracker
179 .delete(
180 "attachments/objects/abc",
181 "attachments",
182 std::time::SystemTime::now(),
183 )
184 .unwrap();
185 }
186
187 let resources: Vec<_> = dummy
188 .records()
189 .into_iter()
190 .map(|record| record.shared_resource_id)
191 .collect();
192 assert_eq!(resources, ["bigtable_objectstore", "gcs_objectstore"]);
193 }
194}