1use std::{
2 cell::{Cell, Ref, RefCell, RefMut},
3 rc::{Rc, Weak},
4};
5
6use crate::{
7 Applier, NodeError, RecomposeScope, SlotDebugSnapshot, SlotTable, SlotTableDebugStats,
8 composer, slot,
9};
10
11pub struct SlotsHost {
14 storage_key: Cell<usize>,
15 pub(crate) inner: RefCell<SlotsHostInner>,
16}
17
18#[derive(Debug, Default)]
19pub(crate) struct SlotPassOutcome {
20 pub(crate) compacted: bool,
21 pub(crate) compact_anchor_registry_storage: bool,
22 pub(crate) compact_payload_storage: bool,
23 pub(crate) trim_growth_slack: bool,
24}
25
26#[derive(Default)]
27pub(crate) struct FinishedSlotPass {
28 pub(crate) outcome: SlotPassOutcome,
29 pub(crate) detached_root_children: Vec<slot::DetachedSubtree>,
30}
31
32pub(crate) struct SlotsHostInner {
33 table: SlotTable,
34 nested_hosts: Vec<Weak<SlotsHost>>,
35 lifecycle: slot::SlotLifecycleCoordinator,
36 runtime_state: Option<Rc<composer::ComposerRuntimeState>>,
37 pub(crate) active_pass: Option<usize>,
40 pub(crate) pass_state: Option<Box<slot::SlotWriteSessionState>>,
44}
45
46const SPARE_WRITE_STATES: usize = 2;
48
49fn spare_write_state(folds: &Rc<slot::BranchFolds>) -> Box<slot::SlotWriteSessionState> {
52 match crate::thread_state::try_runtimes(|state| state.spare_write_states.borrow_mut().pop())
53 .flatten()
54 {
55 Some(mut state) => {
56 state.set_folds(Rc::clone(folds));
57 state
58 }
59 None => Box::new(slot::SlotWriteSessionState::new(Rc::clone(folds))),
60 }
61}
62
63fn recycle_write_state(state: Box<slot::SlotWriteSessionState>) {
64 crate::thread_state::try_runtimes(|threads| {
65 let mut spare = threads.spare_write_states.borrow_mut();
66 if spare.len() < SPARE_WRITE_STATES {
67 spare.push(state);
68 }
69 });
70}
71
72impl Drop for SlotsHost {
73 fn drop(&mut self) {
74 let storage_key = self.storage_key.get();
75 let inner = self.inner.get_mut();
76 if let Some(state) = inner.runtime_state.clone() {
77 if let Err(err) = state.dispose_retained_subtrees_for_host(
78 storage_key,
79 &mut inner.table,
80 &mut inner.lifecycle,
81 ) {
82 log::error!(
83 "retained subtree disposal failed while dropping SlotsHost {storage_key}: {err}"
84 );
85 state.abandon_retained_subtrees_for_host(
86 storage_key,
87 &mut inner.table,
88 &mut inner.lifecycle,
89 );
90 } else {
91 state.clear_host_storage_key(storage_key, &mut inner.table);
92 }
93 }
94 inner.lifecycle.dispose_slot_table(&mut inner.table);
95 }
96}
97
98impl SlotsHost {
99 pub fn storage_key(&self) -> usize {
102 self.storage_key.get()
103 }
104
105 pub fn new(storage: SlotTable) -> Self {
107 let storage_key = storage.storage_id();
108 Self {
109 storage_key: Cell::new(storage_key),
110 inner: RefCell::new(SlotsHostInner {
111 table: storage,
112 nested_hosts: Vec::new(),
113 lifecycle: slot::SlotLifecycleCoordinator::default(),
114 runtime_state: None,
115 active_pass: None,
116 pass_state: None,
117 }),
118 }
119 }
120
121 pub fn note_nested_host(&self, nested: &Rc<Self>) {
124 let Ok(mut inner) = self.inner.try_borrow_mut() else {
125 return;
126 };
127 inner.nested_hosts.retain(|held| held.upgrade().is_some());
128 if inner
129 .nested_hosts
130 .iter()
131 .any(|held| held.upgrade().is_some_and(|host| Rc::ptr_eq(&host, nested)))
132 {
133 return;
134 }
135 inner.nested_hosts.push(Rc::downgrade(nested));
136 }
137
138 pub(crate) fn forget_effects(&self) -> bool {
139 let (forgotten, nested, runtime_state) = {
140 let Ok(mut inner) = self.inner.try_borrow_mut() else {
141 return false;
142 };
143 if inner.active_pass.is_some() {
144 return false;
145 }
146 let drops = inner.table.take_effect_drops();
147 inner.nested_hosts.retain(|held| held.upgrade().is_some());
148 let nested: Vec<Rc<Self>> = inner
149 .nested_hosts
150 .iter()
151 .filter_map(Weak::upgrade)
152 .collect();
153 (drops, nested, inner.runtime_state.clone())
154 };
155 let mut any = !forgotten.is_empty();
156 drop(forgotten);
157 for host in nested {
158 any |= host.forget_effects();
159 }
160 if any {
161 self.borrow()
162 .scopes()
163 .for_each(RecomposeScope::force_recompose);
164 if let Some(runtime_state) = runtime_state {
165 runtime_state.force_recompose_retained_scopes(self.storage_key());
166 }
167 }
168 any
169 }
170
171 pub(crate) fn bind_runtime_state(&self, state: &Rc<composer::ComposerRuntimeState>) {
172 let mut inner = self.inner.borrow_mut();
173 inner.runtime_state = Some(Rc::clone(state));
174 }
175
176 pub(crate) fn rebind_orphaned_runtime_state(
177 &self,
178 state: &Rc<composer::ComposerRuntimeState>,
179 ) -> bool {
180 let inner = self.inner.borrow();
181 if inner.active_pass.is_some() {
182 log::error!("cannot rebind SlotsHost during an active pass");
183 return false;
184 }
185 let Some(bound_state) = inner.runtime_state.as_ref() else {
186 drop(inner);
187 self.bind_runtime_state(state);
188 return true;
189 };
190 if Rc::ptr_eq(bound_state, state) {
191 return true;
192 }
193 if bound_state.has_live_applier_host() {
194 return false;
195 }
196 drop(inner);
197
198 let mut inner = self.inner.borrow_mut();
199 let Some(bound_state) = inner.runtime_state.as_ref() else {
200 inner.runtime_state = Some(Rc::clone(state));
201 return true;
202 };
203 if Rc::ptr_eq(bound_state, state) {
204 return true;
205 }
206 if bound_state.has_live_applier_host() {
207 return false;
208 }
209
210 let previous_state = Rc::clone(bound_state);
211 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
212 lifecycle.flush_pending_drops();
213 let host_key = self.storage_key();
214 if previous_state
215 .dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
216 .is_err()
217 {
218 inner.lifecycle = lifecycle;
219 return false;
220 }
221 previous_state.clear_host(self, &mut inner.table);
222 lifecycle.flush_pending_drops();
223 inner.runtime_state = Some(Rc::clone(state));
224 inner.lifecycle = lifecycle;
225 true
226 }
227
228 pub(crate) fn runtime_state(&self) -> Option<Rc<composer::ComposerRuntimeState>> {
229 self.inner.borrow().runtime_state.clone()
230 }
231
232 pub(crate) fn runtime_state_ptr(&self) -> Option<*const composer::ComposerRuntimeState> {
233 self.inner.borrow().runtime_state.as_ref().map(Rc::as_ptr)
234 }
235
236 pub(crate) fn borrow(&self) -> Ref<'_, SlotTable> {
237 Ref::map(self.inner.borrow(), |inner| &inner.table)
238 }
239
240 pub(crate) fn borrow_mut(&self) -> RefMut<'_, SlotTable> {
241 RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.table)
242 }
243
244 pub fn into_table(self: Rc<Self>) -> Result<SlotTable, NodeError> {
247 if Rc::strong_count(&self) != 1 {
248 return Err(NodeError::SlotHostUnavailable {
249 operation: "SlotsHost::into_table",
250 reason: "other host references are alive",
251 });
252 }
253 self.take_table_for_transfer()
254 }
255
256 fn take_table_for_transfer(&self) -> Result<SlotTable, NodeError> {
257 let inner = self.inner.borrow();
258 if inner.active_pass.is_some() {
259 return Err(NodeError::SlotHostUnavailable {
260 operation: "SlotsHost::into_table",
261 reason: "slot pass is active",
262 });
263 }
264 drop(inner);
265 let mut inner = self.inner.borrow_mut();
266 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
267 lifecycle.flush_pending_drops();
268 if let Some(state) = inner.runtime_state.clone() {
269 let host_key = self.storage_key();
270 if let Err(error) =
271 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
272 {
273 inner.lifecycle = lifecycle;
274 return Err(error);
275 }
276 state.clear_host(self, &mut inner.table);
277 lifecycle.flush_pending_drops();
278 }
279 let taken = std::mem::take(&mut inner.table);
280 self.storage_key.set(inner.table.storage_id());
281 inner.runtime_state = None;
282 inner.lifecycle = lifecycle;
283 Ok(taken)
284 }
285
286 pub fn reset(&self) -> Result<(), NodeError> {
289 let inner = self.inner.borrow();
290 if inner.active_pass.is_some() {
291 return Err(NodeError::SlotHostUnavailable {
292 operation: "SlotsHost::reset",
293 reason: "slot pass is active",
294 });
295 }
296 let runtime_state = inner.runtime_state.clone();
297 drop(inner);
298 let mut inner = self.inner.borrow_mut();
299 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
300 if let Some(state) = runtime_state {
301 let host_key = self.storage_key();
302 if let Err(error) =
303 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
304 {
305 inner.lifecycle = lifecycle;
306 return Err(error);
307 }
308 state.clear_host(self, &mut inner.table);
309 }
310 lifecycle.dispose_slot_table(&mut inner.table);
311 inner.table = SlotTable::default();
312 self.storage_key.set(inner.table.storage_id());
313 inner.runtime_state = None;
314 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
315 Ok(())
316 }
317
318 pub(crate) fn abandon_after_apply_failure(&self) {
319 let inner = self.inner.borrow();
320 if inner.active_pass.is_some() {
321 log::error!("cannot abandon SlotsHost during an active pass");
322 return;
323 }
324 let runtime_state = inner.runtime_state.clone();
325 drop(inner);
326 let mut inner = self.inner.borrow_mut();
327 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
328 if let Some(state) = runtime_state {
329 let host_key = self.storage_key();
330 state.abandon_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle);
331 }
332 lifecycle.dispose_slot_table(&mut inner.table);
333 inner.table = SlotTable::default();
334 self.storage_key.set(inner.table.storage_id());
335 inner.runtime_state = None;
336 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
337 }
338
339 pub(crate) fn debug_stats(&self) -> SlotTableDebugStats {
340 let inner = self.inner.borrow();
341 let local = inner.table.debug_stats();
342 let lifecycle = inner.lifecycle.debug_stats();
343 let retention = inner
344 .runtime_state
345 .clone()
346 .map(|state| state.slot_retention_debug_stats(self))
347 .unwrap_or_default();
348 SlotTableDebugStats::from_parts(local, lifecycle, retention)
349 }
350
351 #[cold]
352 #[inline(never)]
353 pub(crate) fn debug_snapshot(&self) -> SlotDebugSnapshot {
354 let inner = self.inner.borrow();
355 let mut snapshot = inner.table.debug_snapshot();
356 if let Some(state) = inner.runtime_state.clone() {
357 state.fill_slot_debug_snapshot(self, &mut snapshot);
358 }
359 snapshot
360 }
361
362 pub(crate) fn begin_pass(&self, mode: slot::SlotPassMode, folds: &Rc<slot::BranchFolds>) {
363 let mut inner = self.inner.borrow_mut();
364 let SlotsHostInner {
365 table,
366 active_pass,
367 pass_state,
368 ..
369 } = &mut *inner;
370 if active_pass.is_some() {
371 log::error!("slot pass already active for host");
372 return;
373 }
374 let state = match pass_state {
375 Some(state) => {
376 state.set_folds(Rc::clone(folds));
377 state
378 }
379 None => pass_state.insert(spare_write_state(folds)),
380 };
381 state.reset_for_pass(mode);
382 *active_pass = Some(table.storage_capacity());
383 }
384
385 pub(crate) fn has_active_pass(&self) -> bool {
386 self.inner.borrow().active_pass.is_some()
387 }
388
389 pub(crate) fn abandon_active_pass(&self) {
390 self.inner.borrow_mut().active_pass = None;
391 }
392
393 pub(crate) fn with_write_session<R>(
394 &self,
395 f: impl FnOnce(&mut slot::SlotWriteSession<'_>) -> R,
396 ) -> R {
397 let mut inner = self.inner.borrow_mut();
398 let SlotsHostInner {
399 table,
400 lifecycle,
401 active_pass,
402 pass_state,
403 ..
404 } = &mut *inner;
405 let state = pass_state
406 .as_mut()
407 .filter(|_| active_pass.is_some())
408 .expect("slot write session requires an active pass");
409 let mut session = table.write_session(lifecycle, state);
410 f(&mut session)
411 }
412
413 pub(crate) fn with_table_and_lifecycle_mut<R>(
414 &self,
415 f: impl FnOnce(&mut SlotTable, &mut slot::SlotLifecycleCoordinator) -> R,
416 ) -> R {
417 let mut inner = self.inner.borrow_mut();
418 let SlotsHostInner {
419 table, lifecycle, ..
420 } = &mut *inner;
421 f(table, lifecycle)
422 }
423
424 pub(crate) fn finish_pass(
425 &self,
426 applier: &mut dyn Applier,
427 ) -> Result<FinishedSlotPass, NodeError> {
428 let mut inner = self.inner.borrow_mut();
429 let SlotsHostInner {
430 table,
431 lifecycle,
432 active_pass,
433 pass_state,
434 ..
435 } = &mut *inner;
436 let (Some(storage_capacity), Some(state)) = (active_pass.take(), pass_state.as_mut())
437 else {
438 return Ok(FinishedSlotPass::default());
439 };
440
441 state.flush_payload_location_refreshes(table);
442
443 #[cfg(any(test, debug_assertions))]
444 if let Err(err) = state.validate(table) {
445 log::error!("slot writer invariant violation before finalize_pass: {err:?}");
446 return Err(NodeError::SlotHostUnavailable {
447 operation: "SlotsHost::finish_pass",
448 reason: "slot writer invariant violation",
449 });
450 }
451
452 let detached_root_children = {
453 let mut session = table.write_session(lifecycle, state);
454 session.finalize_pass(applier)?
455 };
456
457 let outcome = SlotPassOutcome {
458 compacted: state.request_compaction,
459 compact_anchor_registry_storage: state.request_anchor_storage_compaction,
460 compact_payload_storage: state.request_payload_storage_compaction,
461 trim_growth_slack: state.removed_nothing()
462 && table.storage_capacity() > storage_capacity,
463 };
464 if state.is_shared()
465 && let Some(state) = pass_state.take()
466 {
467 recycle_write_state(state);
468 }
469 Ok(FinishedSlotPass {
470 outcome,
471 detached_root_children,
472 })
473 }
474
475 pub(crate) fn flush_pending_drops(&self) {
476 self.inner.borrow_mut().lifecycle.flush_pending_drops();
477 }
478
479 pub(crate) fn complete_pass_cleanup(&self, outcome: &SlotPassOutcome) {
480 let mut inner = self.inner.borrow_mut();
481 let SlotsHostInner {
482 table,
483 lifecycle,
484 runtime_state,
485 ..
486 } = &mut *inner;
487 lifecycle.flush_pending_drops();
488 if outcome.compacted {
489 table.compact_storage();
490 lifecycle.compact_storage();
491 } else if outcome.trim_growth_slack {
492 table.trim_growth_slack();
493 }
494 if let Some(state) = runtime_state.clone() {
495 state.compact_table_identity_storage_for_host(
496 self,
497 table,
498 outcome.compact_anchor_registry_storage,
499 outcome.compact_payload_storage,
500 );
501 } else {
502 if outcome.compact_anchor_registry_storage {
503 table.compact_anchor_registry_storage(None);
504 }
505 if outcome.compact_payload_storage {
506 table.compact_payload_anchor_registry_storage(None);
507 }
508 }
509 table.assert_fast_integrity("slot pass cleanup");
510 #[cfg(any(test, debug_assertions))]
511 {
512 table.debug_verify();
513 if let Some(state) = runtime_state.clone() {
514 state.debug_verify_host(self, table);
515 }
516 }
517 }
518}