sabi/tokio/mod.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
5//! This module provides Tokio-specific implementations for asynchronous data access,
6//! including `AsyncGroup` for concurrent task management, `DataConn` for
7//! transactional data connections, `DataSrc` for data source management,
8//! and `DataHub` as a central orchestrator.
9//!
10//! It leverages Rust's asynchronous capabilities with the Tokio runtime
11//! to enable efficient and concurrent handling of data operations.
12
13mod async_group;
14mod data_acc;
15mod data_conn;
16mod data_hub;
17mod data_src;
18
19use crate::{ErrEntry, SendSyncNonNull, TxnFailureReport};
20
21use std::any;
22use std::collections::HashMap;
23use std::future::Future;
24use std::pin::Pin;
25use std::sync::Arc;
26
27pub use data_conn::DataConnError;
28pub use data_hub::DataHubError;
29pub use data_src::{
30 create_static_data_src_container, setup_async, setup_with_order_async, uses, uses_async,
31 DataSrcError,
32};
33
34/// A convenience macro to easily convert an asynchronous function into a `Pin<Box<dyn Future>>`
35/// closure suitable for `DataHub`'s `run_async` or `txn_async` methods.
36///
37/// This macro simplifies passing async functions by handling the boxing and pinning.
38/// The resulting `Future` implements `Send`.
39///
40/// # Example
41///
42/// ```ignore
43/// async fn my_logic(data: &mut (impl MyData + Send)) -> errs::Result<()> {
44/// // ... some logic using data
45/// Ok(())
46/// }
47///
48/// #[tokio::main]
49/// async fn main() {
50/// let mut hub = DataHub::new();
51/// hub.txn_async(logic!(my_logic)).await.unwrap();
52/// }
53/// ```
54#[doc(inline)]
55pub use crate::_logic as logic;
56
57/// Macro for registering a global data source at the top-level.
58///
59/// # Parameters
60///
61/// * `$name` - The name of the data source (must be a string literal).
62/// * `$data_src` - The data source instance.
63///
64/// # Examples
65///
66/// ```ignore
67/// uses!("my_global_source", MyDataSource::new());
68/// ```
69#[doc(inline)]
70pub use crate::_uses_for_async as uses;
71
72/// The structure that allows for the concurrent execution of multiple asynchronous tasks
73/// using green-thread and waits for all of them to complete.
74///
75/// Functions are added using the `add` method and are then run concurrently in separate green-threads.
76/// The `AsyncGroup` ensures that all tasks finish before proceeding,
77/// and can collect any errors that occur.
78#[allow(clippy::type_complexity)]
79pub struct AsyncGroup {
80 attrs: Vec<(usize, Arc<str>)>,
81 tasks: Vec<Pin<Box<dyn Future<Output = errs::Result<()>> + Send + 'static>>>,
82 _index: usize,
83 _name: Arc<str>,
84}
85
86/// The asynchronous trait for data connection implementations, providing methods for transaction
87/// management.
88///
89/// Implementors of this trait represent a connection to a data source and define
90/// how to commit, rollback, and handle the lifecycle of transactions.
91#[allow(async_fn_in_trait)]
92#[allow(unused_variables)] // rustdoc
93pub trait DataConn {
94 /// Attempts to asynchronously commit the changes made within this data connection.
95 ///
96 /// This is typically the main commit process, executed after `pre_commit_async`.
97 ///
98 /// # Parameters
99 ///
100 /// * `ag` - An `AsyncGroup` to which asynchronous tasks related to the commit can be added.
101 ///
102 /// # Returns
103 ///
104 /// A `Result` indicating success or failure of the commit operation.
105 fn commit_async(
106 &mut self,
107 ag: &mut AsyncGroup,
108 ) -> impl Future<Output = errs::Result<()>> + Send;
109
110 /// Performs preparatory actions before the main commit process.
111 ///
112 /// This method is called before `commit_async` and can be used for tasks like
113 /// validation or preparing data.
114 ///
115 /// # Parameters
116 ///
117 /// * `ag` - An `AsyncGroup` to which asynchronous tasks related to pre-commit can be added.
118 ///
119 /// # Returns
120 ///
121 /// A `Result` indicating success or failure of the pre-commit operation.
122 fn pre_commit_async(
123 &mut self,
124 ag: &mut AsyncGroup,
125 ) -> impl Future<Output = errs::Result<()>> + Send {
126 async { Ok(()) }
127 }
128
129 /// Performs actions after the main commit process, only if it succeeds.
130 ///
131 /// This can be used for cleanup or post-transaction logging.
132 ///
133 /// # Parameters
134 ///
135 /// * `ag` - An `AsyncGroup` to which asynchronous tasks related to post-commit can be added.
136 ///
137 /// # Returns
138 ///
139 /// A `Result` indicating success or failure of the post-commit operation.
140 fn post_commit_async(
141 &mut self,
142 ag: &mut AsyncGroup,
143 ) -> impl Future<Output = errs::Result<()>> + Send {
144 async { Ok(()) }
145 }
146
147 /// Returns whether the transaction on this connection has been successfully committed.
148 fn is_committed(&self) -> bool;
149
150 /// Rolls back any changes made within this data connection's transaction.
151 ///
152 /// This method undoes all operations performed since the beginning of the transaction,
153 /// restoring the data service to its state before the transaction began.
154 ///
155 /// # Parameters
156 ///
157 /// * `ag` - An `AsyncGroup` to which asynchronous tasks related to rollback can be added.
158 ///
159 /// # Returns
160 ///
161 /// A `Result` indicating success or failure of the rollback operation.
162 fn rollback_async(
163 &mut self,
164 ag: &mut AsyncGroup,
165 ) -> impl Future<Output = errs::Result<()>> + Send;
166
167 /// An asynchronous lifecycle callback invoked when a transaction fails and a rollback is executed.
168 ///
169 /// This allows the data connection to handle post-failure tasks or custom logic
170 /// based on the provided transaction failure reports.
171 ///
172 /// # Parameters
173 ///
174 /// * `ag`: A mutable reference to an [`AsyncGroup`] for asynchronous task execution.
175 /// * `reports`: An [`Arc`] slice of [`TxnFailureReport`] containing failure details for all
176 /// connections.
177 #[cfg_attr(coverage_nightly, coverage(off))]
178 fn on_txn_failure_async(
179 &mut self,
180 ag: &mut AsyncGroup,
181 reports: Arc<[TxnFailureReport]>,
182 ) -> impl Future<Output = ()> + Send {
183 async {}
184 }
185
186 /// Closes the data connection, releasing any associated resources.
187 ///
188 /// This method is always called at the end of a transaction, regardless of its outcome.
189 fn close(&mut self);
190}
191
192pub(crate) struct NoopDataConn {}
193
194#[cfg_attr(coverage_nightly, coverage(off))]
195impl DataConn for NoopDataConn {
196 async fn commit_async(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> {
197 Ok(())
198 }
199 fn is_committed(&self) -> bool {
200 false
201 }
202 async fn rollback_async(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> {
203 Ok(())
204 }
205 fn close(&mut self) {}
206}
207
208#[allow(clippy::type_complexity)]
209#[repr(C)]
210pub(crate) struct DataConnContainer<C = NoopDataConn>
211where
212 C: DataConn + 'static,
213{
214 drop_fn: fn(*const DataConnContainer),
215 is_fn: fn(any::TypeId) -> bool,
216 type_fn: fn() -> &'static str,
217
218 commit_fn: for<'ag> fn(
219 *const DataConnContainer,
220 &'ag mut AsyncGroup,
221 ) -> Pin<Box<dyn Future<Output = errs::Result<()>> + Send + 'ag>>,
222
223 pre_commit_fn: for<'ag> fn(
224 *const DataConnContainer,
225 &'ag mut AsyncGroup,
226 ) -> Pin<Box<dyn Future<Output = errs::Result<()>> + Send + 'ag>>,
227
228 post_commit_fn: for<'ag> fn(
229 *const DataConnContainer,
230 &'ag mut AsyncGroup,
231 )
232 -> Pin<Box<dyn Future<Output = errs::Result<()>> + Send + 'ag>>,
233
234 is_committed_fn: fn(*const DataConnContainer) -> bool,
235
236 rollback_fn: for<'ag> fn(
237 *const DataConnContainer,
238 &'ag mut AsyncGroup,
239 ) -> Pin<Box<dyn Future<Output = errs::Result<()>> + Send + 'ag>>,
240
241 on_txn_failure_fn: for<'ag> fn(
242 *const DataConnContainer,
243 &'ag mut AsyncGroup,
244 Arc<[TxnFailureReport]>,
245 ) -> Pin<Box<dyn Future<Output = ()> + Send + 'ag>>,
246
247 close_fn: fn(*const DataConnContainer),
248
249 name: Arc<str>,
250 data_conn: Box<C>,
251}
252
253pub(crate) struct DataConnManager {
254 vec: Vec<Option<SendSyncNonNull<DataConnContainer>>>,
255 index_map: HashMap<Arc<str>, usize>,
256 committed: bool,
257}
258
259/// The trait that abstracts a data source responsible for managing connections
260/// to external data services, such as databases, file systems, or messaging services.
261///
262/// It receives configuration for connecting to an external data service and then
263/// creates and supplies [`DataConn`] instance, representing a single session connection.
264#[trait_variant::make(Send)]
265#[allow(unused_variables)] // for rustdoc
266pub trait DataSrc<C>
267where
268 C: DataConn + 'static,
269{
270 /// Performs the asynchronous setup process for the data source.
271 ///
272 /// This method is responsible for establishing global connections, configuring
273 /// connection pools, or performing any necessary initializations required
274 /// before [`DataConn`] instances can be created.
275 ///
276 /// # Parameters
277 ///
278 /// * `ag`: A mutable reference to an [`AsyncGroup`]. This is used if the setup
279 /// process is potentially time-consuming and can benefit from concurrent
280 /// execution in a separate thread.
281 ///
282 /// # Returns
283 ///
284 /// * `errs::Result<()>`: `Ok(())` if the setup is successful, or an [`errs::Err`]
285 /// if any part of the setup fails.
286 async fn setup_async(&mut self, ag: &mut AsyncGroup) -> errs::Result<()>;
287
288 /// Closes the data source and releases any globally held resources.
289 ///
290 /// This method should perform cleanup operations, such as closing global connections
291 /// or shutting down connection pools, that were established during the setup process.
292 fn close(&mut self);
293
294 /// Asynchronously creates a new [`DataConn`] instance which is a connection per session.
295 ///
296 /// Each call to this method should yield a distinct [`DataConn`] object tailored
297 /// for a single session's operations.
298 ///
299 /// # Returns
300 ///
301 /// * `errs::Result<Box<C>>`: `Ok(Box<C>)` containing the newly created [`DataConn`]
302 /// if successful, or an [`errs::Err`] if the connection could not be created.
303 async fn create_data_conn_async(&mut self) -> errs::Result<Box<C>>;
304}
305
306pub(crate) struct NoopDataSrc {}
307
308#[cfg_attr(coverage_nightly, coverage(off))]
309impl DataSrc<NoopDataConn> for NoopDataSrc {
310 async fn setup_async(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> {
311 Ok(())
312 }
313 fn close(&mut self) {}
314 async fn create_data_conn_async(&mut self) -> errs::Result<Box<NoopDataConn>> {
315 Ok(Box::new(NoopDataConn {}))
316 }
317}
318
319#[allow(clippy::type_complexity)]
320#[repr(C)]
321pub(crate) struct DataSrcContainer<S = NoopDataSrc, C = NoopDataConn>
322where
323 S: DataSrc<C>,
324 C: DataConn + 'static,
325{
326 drop_fn: fn(*const DataSrcContainer),
327 close_fn: fn(*const DataSrcContainer),
328 is_data_conn_fn: fn(any::TypeId) -> bool,
329
330 setup_fn: for<'ag> fn(
331 *const DataSrcContainer,
332 &'ag mut AsyncGroup,
333 ) -> Pin<Box<dyn Future<Output = errs::Result<()>> + Send + 'ag>>,
334
335 create_data_conn_fn: fn(
336 *const DataSrcContainer,
337 ) -> Pin<
338 Box<dyn Future<Output = errs::Result<Box<DataConnContainer<C>>>> + Send + 'static>,
339 >,
340
341 local: bool,
342 name: Arc<str>,
343 data_src: S,
344}
345
346pub(crate) struct DataSrcManager {
347 vec_unready: Vec<SendSyncNonNull<DataSrcContainer>>,
348 vec_ready: Vec<SendSyncNonNull<DataSrcContainer>>,
349 local: bool,
350}
351
352/// A utility struct that ensures to close and drop global data sources when it goes out of scope.
353///
354/// This struct implements the `Drop` trait, and its `drop` method handles the closing and
355/// dropping of registered global data sources.
356/// Therefore, this ensures that these operations are automatically executed at the end of
357/// the scope.
358///
359/// **NOTE:** Do not receive an instance of this struct into an anonymous variable
360/// (`let _ = ...`), because an anonymous variable is dropped immediately at that point.
361pub struct AutoShutdown {}
362
363/// The struct that acts as a central hub for data input/output operations, integrating
364/// multiple *Data* traits (which are passed to business logic functions as their arguments) with
365/// [`DataAcc`] traits (which implement default data I/O methods for external services).
366///
367/// It facilitates data access by providing [`DataConn`] objects, created from
368/// both global data sources (registered via the global [`uses!`] macro) and
369/// session-local data sources (registered via [`DataHub::uses`] method).
370///
371/// The [`DataHub`] is capable of performing aggregated transactional operations
372/// on all [`DataConn`] objects created from its registered [`DataSrc`] instances.
373pub struct DataHub {
374 local_data_src_manager: DataSrcManager,
375 data_src_map: HashMap<Arc<str>, (bool, usize)>,
376 data_conn_manager: DataConnManager,
377 fixed: bool,
378}
379
380/// This trait provides a mechanism to retrieve a mutable reference to a [`DataConn`] object
381/// by name, creating it if necessary.
382///
383/// It is typically implemented as a derived trait with default methods (using
384/// the `override_macro` crate) on [`DataHub`], allowing application logic to
385/// interact with data services through an abstract interface.
386pub trait DataAcc {
387 /// Retrieves a mutable reference to a [`DataConn`] object by name, creating it if necessary.
388 ///
389 /// This is the core method used by [`DataAcc`] implementations to obtain connections
390 /// to external data services. It first checks if a [`DataConn`] with the given name
391 /// already exists in the current session. If not, it attempts to find a
392 /// corresponding [`DataSrc`] and create a new [`DataConn`] from it.
393 ///
394 /// # Type Parameters
395 ///
396 /// * `C`: The concrete type of [`DataConn`] expected.
397 ///
398 /// # Parameters
399 ///
400 /// * `name`: The name of the data source/connection to retrieve.
401 ///
402 /// # Returns
403 ///
404 /// * `errs::Result<&mut C>`: A mutable reference to the [`DataConn`] instance if successful,
405 /// or an [`errs::Err`] if the data source is not found, or if the retrieved/created
406 /// [`DataConn`] cannot be cast to the specified type `C`.
407 #[allow(async_fn_in_trait)]
408 fn get_data_conn_async<C: DataConn + 'static>(
409 &mut self,
410 name: &str,
411 ) -> impl Future<Output = errs::Result<&mut C>> + Send;
412}
413
414#[doc(hidden)]
415pub struct StaticDataSrcContainer {
416 pub(crate) ssnnptr: SendSyncNonNull<DataSrcContainer>,
417}
418
419#[doc(hidden)]
420pub struct StaticDataSrcRegistration {
421 pub(crate) factory: fn() -> StaticDataSrcContainer,
422}