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}