use std::cell::RefCell;
use std::future::Future;
use std::sync::Arc;
use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock};
use super::SessionState;
pub type SharedSession = Arc<RwLock<SessionState>>;
type LiveCell = Arc<std::sync::RwLock<SharedSession>>;
tokio::task_local! {
static BOUND: RefCell<Option<(LiveCell, SharedSession)>>;
}
pub(super) fn bound_session() -> Option<SharedSession> {
BOUND
.try_with(|bound| {
bound
.borrow()
.as_ref()
.map(|(_, session)| Arc::clone(session))
})
.ok()
.flatten()
}
#[derive(Clone)]
pub struct SessionSlot {
live: LiveCell,
own: Arc<std::sync::Mutex<std::collections::HashMap<String, String>>>,
}
impl SessionSlot {
pub fn new(session: SharedSession) -> Self {
Self {
live: Arc::new(std::sync::RwLock::new(session)),
own: Arc::default(),
}
}
pub fn remember_own(&self, project_root: &str, session_id: &str) {
self.own
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(project_root.to_owned(), session_id.to_owned());
}
pub fn own_session_id(&self, project_root: &str) -> Option<String> {
self.own
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(project_root)
.cloned()
}
pub fn live(&self) -> SharedSession {
Arc::clone(
&self
.live
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner),
)
}
pub fn instance(&self) -> SharedSession {
BOUND
.try_with(|bound| {
bound
.borrow()
.as_ref()
.filter(|(slot, _)| Arc::ptr_eq(slot, &self.live))
.map(|(_, session)| Arc::clone(session))
})
.ok()
.flatten()
.unwrap_or_else(|| self.live())
}
pub async fn read(&self) -> OwnedRwLockReadGuard<SessionState> {
self.instance().read_owned().await
}
pub async fn write(&self) -> OwnedRwLockWriteGuard<SessionState> {
self.instance().write_owned().await
}
pub fn replace_if_live(&self, current: &SharedSession, next: SharedSession) -> bool {
let mut live = self
.live
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !Arc::ptr_eq(&live, current) {
return false;
}
*live = next;
true
}
pub fn rebind(&self, next: SharedSession) {
let _ = BOUND.try_with(|bound| {
if let Some((slot, session)) = bound.borrow_mut().as_mut()
&& Arc::ptr_eq(slot, &self.live)
{
*session = next;
}
});
}
pub async fn scope<F: Future>(&self, request: F) -> F::Output {
let (output, bound) = BOUND
.scope(RefCell::new(None), async {
let output = request.await;
let bound = BOUND.with(|bound| bound.borrow_mut().take());
(output, bound)
})
.await;
if let Some((_, session)) = bound
&& !Arc::ptr_eq(&session, &self.live())
{
let pending = {
let state = session.read().await;
(state.stats.unsaved_changes > 0).then(|| state.project_root.clone())
};
if let Some(project) = pending {
crate::core::policy::runtime::REQUEST_PROJECT
.scope(
RefCell::new(project.map(std::path::PathBuf::from)),
SessionState::save_shared_logged(session),
)
.await;
}
}
output
}
pub fn pin(&self) {
let _ = BOUND.try_with(|bound| {
let mut bound = bound.borrow_mut();
if bound.is_none() {
*bound = Some((Arc::clone(&self.live), self.live()));
}
});
}
}