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)]
195pub fn create_static_data_src_container<S, C>(
196    name: &'static str,
197    data_src: S,
198) -> StaticDataSrcContainer
199where
200    S: DataSrc<C>,
201    C: DataConn + 'static,
202{
203    let boxed = Box::new(DataSrcContainer::<S, C>::new(name, data_src, false));
204    let ptr = ptr::NonNull::from(Box::leak(boxed)).cast::<DataSrcContainer>();
205    StaticDataSrcContainer {
206        ssnnptr: SendSyncNonNull::new(ptr),
207    }
208}
209
210impl StaticDataSrcRegistration {
211    pub const fn new(factory: fn() -> StaticDataSrcContainer) -> Self {
212        Self { factory }
213    }
214}
215inventory::collect!(StaticDataSrcRegistration);
216
217/// Registers a global data source that can be used throughout the application.
218///
219/// This macro associates a given [`DataSrc`] implementation with a unique name.
220/// This name will later be used to retrieve session-specific [`DataConn`] instances
221/// from this data source.
222///
223/// Global data sources are set up once via the [`setup`] function and are available
224/// to all [`DataHub`] instances.
225///
226/// # Parameters
227///
228/// * `name`: The unique name for the data source.
229/// * `ds`: The [`DataSrc`] instance to register.
230///
231/// # Examples
232///
233/// ```ignore
234/// use sabi::{DataSrc, DataConn, AsyncGroup, uses};
235/// use errs::Err;
236///
237/// struct MyDataSrc;
238/// impl DataSrc<MyDataConn> for MyDataSrc {
239///     fn setup(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> { Ok(()) }
240///     fn close(&mut self) {}
241///     fn create_data_conn(&mut self) -> errs::Result<Box<MyDataConn>> {
242///         Ok(Box::new(MyDataConn))
243///     }
244/// }
245///
246/// struct MyDataConn;
247/// impl DataConn for MyDataConn {
248///     fn commit(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> { Ok(()) }
249///     fn rollback(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> { Ok(()) }
250///     fn is_committed(&self) -> bool { false }
251///     fn close(&mut self) {}
252/// }
253///
254/// uses!("my_data_src", MyDataSrc);
255/// ```
256#[macro_export]
257macro_rules! uses {
258    ($name:tt, $data_src:expr) => {
259        const _: () = {
260            inventory::submit! {
261                $crate::StaticDataSrcRegistration::new(|| {
262                    $crate::create_static_data_src_container($name, $data_src)
263                })
264            }
265        };
266    };
267}
268
269pub(crate) fn copy_global_data_srcs_to_map(index_map: &mut HashMap<Arc<str>, (bool, usize)>) {
270    if let Ok(ds_m) = DS_MANAGER.read_relaxed() {
271        ds_m.copy_ds_ready_to_map(index_map);
272    } else if (match DS_MANAGER.transition_to_read(NOOP) {
273        Ok(_) => Ok(()),
274        Err(e) => match e.kind() {
275            PhasedErrorKind::PhaseIsAlreadyCleanup => Ok(()),
276            PhasedErrorKind::DuringTransitionToRead => Ok(()),
277            _ => Err(()),
278        },
279    })
280    .is_ok()
281    {
282        if let Ok(ds_m) = DS_MANAGER.read_relaxed() {
283            ds_m.copy_ds_ready_to_map(index_map);
284        }
285    }
286}
287
288pub(crate) fn create_data_conn_from_global_data_src<C>(
289    index: usize,
290    name: impl AsRef<str>,
291) -> errs::Result<Box<DataConnContainer>>
292where
293    C: DataConn + 'static,
294{
295    match DS_MANAGER.read_relaxed() {
296        Ok(ds_manager) => ds_manager.create_data_conn::<C>(index, name),
297        Err(e) => Err(errs::Err::with_source(
298            DataSrcError::FailToCreateDataConn {
299                name: name.as_ref().into(),
300                data_conn_type: any::type_name::<C>(),
301            },
302            e,
303        )),
304    }
305}