use std::sync::{Mutex, MutexGuard, OnceLock, RwLock};
use tracing::warn;
use crate::error::Error;
use crate::generated::{ClientState, StatusResponse};
use crate::lldb::LldbProcess;
static GLOBAL_STATE: OnceLock<RwLock<InternalState>> = OnceLock::new();
static WAKE_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
static INITIALIZED: OnceLock<RwLock<bool>> = OnceLock::new();
#[derive(Debug)]
pub struct InternalState {
pub state: ClientState,
pub name: String,
pub control_host: String,
pub advertise_host: Option<String>,
pub control_port: u16,
pub actual_control_port: u16,
pub debug_port: u16,
pub actual_debug_port: u16,
pub debug_port_active: bool,
pub daemon_url: String,
pub connection_id: Option<String>,
pub lldb_dap_path: String,
pub detrix_home: Option<String>,
pub workspace_root: Option<String>,
pub safe_mode: bool,
pub build_commit: Option<String>,
pub build_tag: Option<String>,
pub health_check_timeout_ms: u64,
pub register_timeout_ms: u64,
pub unregister_timeout_ms: u64,
pub lldb_start_timeout_ms: u64,
pub daemon_advertise_url: Option<String>,
}
impl Default for InternalState {
fn default() -> Self {
Self {
state: ClientState::Sleeping,
name: String::new(),
control_host: "127.0.0.1".to_string(),
advertise_host: None,
control_port: 0,
actual_control_port: 0,
debug_port: 0,
actual_debug_port: 0,
debug_port_active: false,
daemon_url: "http://127.0.0.1:8090".to_string(),
connection_id: None,
lldb_dap_path: String::new(),
detrix_home: None,
workspace_root: None,
safe_mode: false,
build_commit: None,
build_tag: None,
health_check_timeout_ms: 2000,
register_timeout_ms: 5000,
unregister_timeout_ms: 2000,
lldb_start_timeout_ms: 10000,
daemon_advertise_url: None,
}
}
}
impl InternalState {
pub fn to_status_response(&self) -> StatusResponse {
StatusResponse {
state: self.state,
name: self.name.clone(),
control_host: self.control_host.clone(),
control_port: i32::from(self.actual_control_port),
debug_port: i32::from(self.actual_debug_port),
debug_port_active: self.debug_port_active,
daemon_url: self.daemon_url.clone(),
connection_id: self.connection_id.clone(),
}
}
}
pub fn get() -> &'static RwLock<InternalState> {
GLOBAL_STATE.get_or_init(|| RwLock::new(InternalState::default()))
}
pub fn reset() {
if let Some(state) = GLOBAL_STATE.get() {
match state.write() {
Ok(mut guard) => *guard = InternalState::default(),
Err(poisoned) => {
warn!("Global state lock poisoned during reset, recovering");
*poisoned.into_inner() = InternalState::default();
}
}
}
if let Some(init) = INITIALIZED.get() {
match init.write() {
Ok(mut guard) => *guard = false,
Err(poisoned) => {
warn!("Initialized lock poisoned during reset, recovering");
*poisoned.into_inner() = false;
}
}
}
}
pub fn is_initialized() -> bool {
*INITIALIZED
.get_or_init(|| RwLock::new(false))
.read()
.unwrap_or_else(|poisoned| {
warn!("Initialized lock poisoned during read, recovering");
poisoned.into_inner()
})
}
pub fn set_initialized(value: bool) {
let init = INITIALIZED.get_or_init(|| RwLock::new(false));
match init.write() {
Ok(mut guard) => *guard = value,
Err(poisoned) => {
warn!("Initialized lock poisoned during write, recovering");
*poisoned.into_inner() = value;
}
}
}
pub fn acquire_wake_lock() -> Result<MutexGuard<'static, ()>, Error> {
Ok(WAKE_LOCK.get_or_init(|| Mutex::new(())).lock()?)
}
static LLDB_PROCESS: OnceLock<Mutex<Option<LldbProcess>>> = OnceLock::new();
pub fn get_lldb_process() -> &'static Mutex<Option<LldbProcess>> {
LLDB_PROCESS.get_or_init(|| Mutex::new(None))
}
pub fn set_lldb_process(process: LldbProcess) {
match get_lldb_process().lock() {
Ok(mut guard) => *guard = Some(process),
Err(poisoned) => {
warn!("lldb process lock poisoned during set, recovering");
*poisoned.into_inner() = Some(process);
}
}
}
pub fn take_lldb_process() -> Option<LldbProcess> {
match get_lldb_process().lock() {
Ok(mut guard) => guard.take(),
Err(poisoned) => {
warn!("lldb process lock poisoned during take, recovering");
poisoned.into_inner().take()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_state() {
let state = InternalState::default();
assert!(matches!(state.state, ClientState::Sleeping));
assert_eq!(state.control_host, "127.0.0.1");
}
#[test]
fn test_status_response() {
let mut state = InternalState::default();
state.name = "test-client".to_string();
state.actual_control_port = 8091;
let response = state.to_status_response();
assert_eq!(response.name, "test-client");
assert_eq!(response.control_port, 8091);
}
#[test]
fn test_set_initialized_roundtrip() {
set_initialized(true);
assert!(is_initialized());
set_initialized(false);
assert!(!is_initialized());
}
#[test]
fn test_take_lldb_process_returns_none_when_empty() {
let _ = take_lldb_process();
assert!(take_lldb_process().is_none());
}
#[test]
fn test_to_status_response_port_cast_safe() {
let mut state = InternalState::default();
state.actual_control_port = u16::MAX;
state.actual_debug_port = u16::MAX;
let response = state.to_status_response();
assert_eq!(response.control_port, i32::from(u16::MAX));
assert_eq!(response.debug_port, i32::from(u16::MAX));
}
}