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}