Skip to main content

sabi/data_src/
global_setup.rs

1// Copyright (C) 2024-2026 Takayuki Sato. All Rights Reserved.
2// This program is free software under MIT License.
3// See the file LICENSE in this distribution for more details.
4
5use super::DataSrcError;
6
7use crate::{
8    AutoShutdown, DataConn, DataConnContainer, DataSrc, DataSrcContainer, DataSrcManager,
9    SendSyncNonNull, StaticDataSrcContainer, StaticDataSrcRegistration,
10};
11
12#[allow(unused)] // for rustdoc
13use crate::DataHub;
14
15use setup_read_cleanup::{PhasedCell, PhasedError, PhasedErrorKind};
16
17use std::collections::HashMap;
18use std::sync::Arc;
19use std::{any, ptr};
20
21pub(crate) static DS_MANAGER: PhasedCell<DataSrcManager> =
22    PhasedCell::new(DataSrcManager::new(false));
23
24const NOOP: fn(&mut DataSrcManager) -> Result<(), PhasedError> = |_| Ok(());
25
26impl Drop for AutoShutdown {
27    fn drop(&mut self) {
28        let _ = DS_MANAGER.transition_to_cleanup(NOOP);
29        match DS_MANAGER.get_mut_unlocked() {
30            Ok(ds_m) => ds_m.close(),
31            Err(e) => {
32                eprintln!("ERROR(sabi): Fail to close and drop global DataSrc(s): {e:?}");
33            }
34        }
35    }
36}
37
38/// Registers a global data source dynamically at runtime.
39///
40/// This function associates a given [`DataSrc`] implementation with a unique name.
41/// This name will later be used to retrieve session-specific [`DataConn`] instances
42/// from this data source.
43///
44/// Global data sources added via this function can be set up via [`setup`] or [`setup_with_order`].
45///
46/// If `setup` or `setup_with_order` has already been called, this function will return an `errs::Err`.  
47///
48/// # Parameters
49///
50/// * `name`: The unique name for the data source.
51/// * `ds`: The [`DataSrc`] instance to register.
52///
53/// # Returns
54///
55/// * `errs::Result<()>`: [`Ok`] if the data source is successfully registered, or an [`errs::Err`] if
56///   the global data source manager is in an invalid state or if [`setup`] or [`setup_with_order`]
57///   has already been called.
58pub fn uses<S, C>(name: impl Into<Arc<str>>, ds: S) -> errs::Result<()>
59where
60    S: DataSrc<C>,
61    C: DataConn + 'static,
62{
63    match DS_MANAGER.get_mut_unlocked() {
64        Ok(dsm) => {
65            dsm.add(name, ds);
66            Ok(())
67        }
68        Err(e) => Err(errs::Err::with_source(
69            DataSrcError::FailToRegisterGlobalDataSrc { name: name.into() },
70            e,
71        )),
72    }
73}
74
75fn collect_static_data_src_containers(dsm: &mut DataSrcManager) {
76    let regs: Vec<_> = inventory::iter::<StaticDataSrcRegistration>
77        .into_iter()
78        .collect();
79
80    let mut static_vec: Vec<SendSyncNonNull<DataSrcContainer>> = Vec::with_capacity(regs.len());
81    for reg in regs {
82        let any_container = (reg.factory)();
83        static_vec.push(any_container.ssnnptr);
84    }
85
86    dsm.prepend(static_vec);
87}
88
89/// Executes the setup process for all globally registered data sources.
90///
91/// This setup typically involves tasks such as creating connection pools,
92/// opening global connections, or performing initial configurations necessary
93/// for creating session-specific connections.
94///
95/// If any data source fails to set up, this function returns an [`errs::Err`] with
96/// [`DataSrcError::FailToSetupGlobalDataSrcs`], containing a vector of the names
97/// of the failed data sources and their corresponding [`errs::Err`] objects. In such a case,
98/// all global data sources that were successfully set up are also closed.
99///
100/// If all data source setups are successful, the [`Result::Ok`] which contains an
101/// [`AutoShutdown`] object is returned. This object is designed to close and drop global
102/// data sources when it's dropped.
103/// Thanks to Rust's ownership mechanism, this ensures that the global data sources are
104/// automatically cleaned up when the return value goes out of scope.
105///
106/// **NOTE:** Do not receive the [`Result`] or its inner object into an anonymous
107/// variable using `let _ = ...`.
108/// If you do, the inner object is dropped immediately at that point.
109///
110/// # Returns
111///
112/// * `Result<AutoShutdown, errs::Err>`: An [`AutoShutdown`] if all global data sources are
113///   set up successfully, or an [`errs::Err`] if any setup fails.
114pub fn setup() -> errs::Result<AutoShutdown> {
115    let mut errors = Vec::new();
116    let em = &mut errors;
117
118    if let Err(e) = DS_MANAGER.transition_to_read(move |dsm| {
119        collect_static_data_src_containers(dsm);
120        dsm.setup(em);
121        Ok::<(), PhasedError>(())
122    }) {
123        if e.kind() == PhasedErrorKind::DuringTransitionToRead {
124            Err(errs::Err::new(DataSrcError::DuringSetupGlobalDataSrcs))
125        } else {
126            Err(errs::Err::new(DataSrcError::AlreadySetupGlobalDataSrcs))
127        }
128    } else if errors.is_empty() {
129        Ok(AutoShutdown {})
130    } else {
131        Err(errs::Err::new(DataSrcError::FailToSetupGlobalDataSrcs {
132            errors,
133        }))
134    }
135}
136
137/// Executes the setup process for all globally registered data sources, allowing for a specified
138/// order of setup for a subset of data sources.
139///
140/// This function is similar to [`setup`], but it allows defining a specific order for the setup
141/// of certain data sources identified by their names. Data sources not specified in `names` will
142/// be set up after the named ones, in their order of acquisition.
143///
144/// This setup typically involves tasks such as creating connection pools,
145/// opening global connections, or performing initial configurations necessary
146/// for creating session-specific connections.
147///
148/// If any data source fails to set up, this function returns an [`errs::Err`] with
149/// [`DataSrcError::FailToSetupGlobalDataSrcs`], containing a vector of the names
150/// of the failed data sources and their corresponding [`errs::Err`] objects. In such a case,
151/// all global data sources that were successfully set up are also closed.
152///
153/// If all data source setups are successful, the [`Result::Ok`] which contains an
154/// [`AutoShutdown`] object is returned. This object is designed to close and drop global
155/// data sources when it's dropped.
156/// Thanks to Rust's ownership mechanism, this ensures that the global data sources are
157/// automatically cleaned up when the return value goes out of scope.
158///
159/// **NOTE:** Do not receive the [`Result`] or its inner object into an anonymous
160/// variable using `let _ = ...`.
161/// If you do, the inner object is dropped immediately at that point.
162///
163/// # Parameters
164///
165/// * `names`: A slice of `&str` representing the names of data sources to set up in a specific order.
166///
167/// # Returns
168///
169/// * `Result<AutoShutdown, errs::Err>`: An [`AutoShutdown`] if all global data sources are
170///   set up successfully, or an [`errs::Err`] if any setup fails.
171pub fn setup_with_order(names: &[&str]) -> errs::Result<AutoShutdown> {
172    let mut errors = Vec::new();
173    let em = &mut errors;
174
175    if let Err(e) = DS_MANAGER.transition_to_read(move |dsm| {
176        collect_static_data_src_containers(dsm);
177        dsm.setup_with_order(names, em);
178        Ok::<(), PhasedError>(())
179    }) {
180        if e.kind() == PhasedErrorKind::DuringTransitionToRead {
181            Err(errs::Err::new(DataSrcError::DuringSetupGlobalDataSrcs))
182        } else {
183            Err(errs::Err::new(DataSrcError::AlreadySetupGlobalDataSrcs))
184        }
185    } else if errors.is_empty() {
186        Ok(AutoShutdown {})
187    } else {
188        Err(errs::Err::new(DataSrcError::FailToSetupGlobalDataSrcs {
189            errors,
190        }))
191    }
192}
193
194#[doc(hidden)]
195/// Helper function to create a [`StaticDataSrcContainer`] for static registration.
196/// This function is used by the [`uses!`] macro.
197pub fn create_static_data_src_container<S, C>(
198    name: &'static str,
199    data_src: S,
200) -> StaticDataSrcContainer
201where
202    S: DataSrc<C>,
203    C: DataConn + 'static,
204{
205    let boxed = Box::new(DataSrcContainer::<S, C>::new(name, data_src, false));
206    let ptr = ptr::NonNull::from(Box::leak(boxed)).cast::<DataSrcContainer>();
207    StaticDataSrcContainer {
208        ssnnptr: SendSyncNonNull::new(ptr),
209    }
210}
211
212impl StaticDataSrcRegistration {
213    pub const fn new(factory: fn() -> StaticDataSrcContainer) -> Self {
214        Self { factory }
215    }
216}
217inventory::collect!(StaticDataSrcRegistration);
218
219/// Registers a global data source that can be used throughout the application.
220///
221/// This macro associates a given [`DataSrc`] implementation with a unique name.
222/// This name will later be used to retrieve session-specific [`DataConn`] instances
223/// from this data source.
224///
225/// Global data sources are set up once via the [`setup`] function and are available
226/// to all [`DataHub`] instances.
227///
228/// # Parameters
229///
230/// * `name`: The unique name for the data source.
231/// * `ds`: The [`DataSrc`] instance to register.
232///
233/// # Examples
234///
235/// ```ignore
236/// use sabi::{DataSrc, DataConn, AsyncGroup, uses};
237/// use errs::Err;
238///
239/// struct MyDataSrc;
240/// impl DataSrc<MyDataConn> for MyDataSrc {
241///     fn setup(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> { Ok(()) }
242///     fn close(&mut self) {}
243///     fn create_data_conn(&mut self) -> errs::Result<Box<MyDataConn>> {
244///         Ok(Box::new(MyDataConn))
245///     }
246/// }
247///
248/// struct MyDataConn;
249/// impl DataConn for MyDataConn {
250///     fn commit(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> { Ok(()) }
251///     fn rollback(&mut self, _ag: &mut AsyncGroup) {}
252///     fn close(&mut self) {}
253/// }
254///
255/// uses!("my_data_src", MyDataSrc);
256/// ```
257#[macro_export]
258macro_rules! uses {
259    ($name:tt, $data_src:expr) => {
260        const _: () = {
261            inventory::submit! {
262                $crate::StaticDataSrcRegistration::new(|| {
263                    $crate::create_static_data_src_container($name, $data_src)
264                })
265            }
266        };
267    };
268}
269
270pub(crate) fn copy_global_data_srcs_to_map(index_map: &mut HashMap<Arc<str>, (bool, usize)>) {
271    if let Ok(ds_m) = DS_MANAGER.read_relaxed() {
272        ds_m.copy_ds_ready_to_map(index_map);
273    } else if (match DS_MANAGER.transition_to_read(NOOP) {
274        Ok(_) => Ok(()),
275        Err(e) => match e.kind() {
276            PhasedErrorKind::PhaseIsAlreadyCleanup => Ok(()),
277            PhasedErrorKind::DuringTransitionToRead => Ok(()),
278            _ => Err(()),
279        },
280    })
281    .is_ok()
282    {
283        if let Ok(ds_m) = DS_MANAGER.read_relaxed() {
284            ds_m.copy_ds_ready_to_map(index_map);
285        }
286    }
287}
288
289pub(crate) fn create_data_conn_from_global_data_src<C>(
290    index: usize,
291    name: impl AsRef<str>,
292) -> errs::Result<Box<DataConnContainer>>
293where
294    C: DataConn + 'static,
295{
296    match DS_MANAGER.read_relaxed() {
297        Ok(ds_manager) => ds_manager.create_data_conn::<C>(index, name),
298        Err(e) => Err(errs::Err::with_source(
299            DataSrcError::FailToCreateDataConn {
300                name: name.as_ref().into(),
301                data_conn_type: any::type_name::<C>(),
302            },
303            e,
304        )),
305    }
306}