sz_rust_sdk/core/
environment.rs

1//! Core implementation of SzEnvironment trait
2
3use crate::{
4    error::{SzError, SzResult},
5    ffi_call, ffi_call_i64,
6    traits::*,
7    types::*,
8};
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::{Arc, Mutex, OnceLock};
11
12/// Core implementation of the SzEnvironment trait
13///
14/// This struct manages the lifecycle of the Senzing environment and serves
15/// as a factory for obtaining instances of other SDK components.
16pub struct SzEnvironmentCore {
17    is_destroyed: Arc<AtomicBool>,
18    is_initialized: Arc<AtomicBool>,
19    module_name: String,
20    ini_params: String,
21    verbose_logging: bool,
22}
23
24// Singleton storage for the global SzEnvironmentCore instance
25static GLOBAL_ENVIRONMENT: OnceLock<Mutex<Option<Arc<SzEnvironmentCore>>>> = OnceLock::new();
26
27impl SzEnvironmentCore {
28    /// Creates a new SzEnvironment instance
29    ///
30    /// # Arguments
31    ///
32    /// * `module_name` - Name of the module for logging purposes
33    /// * `ini_params` - JSON string containing initialization parameters
34    /// * `verbose_logging` - Whether to enable verbose logging
35    pub fn new(module_name: &str, ini_params: &str, verbose_logging: bool) -> SzResult<Self> {
36        Ok(Self {
37            is_destroyed: Arc::new(AtomicBool::new(false)),
38            is_initialized: Arc::new(AtomicBool::new(false)),
39            module_name: module_name.to_string(),
40            ini_params: ini_params.to_string(),
41            verbose_logging,
42        })
43    }
44
45    /// Creates a new SzEnvironment instance with default parameters
46    pub fn new_default() -> SzResult<Self> {
47        Self::new("SzRustSDK", "{}", false)
48    }
49
50    /// Gets or creates the global singleton SzEnvironmentCore instance
51    ///
52    /// This method ensures that only one SzEnvironmentCore instance exists
53    /// per process, which is required by the Senzing SDK.
54    ///
55    /// # Arguments
56    ///
57    /// * `module_name` - Name of the module for logging purposes
58    /// * `ini_params` - JSON string containing initialization parameters
59    /// * `verbose_logging` - Whether to enable verbose logging
60    pub fn get_instance(
61        module_name: &str,
62        ini_params: &str,
63        verbose_logging: bool,
64    ) -> SzResult<Arc<Self>> {
65        let global_env = GLOBAL_ENVIRONMENT.get_or_init(|| Mutex::new(None));
66        let mut env_guard = global_env.lock().unwrap();
67
68        match env_guard.as_ref() {
69            Some(existing_env) => {
70                // Check if the existing environment is still valid
71                if existing_env.is_destroyed() {
72                    let new_env = Arc::new(Self::new(module_name, ini_params, verbose_logging)?);
73                    *env_guard = Some(new_env.clone());
74                    Ok(new_env)
75                } else {
76                    // Validate critical parameters match existing instance (ini_params and verbose_logging)
77                    // Module name can be different as it's only used for logging
78                    if existing_env.ini_params != ini_params
79                        || existing_env.verbose_logging != verbose_logging
80                    {
81                        return Err(SzError::configuration(
82                            "Cannot change critical initialization parameters (ini_params, verbose_logging) after SzEnvironmentCore instance is created",
83                        ));
84                    }
85                    // Return the existing valid environment (module name can be different)
86                    Ok(existing_env.clone())
87                }
88            }
89            None => {
90                // Create the first instance
91                let new_env = Arc::new(Self::new(module_name, ini_params, verbose_logging)?);
92                *env_guard = Some(new_env.clone());
93                Ok(new_env)
94            }
95        }
96    }
97
98    /// Gets the existing global singleton SzEnvironmentCore instance
99    ///
100    /// This method returns the existing singleton instance without creating a new one.
101    /// It will return an error if no instance has been created yet.
102    ///
103    /// # Returns
104    ///
105    /// Returns the existing singleton instance or an error if none exists.
106    pub fn get_existing_instance() -> SzResult<Arc<Self>> {
107        let global_env = GLOBAL_ENVIRONMENT.get_or_init(|| Mutex::new(None));
108        let env_guard = global_env.lock().unwrap();
109
110        match env_guard.as_ref() {
111            Some(existing_env) => {
112                if existing_env.is_destroyed() {
113                    Err(SzError::unrecoverable(
114                        "SzEnvironmentCore instance has been destroyed",
115                    ))
116                } else {
117                    Ok(existing_env.clone())
118                }
119            }
120            None => Err(SzError::unrecoverable(
121                "No SzEnvironmentCore instance has been created yet. Call get_instance() first.",
122            )),
123        }
124    }
125
126    /// Gets the global singleton instance if it exists
127    ///
128    /// Returns None if no instance has been created yet.
129    pub fn try_get_instance() -> Option<Arc<Self>> {
130        GLOBAL_ENVIRONMENT
131            .get()?
132            .lock()
133            .unwrap()
134            .as_ref()
135            .map(|env| env.clone())
136    }
137
138    /// Destroys the global singleton instance
139    ///
140    /// This allows a new instance to be created with different parameters.
141    pub fn destroy_global_instance() -> SzResult<()> {
142        if let Some(global_env) = GLOBAL_ENVIRONMENT.get() {
143            let mut env_guard = match global_env.lock() {
144                Ok(guard) => guard,
145                Err(poisoned) => {
146                    // Recover from poisoned mutex
147                    poisoned.into_inner()
148                }
149            };
150            if let Some(env) = env_guard.take() {
151                // Ensure complete destruction of all Senzing modules
152                if !env.is_destroyed() {
153                    // Mark the environment as destroyed
154                    env.is_destroyed
155                        .store(true, std::sync::atomic::Ordering::Relaxed);
156
157                    // Cleanup environment-tied components only
158                    // SzConfig* functions are handled independently in their constructors/destructors
159                    unsafe {
160                        // These are tied to the environment lifecycle
161                        let _ = crate::ffi::bindings::SzDiagnostic_destroy();
162                        let _ = crate::ffi::bindings::SzProduct_destroy();
163                        // Finally destroy the main Senzing environment
164                        let _ = crate::ffi::bindings::Sz_destroy();
165
166                        // Clear exception states for environment-tied components
167                        crate::ffi::bindings::Sz_clearLastException();
168                        crate::ffi::bindings::SzDiagnostic_clearLastException();
169                        crate::ffi::bindings::SzProduct_clearLastException();
170                    }
171
172                    // Give the native library time to fully clean up internal state
173                    std::thread::sleep(std::time::Duration::from_millis(100));
174                }
175            }
176        }
177        Ok(())
178    }
179
180    /// Get the initialization parameters used by this environment
181    pub fn get_ini_params(&self) -> &str {
182        &self.ini_params
183    }
184
185    /// Get the verbose logging setting used by this environment
186    pub fn get_verbose_logging(&self) -> bool {
187        self.verbose_logging
188    }
189
190    /// Ensures Sz_init has been called - should be called before any engine operations
191    ///
192    /// This method is thread-safe and will only call Sz_init once.
193    fn ensure_initialized(&self) -> SzResult<()> {
194        if self.is_initialized.load(Ordering::Relaxed) {
195            return Ok(());
196        }
197
198        // Use compare-and-swap to ensure only one thread calls Sz_init
199        if self
200            .is_initialized
201            .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
202            .is_ok()
203        {
204            let module_name_c = crate::ffi::helpers::str_to_c_string(&self.module_name)?;
205            let ini_params_c = crate::ffi::helpers::str_to_c_string(&self.ini_params)?;
206            let verbose = if self.verbose_logging { 1 } else { 0 };
207
208            ffi_call!(crate::ffi::bindings::Sz_init(
209                module_name_c.as_ptr(),
210                ini_params_c.as_ptr(),
211                verbose as i64
212            ));
213        }
214
215        Ok(())
216    }
217}
218
219impl SzEnvironment for SzEnvironmentCore {
220    fn destroy(&mut self) -> SzResult<()> {
221        if self.is_destroyed.load(Ordering::Relaxed) {
222            return Ok(());
223        }
224
225        ffi_call_i64!(crate::ffi::bindings::Sz_destroy());
226        self.is_destroyed.store(true, Ordering::Relaxed);
227        Ok(())
228    }
229
230    fn is_destroyed(&self) -> bool {
231        self.is_destroyed.load(Ordering::Relaxed)
232    }
233
234    fn reinitialize(&self, config_id: ConfigId) -> SzResult<()> {
235        if self.is_destroyed() {
236            return Err(SzError::unrecoverable("Environment has been destroyed"));
237        }
238
239        // Ensure Sz_init has been called before reinitializing
240        self.ensure_initialized()?;
241
242        ffi_call!(crate::ffi::bindings::Sz_reinit(config_id));
243        Ok(())
244    }
245
246    fn get_active_config_id(&self) -> SzResult<ConfigId> {
247        if self.is_destroyed() {
248            return Err(SzError::unrecoverable("Environment has been destroyed"));
249        }
250
251        // Ensure Sz_init has been called before getting active config ID
252        self.ensure_initialized()?;
253
254        let config_id = unsafe { crate::ffi::bindings::Sz_getActiveConfigID() };
255        Ok(config_id)
256    }
257
258    fn get_product(&self) -> SzResult<Box<dyn SzProduct>> {
259        if self.is_destroyed() {
260            return Err(SzError::unrecoverable("Environment has been destroyed"));
261        }
262
263        // Use the same settings as the environment for consistent initialization
264        let product_core = super::product::SzProductCore::new_with_params(
265            &self.module_name,
266            &self.ini_params,
267            self.verbose_logging,
268        )?;
269        Ok(Box::new(product_core))
270    }
271
272    fn get_engine(&self) -> SzResult<Box<dyn SzEngine>> {
273        if self.is_destroyed() {
274            return Err(SzError::unrecoverable("Environment has been destroyed"));
275        }
276
277        // Ensure Sz_init has been called before creating engine
278        self.ensure_initialized()?;
279
280        let engine_core = super::engine::SzEngineCore::new()?;
281        Ok(Box::new(engine_core))
282    }
283
284    fn get_config_manager(&self) -> SzResult<Box<dyn SzConfigManager>> {
285        if self.is_destroyed() {
286            return Err(SzError::unrecoverable("Environment has been destroyed"));
287        }
288
289        // Ensure Sz_init has been called before creating config manager
290        self.ensure_initialized()?;
291
292        let config_mgr_core = super::config_manager::SzConfigManagerCore::new()?;
293        Ok(Box::new(config_mgr_core))
294    }
295
296    fn get_diagnostic(&self) -> SzResult<Box<dyn SzDiagnostic>> {
297        if self.is_destroyed() {
298            return Err(SzError::unrecoverable("Environment has been destroyed"));
299        }
300
301        // Ensure Sz_init has been called before creating diagnostic
302        self.ensure_initialized()?;
303
304        let diagnostic_core = super::diagnostic::SzDiagnosticCore::new_with_params(
305            &self.module_name,
306            &self.ini_params,
307            self.verbose_logging,
308        )?;
309        Ok(Box::new(diagnostic_core))
310    }
311}
312
313impl Drop for SzEnvironmentCore {
314    fn drop(&mut self) {
315        // Disable automatic cleanup for now to avoid segfaults
316        // The user should call destroy() explicitly if needed
317        // TODO: Investigate proper cleanup sequence with Senzing library
318        if !self.is_destroyed() {
319            // Mark as destroyed without calling Sz_destroy()
320            self.is_destroyed
321                .store(true, std::sync::atomic::Ordering::Relaxed);
322        }
323    }
324}