lean_ctx/core/session/
slot.rs1use std::cell::RefCell;
12use std::future::Future;
13use std::sync::Arc;
14
15use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock};
16
17use super::SessionState;
18
19pub type SharedSession = Arc<RwLock<SessionState>>;
20type LiveCell = Arc<std::sync::RwLock<SharedSession>>;
21
22tokio::task_local! {
23 static BOUND: RefCell<Option<(LiveCell, SharedSession)>>;
26}
27
28pub(super) fn bound_session() -> Option<SharedSession> {
31 BOUND
32 .try_with(|bound| {
33 bound
34 .borrow()
35 .as_ref()
36 .map(|(_, session)| Arc::clone(session))
37 })
38 .ok()
39 .flatten()
40}
41
42#[derive(Clone)]
45pub struct SessionSlot {
46 live: LiveCell,
47 own: Arc<std::sync::Mutex<std::collections::HashMap<String, String>>>,
51}
52
53impl SessionSlot {
54 pub fn new(session: SharedSession) -> Self {
55 Self {
56 live: Arc::new(std::sync::RwLock::new(session)),
57 own: Arc::default(),
58 }
59 }
60
61 pub fn remember_own(&self, project_root: &str, session_id: &str) {
63 self.own
64 .lock()
65 .unwrap_or_else(std::sync::PoisonError::into_inner)
66 .insert(project_root.to_owned(), session_id.to_owned());
67 }
68
69 pub fn own_session_id(&self, project_root: &str) -> Option<String> {
71 self.own
72 .lock()
73 .unwrap_or_else(std::sync::PoisonError::into_inner)
74 .get(project_root)
75 .cloned()
76 }
77
78 pub fn live(&self) -> SharedSession {
80 Arc::clone(
81 &self
82 .live
83 .read()
84 .unwrap_or_else(std::sync::PoisonError::into_inner),
85 )
86 }
87
88 pub fn instance(&self) -> SharedSession {
91 BOUND
92 .try_with(|bound| {
93 bound
94 .borrow()
95 .as_ref()
96 .filter(|(slot, _)| Arc::ptr_eq(slot, &self.live))
97 .map(|(_, session)| Arc::clone(session))
98 })
99 .ok()
100 .flatten()
101 .unwrap_or_else(|| self.live())
102 }
103
104 pub async fn read(&self) -> OwnedRwLockReadGuard<SessionState> {
105 self.instance().read_owned().await
106 }
107
108 pub async fn write(&self) -> OwnedRwLockWriteGuard<SessionState> {
109 self.instance().write_owned().await
110 }
111
112 pub fn replace_if_live(&self, current: &SharedSession, next: SharedSession) -> bool {
115 let mut live = self
116 .live
117 .write()
118 .unwrap_or_else(std::sync::PoisonError::into_inner);
119 if !Arc::ptr_eq(&live, current) {
120 return false;
121 }
122 *live = next;
123 true
124 }
125
126 pub fn rebind(&self, next: SharedSession) {
130 let _ = BOUND.try_with(|bound| {
131 if let Some((slot, session)) = bound.borrow_mut().as_mut()
132 && Arc::ptr_eq(slot, &self.live)
133 {
134 *session = next;
135 }
136 });
137 }
138
139 pub async fn scope<F: Future>(&self, request: F) -> F::Output {
145 let (output, bound) = BOUND
146 .scope(RefCell::new(None), async {
147 let output = request.await;
148 let bound = BOUND.with(|bound| bound.borrow_mut().take());
149 (output, bound)
150 })
151 .await;
152 if let Some((_, session)) = bound
153 && !Arc::ptr_eq(&session, &self.live())
154 {
155 let pending = {
156 let state = session.read().await;
157 (state.stats.unsaved_changes > 0).then(|| state.project_root.clone())
158 };
159 if let Some(project) = pending {
160 crate::core::policy::runtime::REQUEST_PROJECT
161 .scope(
162 RefCell::new(project.map(std::path::PathBuf::from)),
163 SessionState::save_shared_logged(session),
164 )
165 .await;
166 }
167 }
168 output
169 }
170
171 pub fn pin(&self) {
175 let _ = BOUND.try_with(|bound| {
176 let mut bound = bound.borrow_mut();
177 if bound.is_none() {
178 *bound = Some((Arc::clone(&self.live), self.live()));
179 }
180 });
181 }
182}