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}
37
38struct SendOnDrop(mpsc::Sender<()>);
39
40impl Drop for SendOnDrop {
41 fn drop(&mut self) {
42 let _ = self.0.send(());
43 }
44}
45
46struct HooksInner {
47 table: Mutex<HooksTable>,
48 _send_on_drop: SendOnDrop,
52}
53
54pub struct Hooks<'a> {
56 inner: Option<Arc<HooksInner>>,
57 receiver: mpsc::Receiver<()>,
58 _phantom: std::marker::PhantomData<&'a ()>,
59}
60
61impl<'a> Hooks<'a> {
62 pub fn new() -> (Self, Arc<HooksHandle>) {
64 let (sender, receiver) = mpsc::channel();
65 let inner = Arc::new(HooksInner {
66 table: Mutex::new(HooksTable::default()),
67 _send_on_drop: SendOnDrop(sender),
68 });
69 let handle = Arc::new(HooksHandle { inner: Arc::downgrade(&inner) });
70 (Self { inner: Some(inner), receiver, _phantom: std::marker::PhantomData }, handle)
71 }
72
73 pub fn set_pre_commit(
79 &mut self,
80 hook: impl Fn(&Transaction<'_>) -> Result<(), Error> + Send + Sync + 'a,
81 ) {
82 let boxed: Box<PreCommitHookFn<'a>> = Box::new(hook);
83 let static_hook: Box<PreCommitHookFn<'static>> = unsafe { std::mem::transmute(boxed) };
87 if let Some(inner) = &self.inner {
88 inner.table.lock().pre_commit = Some(Arc::from(static_hook));
89 }
90 }
91
92 pub fn set_before_commit(&mut self, hook: impl Fn() + Send + Sync + 'a) {
96 let boxed: Box<SyncHookFn<'a>> = Box::new(hook);
97 let static_hook: Box<SyncHookFn<'static>> = unsafe { std::mem::transmute(boxed) };
101 if let Some(inner) = &self.inner {
102 inner.table.lock().before_commit = Some(Arc::from(static_hook));
103 }
104 }
105
106 pub fn set_unlock_resources_acquired(&mut self, hook: impl Fn() + Send + Sync + 'a) {
109 let boxed: Box<SyncHookFn<'a>> = Box::new(hook);
110 let static_hook: Box<SyncHookFn<'static>> = unsafe { std::mem::transmute(boxed) };
114 if let Some(inner) = &self.inner {
115 inner.table.lock().unlock_resources_acquired = Some(Arc::from(static_hook));
116 }
117 }
118
119 fn detach(&mut self) {
120 if let Some(inner) = self.inner.take() {
121 drop(inner);
122 let _ = self.receiver.recv();
123 }
124 }
125}
126
127impl Drop for Hooks<'_> {
128 fn drop(&mut self) {
129 self.detach();
130 }
131}
132
133#[derive(Default)]
135pub struct HooksHandle {
136 inner: Weak<HooksInner>,
137}
138
139impl HooksHandle {
140 pub fn on_pre_commit(&self, transaction: &Transaction<'_>) -> Result<(), Error> {
142 if let Some(inner) = self.inner.upgrade() {
143 let hook = inner.table.lock().pre_commit.clone();
145 if let Some(hook) = hook {
146 return hook(transaction);
147 }
148 }
149 Ok(())
150 }
151
152 pub fn on_before_commit(&self) {
154 if let Some(inner) = self.inner.upgrade() {
155 let hook = inner.table.lock().before_commit.clone();
157 if let Some(hook) = hook {
158 hook();
159 }
160 }
161 }
162
163 pub fn on_unlock_resources_acquired(&self) {
165 if let Some(inner) = self.inner.upgrade() {
166 let hook = inner.table.lock().unlock_resources_acquired.clone();
168 if let Some(hook) = hook {
169 hook();
170 }
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use std::sync::atomic::{AtomicBool, Ordering};
179
180 #[test]
181 fn test_hooks_basic() {
182 let called = AtomicBool::new(false);
183 let (mut hooks, handle) = Hooks::new();
184
185 let called_ref = &called;
186 hooks.set_before_commit(move || {
187 called_ref.store(true, Ordering::Relaxed);
188 });
189
190 handle.on_before_commit();
191 assert!(called.load(Ordering::Relaxed));
192 }
193
194 #[test]
195 fn test_hooks_detached_on_drop() {
196 let called = AtomicBool::new(false);
197 let handle = {
198 let (mut hooks, handle) = Hooks::new();
199 let called_ref = &called;
200 hooks.set_before_commit(move || {
201 called_ref.store(true, Ordering::Relaxed);
202 });
203 handle
204 };
205
206 handle.on_before_commit();
208 assert!(!called.load(Ordering::Relaxed));
209 }
210
211 #[test]
212 fn test_hooks_unlock_resources_acquired() {
213 let called = AtomicBool::new(false);
214 let (mut hooks, handle) = Hooks::new();
215
216 let called_ref = &called;
217 hooks.set_unlock_resources_acquired(move || {
218 called_ref.store(true, Ordering::Relaxed);
219 });
220
221 handle.on_unlock_resources_acquired();
222 assert!(called.load(Ordering::Relaxed));
223 }
224}