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