nym_task/
runtime_registry.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use thiserror::Error;
5
6use crate::ShutdownManager;
7use std::sync::RwLock;
8use std::sync::{Arc, LazyLock};
9
10/// Global registry that manages ShutdownManagers transparently.
11/// This allows SDK components to get automatic task management without
12/// exposing the complexity to end users.
13pub(crate) struct RuntimeRegistry {
14    // For SDK clients: auto-created manager without signal handling
15    sdk_manager: RwLock<Option<Arc<ShutdownManager>>>,
16}
17
18#[derive(Debug, Error)]
19pub enum RegistryAccessError {
20    #[error("the runtime registry is poisoned")]
21    Poisoned,
22
23    #[error("The SDK ShutdownManager already exists")]
24    ExistingShutdownManager,
25
26    #[error("No existing SDK ShutdownManager")]
27    MissingShutdownManager,
28}
29
30impl RuntimeRegistry {
31    /// Create a ShutdownManager for SDK use.
32    /// This manager doesn't listen to OS signals, making it suitable for library use.
33    /// This function overwrite any existing manager!
34    pub(crate) fn create_sdk() -> Result<Arc<ShutdownManager>, RegistryAccessError> {
35        let mut guard = REGISTRY
36            .sdk_manager
37            .write()
38            .map_err(|_| RegistryAccessError::Poisoned)?;
39
40        Ok(guard
41            .insert(Arc::new(
42                ShutdownManager::new_without_signals().with_cancel_on_panic(),
43            ))
44            .clone())
45    }
46
47    /// Get the  ShutdownManager for SDK use.
48    /// This manager doesn't listen to OS signals, making it suitable for library use.
49    /// Not yet used, but maybe in the future
50    #[allow(dead_code)]
51    pub(crate) fn get_sdk() -> Result<Arc<ShutdownManager>, RegistryAccessError> {
52        let guard = REGISTRY
53            .sdk_manager
54            .read()
55            .map_err(|_| RegistryAccessError::Poisoned)?;
56        if let Some(manager) = guard.as_ref() {
57            Ok(manager.clone())
58        } else {
59            Err(RegistryAccessError::MissingShutdownManager)
60        }
61    }
62
63    /// Check if an SDK manager has been created.
64    /// Useful for testing and debugging.
65    #[allow(dead_code)]
66    pub(crate) fn has_sdk_manager() -> Result<bool, RegistryAccessError> {
67        Ok(REGISTRY
68            .sdk_manager
69            .read()
70            .map_err(|_| RegistryAccessError::Poisoned)?
71            .is_some())
72    }
73
74    /// Clear the SDK manager.
75    /// This is primarily for testing to ensure isolation between tests.
76    #[cfg(test)]
77    pub(crate) async fn clear() -> Result<(), RegistryAccessError> {
78        *REGISTRY
79            .sdk_manager
80            .write()
81            .map_err(|_| RegistryAccessError::Poisoned)? = None;
82        Ok(())
83    }
84}
85
86/// Global instance of the runtime registry.
87/// Uses LazyLock for on-demand initialization.
88static REGISTRY: LazyLock<RuntimeRegistry> = LazyLock::new(|| RuntimeRegistry {
89    sdk_manager: RwLock::new(None),
90});
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[tokio::test]
97    async fn test_get_or_create_sdk() {
98        // Clear any existing manager
99        let _ = RuntimeRegistry::clear().await;
100
101        assert!(!RuntimeRegistry::has_sdk_manager().unwrap());
102
103        // Error if nothing was created
104        assert!(RuntimeRegistry::get_sdk().is_err());
105
106        let manager1 = RuntimeRegistry::create_sdk().unwrap();
107        assert!(RuntimeRegistry::has_sdk_manager().unwrap());
108
109        let manager2 = RuntimeRegistry::get_sdk().unwrap();
110        // Should return the same instance
111        assert!(Arc::ptr_eq(&manager1, &manager2));
112
113        let _ = RuntimeRegistry::clear().await;
114        assert!(!RuntimeRegistry::has_sdk_manager().unwrap());
115    }
116}