1use crate::object_store::transaction::Transaction;
24use anyhow::Error;
25use fuchsia_sync::Mutex;
26use std::sync::{Arc, Weak, mpsc};
27
28type PreCommitHookFn<'a> = dyn Fn(&Transaction<'_>) -> Result<(), Error> + Send + Sync + 'a;
29type SyncHookFn<'a> = dyn Fn() + Send + Sync + 'a;
30
31#[derive(Default)]
32struct HooksTable {
33 pre_commit: Option<Arc<PreCommitHookFn<'static>>>,
34 before_commit: Option<Arc<SyncHookFn<'static>>>,
35 unlock_resources_acquired: Option<Arc<SyncHookFn<'static>>>,
36 waiting_for_journal_space: Option<Arc<SyncHookFn<'static>>>,
37}
38
39struct SendOnDrop(mpsc::Sender<()>);
40
41impl Drop for SendOnDrop {
42 fn drop(&mut self) {
43 let _ = self.0.send(());
44 }
45}
46
47struct HooksInner {
48 table: Mutex<HooksTable>,
49 _send_on_drop: SendOnDrop,
53}
54
55pub struct Hooks<'a> {
57 inner: Option<Arc<HooksInner>>,
58 receiver: mpsc::Receiver<()>,
59 _phantom: std::marker::PhantomData<&'a ()>,
60}
61
62impl<'a> Hooks<'a> {
63 pub fn new() -> (Self, Arc<HooksHandle>) {
65 let (sender, receiver) = mpsc::channel();
66 let inner = Arc::new(HooksInner {
67 table: Mutex::new(HooksTable::default()),
68 _send_on_drop: SendOnDrop(sender),
69 });
70 let handle = Arc::new(HooksHandle { inner: Arc::downgrade(&inner) });
71 (Self { inner: Some(inner), receiver, _phantom: std::marker::PhantomData }, handle)
72 }
73
74 pub fn set_pre_commit(
80 &mut self,
81 hook: impl Fn(&Transaction<'_>) -> Result<(), Error> + Send + Sync + 'a,
82 ) {
83 let boxed: Box<PreCommitHookFn<'a>> = Box::new(hook);
84 let static_hook: Box<PreCommitHookFn<'static>> = unsafe { std::mem::transmute(boxed) };
88 if let Some(inner) = &self.inner {
89 inner.table.lock().pre_commit = Some(Arc::from(static_hook));
90 }
91 }
92
93 pub fn set_before_commit(&mut self, hook: impl Fn() + Send + Sync + 'a) {
97 let boxed: Box<SyncHookFn<'a>> = Box::new(hook);
98 let static_hook: Box<SyncHookFn<'static>> = unsafe { std::mem::transmute(boxed) };
102 if let Some(inner) = &self.inner {
103 inner.table.lock().before_commit = Some(Arc::from(static_hook));
104 }
105 }
106
107 pub fn set_unlock_resources_acquired(&mut self, hook: impl Fn() + Send + Sync + 'a) {
110 let boxed: Box<SyncHookFn<'a>> = Box::new(hook);
111 let static_hook: Box<SyncHookFn<'static>> = unsafe { std::mem::transmute(boxed) };
115 if let Some(inner) = &self.inner {
116 inner.table.lock().unlock_resources_acquired = Some(Arc::from(static_hook));
117 }
118 }
119
120 pub fn set_waiting_for_journal_space(&mut self, hook: impl Fn() + Send + Sync + 'a) {
123 let boxed: Box<SyncHookFn<'a>> = Box::new(hook);
124 let static_hook: Box<SyncHookFn<'static>> = unsafe { std::mem::transmute(boxed) };
128 if let Some(inner) = &self.inner {
129 inner.table.lock().waiting_for_journal_space = Some(Arc::from(static_hook));
130 }
131 }
132
133 fn detach(&mut self) {
134 if let Some(inner) = self.inner.take() {
135 drop(inner);
136 let _ = self.receiver.recv();
137 }
138 }
139}
140
141impl Drop for Hooks<'_> {
142 fn drop(&mut self) {
143 self.detach();
144 }
145}
146
147#[derive(Default)]
149pub struct HooksHandle {
150 inner: Weak<HooksInner>,
151}
152
153impl HooksHandle {
154 pub fn on_pre_commit(&self, transaction: &Transaction<'_>) -> Result<(), Error> {
156 if let Some(inner) = self.inner.upgrade() {
157 let hook = inner.table.lock().pre_commit.clone();
159 if let Some(hook) = hook {
160 return hook(transaction);
161 }
162 }
163 Ok(())
164 }
165
166 pub fn on_before_commit(&self) {
168 if let Some(inner) = self.inner.upgrade() {
169 let hook = inner.table.lock().before_commit.clone();
171 if let Some(hook) = hook {
172 hook();
173 }
174 }
175 }
176
177 pub fn on_unlock_resources_acquired(&self) {
179 if let Some(inner) = self.inner.upgrade() {
180 let hook = inner.table.lock().unlock_resources_acquired.clone();
182 if let Some(hook) = hook {
183 hook();
184 }
185 }
186 }
187
188 pub fn on_waiting_for_journal_space(&self) {
190 if let Some(inner) = self.inner.upgrade() {
191 let hook = inner.table.lock().waiting_for_journal_space.clone();
193 if let Some(hook) = hook {
194 hook();
195 }
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use std::sync::atomic::{AtomicBool, Ordering};
204
205 #[test]
206 fn test_hooks_basic() {
207 let called = AtomicBool::new(false);
208 let (mut hooks, handle) = Hooks::new();
209
210 let called_ref = &called;
211 hooks.set_before_commit(move || {
212 called_ref.store(true, Ordering::Relaxed);
213 });
214
215 handle.on_before_commit();
216 assert!(called.load(Ordering::Relaxed));
217 }
218
219 #[test]
220 fn test_hooks_detached_on_drop() {
221 let called = AtomicBool::new(false);
222 let handle = {
223 let (mut hooks, handle) = Hooks::new();
224 let called_ref = &called;
225 hooks.set_before_commit(move || {
226 called_ref.store(true, Ordering::Relaxed);
227 });
228 handle
229 };
230
231 handle.on_before_commit();
233 assert!(!called.load(Ordering::Relaxed));
234 }
235
236 #[test]
237 fn test_hooks_unlock_resources_acquired() {
238 let called = AtomicBool::new(false);
239 let (mut hooks, handle) = Hooks::new();
240
241 let called_ref = &called;
242 hooks.set_unlock_resources_acquired(move || {
243 called_ref.store(true, Ordering::Relaxed);
244 });
245
246 handle.on_unlock_resources_acquired();
247 assert!(called.load(Ordering::Relaxed));
248 }
249
250 #[test]
251 fn test_hooks_waiting_for_journal_space() {
252 let called = AtomicBool::new(false);
253 let (mut hooks, handle) = Hooks::new();
254
255 let called_ref = &called;
256 hooks.set_waiting_for_journal_space(move || {
257 called_ref.store(true, Ordering::Relaxed);
258 });
259
260 handle.on_waiting_for_journal_space();
261 assert!(called.load(Ordering::Relaxed));
262 }
263}