1use super::*;
4
5#[path = "work_failure.rs"]
6mod work_failure;
7use work_failure::LaceWorkFailure;
8
9use lace_sans_io::core::OperandCache;
10use lace_sans_io::runtime_support::io_budget::IoReservation;
11use lace_sans_io::runtime_support::record_staging::RecordStaging;
12use lace_sans_io::runtime_support::{StoreBackend, StoreExecution};
13
14type AdoptedStore = Arc<Mutex<Box<dyn StoreBackend>>>;
16
17pub(super) const EXTERNAL_POSITION_POLL_INTERVAL: std::time::Duration =
18 std::time::Duration::from_secs(1);
19
20pub struct Lace {
21 pub(super) owner: Arc<LaceOwner>,
22}
23
24pub(super) struct LaceOwner {
25 pub(super) lifecycle: Mutex<LaceLifecycle>,
26 activity: tokio::sync::Notify,
27 store_execution: StoreExecution,
28 position_may_advance_externally: bool,
29 pub(super) record_staging: Option<RecordStaging>,
31 pub(super) operand_cache: OperandCache,
34 polling: Mutex<PositionPolling>,
35 pub(super) store_position: StorePositionSignal,
36 pub(super) mutation: tokio::sync::Mutex<()>,
37}
38
39pub(super) struct LaceLifecycle {
40 store: Option<AdoptedStore>,
41 copy_reservation: Option<IoReservation>,
43 pub(super) closing: bool,
44 pub(super) active: usize,
45 retained_failure: Option<Error>,
46 terminal_failure: Option<Error>,
47}
48
49struct PositionPolling {
50 next_poll_at: tokio::time::Instant,
51 poll_in_flight: bool,
52}
53
54struct ActiveStoreWork {
55 owner: Arc<LaceOwner>,
56 active: bool,
57}
58
59pub(super) struct LaceWorkCompletion<T> {
60 value: Option<T>,
61 failure: Arc<LaceWorkFailure>,
62}
63
64impl<T> LaceWorkCompletion<T> {
65 pub(super) fn retire(mut self) -> T {
66 self.failure.retire();
67 self.value.take().expect("Lace work completion value")
68 }
69
70 pub(super) fn acknowledge(mut self) -> T {
71 self.failure.acknowledge();
72 self.value.take().expect("Lace work completion value")
73 }
74}
75
76impl<T> Drop for LaceWorkCompletion<T> {
77 fn drop(&mut self) {
78 self.failure.retire();
79 }
80}
81
82pub(super) struct LaceWorkReceiver<T> {
83 receiver: Option<tokio::sync::oneshot::Receiver<LaceWorkCompletion<T>>>,
84 failure: Arc<LaceWorkFailure>,
85 owner: Arc<LaceOwner>,
86}
87
88impl<T> LaceWorkReceiver<T> {
89 pub(super) fn retire(&self) {
90 self.failure.retire();
91 }
92
93 pub(super) async fn receive_completion(&mut self) -> Result<LaceWorkCompletion<T>> {
94 let completion = self
95 .receiver
96 .as_mut()
97 .ok_or_else(|| Error::integration("Lace work completion was already consumed"))?
98 .await
99 .map_err(|_| Error::integration("Lace-owned store work ended without a completion"))?;
100 self.receiver = None;
101 Ok(completion)
102 }
103
104 pub(super) async fn receive(&mut self) -> Result<T> {
105 self.receive_completion()
106 .await
107 .map(LaceWorkCompletion::acknowledge)
108 }
109
110 pub(super) async fn receive_retired(&mut self) -> Result<T> {
111 self.retire();
112 match self.receive_completion().await {
113 Ok(mut completion) => Ok(completion.value.take().expect("Lace work completion value")),
114 Err(error) => {
115 self.owner.retain_failure(error.clone());
116 Err(error)
117 }
118 }
119 }
120}
121
122impl<T> Drop for LaceWorkReceiver<T> {
123 fn drop(&mut self) {
124 if self.receiver.is_some() {
125 self.retire();
126 }
127 }
128}
129
130impl ActiveStoreWork {
131 fn finish(mut self, late_failure: impl FnOnce() -> Option<Error>) {
132 let mut lifecycle = self.owner.lifecycle.lock().unwrap();
133 if lifecycle.closing
134 && let Some(error) = late_failure()
135 {
136 lifecycle.retained_failure =
137 Error::combine(lifecycle.retained_failure.take().into_iter().chain([error]));
138 }
139 lifecycle.active -= 1;
140 self.active = false;
141 if lifecycle.closing && lifecycle.active == 0 {
142 self.owner.activity.notify_waiters();
143 }
144 }
145}
146
147impl Drop for ActiveStoreWork {
148 fn drop(&mut self) {
149 if !self.active {
150 return;
151 }
152 let mut lifecycle = self.owner.lifecycle.lock().unwrap();
153 lifecycle.active -= 1;
154 if lifecycle.closing && lifecycle.active == 0 {
155 self.owner.activity.notify_waiters();
156 }
157 }
158}
159
160#[derive(Clone, Debug)]
161pub(super) enum LaceStoreState {
162 Position(StorePosition),
163 Failed(Error),
164}
165
166pub(super) struct StorePositionSignal {
167 state: Mutex<LaceStoreState>,
168 sender: watch::Sender<LaceStoreState>,
169}
170
171impl StorePositionSignal {
172 fn new(initial: StorePosition) -> Self {
173 let state = LaceStoreState::Position(initial);
174 let (sender, _) = watch::channel(state.clone());
175 Self {
176 state: Mutex::new(state),
177 sender,
178 }
179 }
180 pub(super) fn subscribe(&self) -> watch::Receiver<LaceStoreState> {
181 self.sender.subscribe()
182 }
183 pub(super) fn latest(&self) -> Result<StorePosition> {
184 match &*self.state.lock().unwrap() {
185 LaceStoreState::Position(position) => Ok(*position),
186 LaceStoreState::Failed(error) => Err(error.clone()),
187 }
188 }
189 fn publish(&self, position: StorePosition) -> Result<()> {
190 let mut state = self.state.lock().unwrap();
191 let previous = match &*state {
192 LaceStoreState::Position(previous) => *previous,
193 LaceStoreState::Failed(error) => return Err(error.clone()),
194 };
195 if position < previous {
196 return Err(Error::integration(format!(
197 "StorePosition publication regressed from {} to {}",
198 previous.raw(),
199 position.raw()
200 )));
201 }
202 if position == previous {
203 return Ok(());
204 }
205 *state = LaceStoreState::Position(position);
206 self.sender.send_replace(state.clone());
207 Ok(())
208 }
209 fn fail(&self, error: Error) {
210 let mut state = self.state.lock().unwrap();
211 let error = match &*state {
212 LaceStoreState::Failed(retained) => {
213 let combined = retained.clone().with_cleanup(error);
214 if &combined == retained {
215 return;
216 }
217 combined
218 }
219 LaceStoreState::Position(_) => error,
220 };
221 *state = LaceStoreState::Failed(error);
222 self.sender.send_replace(state.clone());
223 }
224}
225
226pub(super) struct StoreExecutionOutcome {
227 pub(super) response: Option<std::result::Result<StoreResult, StoreError>>,
232 pub(super) failure: Option<Error>,
235}
236
237impl LaceOwner {
238 pub(super) fn retain_failure(&self, error: Error) {
239 let mut lifecycle = self.lifecycle.lock().unwrap();
240 lifecycle.retained_failure =
241 Error::combine(lifecycle.retained_failure.take().into_iter().chain([error]));
242 }
243
244 pub(super) fn terminal_failure(&self) -> Option<Error> {
245 self.lifecycle.lock().unwrap().terminal_failure.clone()
246 }
247
248 fn poison(&self, error: Error) -> Error {
249 let failure = {
250 let mut lifecycle = self.lifecycle.lock().unwrap();
251 let failure =
252 Error::combine(lifecycle.terminal_failure.take().into_iter().chain([error]))
253 .expect("one terminal failure");
254 lifecycle.terminal_failure = Some(failure.clone());
255 failure
256 };
257 self.store_position.fail(failure.clone());
258 failure
259 }
260
261 fn begin_execution(
262 self: &Arc<Self>,
263 ) -> std::result::Result<(AdoptedStore, ActiveStoreWork), Error> {
264 let mut lifecycle = self.lifecycle.lock().unwrap();
265 if let Some(error) = &lifecycle.terminal_failure {
266 return Err(error.clone());
267 }
268 if lifecycle.closing {
269 return Err(Error::store(None, None, "Lace is closing"));
270 }
271 let store = lifecycle
272 .store
273 .as_ref()
274 .ok_or_else(|| Error::store(None, None, "Lace is closed"))?
275 .clone();
276 lifecycle.active += 1;
277 Ok((
278 store,
279 ActiveStoreWork {
280 owner: self.clone(),
281 active: true,
282 },
283 ))
284 }
285
286 pub(super) fn claim(&self) -> Result<()> {
287 let lifecycle = self.lifecycle.lock().unwrap();
288 if let Some(error) = &lifecycle.terminal_failure {
289 return Err(error.clone());
290 }
291 if lifecycle.closing || lifecycle.store.is_none() {
292 return Err(Error::policy("LaceHandle is closed"));
293 }
294 Ok(())
295 }
296
297 fn publish_confirmed_position(&self, position: StorePosition) -> Result<()> {
298 let mut lifecycle = self.lifecycle.lock().unwrap();
299 if let Some(error) = &lifecycle.terminal_failure {
300 return Err(error.clone());
301 }
302 match self.store_position.publish(position) {
303 Ok(()) => Ok(()),
304 Err(error) => {
305 lifecycle.terminal_failure = Some(error.clone());
306 drop(lifecycle);
307 self.store_position.fail(error.clone());
308 Err(error)
309 }
310 }
311 }
312
313 #[cfg(test)]
314 pub(super) fn force_external_poll_due(&self) {
315 self.polling.lock().unwrap().next_poll_at = tokio::time::Instant::now();
316 }
317
318 #[cfg(test)]
319 pub(super) fn external_poll_in_flight(&self) -> bool {
320 self.polling.lock().unwrap().poll_in_flight
321 }
322
323 #[cfg(test)]
324 pub(super) fn publish_position_for_test(&self, position: StorePosition) -> Result<()> {
325 self.publish_confirmed_position(position)
326 }
327
328 #[cfg(test)]
329 pub(super) fn start_store_execution(self: &Arc<Self>, op: StoreOp) -> StoreOpFuture {
330 self.start_store_execution_at(op, None)
331 }
332
333 pub(super) fn start_store_execution_at(
334 self: &Arc<Self>,
335 op: StoreOp,
336 position: Option<ParticipantPosition>,
337 ) -> StoreOpFuture {
338 let (sender, receiver) = tokio::sync::oneshot::channel();
339 let failure = LaceWorkFailure::new(self.clone());
340 let work_failure = failure.clone();
341 let owner = self.clone();
342 match self.begin_execution() {
343 Ok((store, execution)) => {
344 let mutation = matches!(op, StoreOp::StoreRecords { .. });
345 tokio::spawn(async move {
346 let worker_owner = owner.clone();
347 let worker = tokio::spawn(async move {
348 worker_owner.execute_started(store, op, position).await
349 });
350 let (outcome, late_failure) = match worker.await {
351 Ok(outcome) => {
352 let late_failure = mutation.then(|| outcome.failure.clone()).flatten();
353 (outcome, late_failure)
354 }
355 Err(error) => {
356 let error = Error::integration(format!(
357 "Lace-owned store execution task failed: {error}"
358 ));
359 (
360 StoreExecutionOutcome {
361 response: None,
362 failure: Some(error.clone()),
363 },
364 Some(error),
365 )
366 }
367 };
368 work_failure.complete(late_failure);
369 let completion = LaceWorkCompletion {
370 value: Some(outcome),
371 failure: work_failure.clone(),
372 };
373 let _ = sender.send(completion);
374 execution.finish(|| work_failure.take_retired());
375 });
376 }
377 Err(error) => {
378 let _ = sender.send(LaceWorkCompletion {
379 value: Some(StoreExecutionOutcome {
380 response: None,
381 failure: Some(error),
382 }),
383 failure: work_failure,
384 });
385 }
386 }
387 LaceWorkReceiver {
388 receiver: Some(receiver),
389 failure,
390 owner: self.clone(),
391 }
392 }
393
394 async fn execute_started(
395 &self,
396 store: AdoptedStore,
397 op: StoreOp,
398 position: Option<ParticipantPosition>,
399 ) -> StoreExecutionOutcome {
400 let _store_access = self.mutation.lock().await;
404 let mutation = matches!(op, StoreOp::StoreRecords { .. });
405 let response = {
406 let store = store.clone();
407 run_store(self.store_execution, move || {
408 lock_store(&store)?.execute(op)
409 })
410 .await
411 };
412 let store_error = |error: &StoreError| Error::store(position, None, format!("{error:?}"));
413 if !mutation {
414 let failure = response.as_ref().err().map(store_error);
415 return StoreExecutionOutcome {
416 response: Some(response),
417 failure,
418 };
419 }
420 match run_store(self.store_execution, move || {
421 lock_store(&store)?.current_position()
422 })
423 .await
424 {
425 Ok(confirmed) => {
426 let publication = self.publish_confirmed_position(confirmed).err();
427 let failure = Error::combine(
428 publication
429 .into_iter()
430 .chain(response.as_ref().err().map(store_error)),
431 );
432 StoreExecutionOutcome {
433 response: Some(response),
434 failure,
435 }
436 }
437 Err(refresh) => StoreExecutionOutcome {
438 failure: Error::combine(
439 [Error::store(
440 position,
441 None,
442 format!("failed to establish CurrentStorePosition: {refresh:?}"),
443 )]
444 .into_iter()
445 .chain(response.as_ref().err().map(store_error)),
446 ),
447 response: None,
448 },
449 }
450 }
451
452 #[cfg(test)]
453 pub(super) async fn execute(self: &Arc<Self>, op: StoreOp) -> StoreExecutionOutcome {
454 let mut work = self.start_store_execution_at(op, None);
455 work.receive()
456 .await
457 .unwrap_or_else(|error| StoreExecutionOutcome {
458 response: None,
459 failure: Some(error),
460 })
461 }
462
463 pub(super) fn coordinate_external_position_poll(
464 self: &Arc<Self>,
465 now: tokio::time::Instant,
466 ) -> Result<Option<tokio::time::Instant>> {
467 if !self.position_may_advance_externally {
468 return Ok(None);
469 }
470 let mut lifecycle = self.lifecycle.lock().unwrap();
471 if let Some(error) = &lifecycle.terminal_failure {
472 return Err(error.clone());
473 }
474 if lifecycle.closing || lifecycle.store.is_none() {
475 return Err(Error::store(None, None, "Lace is closing"));
476 }
477 let mut polling = self.polling.lock().unwrap();
478 if now < polling.next_poll_at {
479 return Ok(Some(polling.next_poll_at));
480 }
481 if polling.poll_in_flight {
482 return Ok(Some(now + EXTERNAL_POSITION_POLL_INTERVAL));
483 }
484
485 let store = lifecycle.store.as_ref().expect("checked store").clone();
486 lifecycle.active += 1;
487 let execution = ActiveStoreWork {
488 owner: self.clone(),
489 active: true,
490 };
491 polling.poll_in_flight = true;
492 polling.next_poll_at = now + EXTERNAL_POSITION_POLL_INTERVAL;
493 drop(polling);
494 drop(lifecycle);
495
496 let owner = self.clone();
497 tokio::spawn(async move {
498 let worker_owner = owner.clone();
499 let worker = tokio::spawn(async move {
500 let _mutation = worker_owner.mutation.lock().await;
501 let execution = worker_owner.store_execution;
502 match run_store(execution, move || lock_store(&store)?.current_position()).await {
503 Ok(position) => worker_owner.publish_confirmed_position(position),
504 Err(error) => {
505 let error = store_runtime_error(error);
506 Err(worker_owner.poison(error))
507 }
508 }
509 });
510 let result = worker.await.unwrap_or_else(|error| {
511 Err(Error::integration(format!(
512 "Lace-owned position refresh task failed: {error}"
513 )))
514 });
515 if let Err(error) = result {
516 owner.poison(error);
517 }
518 execution.finish(|| None);
519 {
520 let mut polling = owner.polling.lock().unwrap();
521 polling.poll_in_flight = false;
522 polling.next_poll_at =
523 tokio::time::Instant::now() + EXTERNAL_POSITION_POLL_INTERVAL;
524 }
525 owner.activity.notify_waiters();
526 });
527 Ok(Some(now + EXTERNAL_POSITION_POLL_INTERVAL))
528 }
529
530 pub(super) async fn settle_external_position_poll(&self) -> Option<Error> {
531 loop {
532 let notified = self.activity.notified();
533 if !self.polling.lock().unwrap().poll_in_flight {
534 break;
535 }
536 notified.await;
537 }
538 self.terminal_failure()
539 }
540
541 async fn close(self: &Arc<Self>) -> Result<()> {
542 {
543 let mut lifecycle = self.lifecycle.lock().unwrap();
544 lifecycle.closing = true;
545 }
546 loop {
547 let notified = self.activity.notified();
548 if self.lifecycle.lock().unwrap().active == 0 {
549 break;
550 }
551 notified.await;
552 }
553 let (store, copy_reservation, terminal_failure, retained_failure) = {
554 let mut lifecycle = self.lifecycle.lock().unwrap();
555 (
556 lifecycle.store.take(),
557 lifecycle.copy_reservation.take(),
558 lifecycle.terminal_failure.clone(),
559 lifecycle.retained_failure.take(),
560 )
561 };
562 let close_result = match store {
563 Some(store) => match Arc::try_unwrap(store) {
564 Ok(store) => run_store(self.store_execution, move || {
565 let closed = store
566 .into_inner()
567 .unwrap_or_else(std::sync::PoisonError::into_inner)
568 .close();
569 drop(copy_reservation);
570 closed
571 })
572 .await
573 .map_err(store_runtime_error),
574 Err(_) => Err(Error::store(
575 None,
576 None,
577 "store execution remained after Lace close drain",
578 )),
579 },
580 None => Ok(()),
581 };
582 Error::combine(
583 [terminal_failure, retained_failure, close_result.err()]
584 .into_iter()
585 .flatten(),
586 )
587 .map_or(Ok(()), Err)
588 }
589}
590
591async fn run_store<T: Send + 'static>(
593 execution: StoreExecution,
594 work: impl FnOnce() -> std::result::Result<T, StoreError> + Send + 'static,
595) -> std::result::Result<T, StoreError> {
596 match execution {
597 StoreExecution::Inline => work(),
598 StoreExecution::Blocking => match tokio::task::spawn_blocking(work).await {
599 Ok(result) => result,
600 Err(error) => match error.try_into_panic() {
602 Ok(panic) => std::panic::resume_unwind(panic),
603 Err(error) => Err(StoreError::StoreFailed(format!(
604 "store task failed: {error}"
605 ))),
606 },
607 },
608 }
609}
610
611fn lock_store(
612 store: &AdoptedStore,
613) -> std::result::Result<std::sync::MutexGuard<'_, Box<dyn StoreBackend>>, StoreError> {
614 store
615 .lock()
616 .map_err(|_| StoreError::StoreFailed("store lock is poisoned".into()))
617}
618
619fn store_runtime_error(error: StoreError) -> Error {
620 Error::store(None, None, format!("{error:?}"))
621}
622
623pub(super) type StoreOpFuture = LaceWorkReceiver<StoreExecutionOutcome>;
624
625pub(super) fn connected_inputs(
626 options: ConnectedPeerOptions,
627) -> Result<Box<ConnectedCarrierInputs>> {
628 options.validate()?;
629 let (by_secret, peer_proof, expected_peer_by, stream_encodings, port) = options.into_parts();
630 let nonce_seed = lace_sans_io::core::NonceSeed::generate().map_err(|error| {
631 Error::integration(format!(
632 "failed to generate ILTP carrier nonce seed: {error}"
633 ))
634 })?;
635 Ok(Box::new(ConnectedCarrierInputs {
636 by_secret,
637 expected_peer_by,
638 peer_proof,
639 stream_encodings,
640 port,
641 nonce_seed,
642 }))
643}
644
645impl Lace {
646 #[cfg(all(target_pointer_width = "64", any(unix, windows)))]
649 pub fn filesystem(path: impl AsRef<std::path::Path>) -> Result<Self> {
650 let store =
651 lace_sans_io::runtime_support::native_filesystem_store::NativeFilesystemStore::open(
652 path,
653 )
654 .map_err(|error| store_runtime_error(error.into()))?;
655 Self::try_from_store(Box::new(store), false)
656 }
657 pub fn portable(
662 path: impl AsRef<std::path::Path>,
663 format: crate::PortableStoreFormat,
664 ) -> Result<Self> {
665 let store = lace_sans_io::runtime_support::portable_file_store::PortableFileStore::open(
666 path, format,
667 )
668 .map_err(store_runtime_error)?;
669 Self::try_from_store(Box::new(store), false)
670 }
671 #[cfg(feature = "lmdb")]
673 pub fn lmdb(path: impl AsRef<std::path::Path>) -> Result<Self> {
674 let store = lmdb_lace::NativeLmdbStore::open(path).map_err(store_runtime_error)?;
675 Self::try_from_store(Box::new(store), true)
676 }
677 pub(super) fn try_from_store(
678 mut store: Box<dyn StoreBackend>,
679 position_may_advance_externally: bool,
680 ) -> Result<Self> {
681 let initial = store.current_position().map_err(store_runtime_error)?;
682 let store_execution = store.execution();
683 let chunk_bytes = crate::io::IO.chunk_bytes();
684 let record_staging = store.adopt(chunk_bytes);
685 let copy_reservation = match record_staging {
686 Some(_) => Some(crate::io::IO.reserve(chunk_bytes)?),
687 None => None,
688 };
689 Ok(Self {
690 owner: Arc::new(LaceOwner {
691 lifecycle: Mutex::new(LaceLifecycle {
692 store: Some(Arc::new(Mutex::new(store))),
693 copy_reservation,
694 closing: false,
695 active: 0,
696 retained_failure: None,
697 terminal_failure: None,
698 }),
699 activity: tokio::sync::Notify::new(),
700 store_execution,
701 position_may_advance_externally,
702 record_staging,
703 operand_cache: OperandCache::default(),
704 polling: Mutex::new(PositionPolling {
705 next_poll_at: tokio::time::Instant::now() + EXTERNAL_POSITION_POLL_INTERVAL,
706 poll_in_flight: false,
707 }),
708 store_position: StorePositionSignal::new(initial),
709 mutation: tokio::sync::Mutex::new(()),
710 }),
711 })
712 }
713 #[cfg(test)]
714 pub(super) fn from_store(store: Box<dyn StoreBackend>) -> Self {
715 Self::try_from_store(store, false).expect("test store must report its initial position")
716 }
717 #[cfg(test)]
718 pub(super) fn from_externally_advancing_store(store: Box<dyn StoreBackend>) -> Self {
719 Self::try_from_store(store, true).expect("test store must report its initial position")
720 }
721 pub fn memory(records: impl IntoIterator<Item = Record>) -> Self {
722 Self::try_from_store(
723 Box::new(InMemoryStore::source(records.into_iter().collect())),
724 false,
725 )
726 .expect("in-memory store must report its initial position")
727 }
728 pub fn empty_memory() -> Self {
729 Self::memory(Vec::<Record>::new())
730 }
731 pub fn memory_with_capacity(
738 records: impl IntoIterator<Item = Record>,
739 bytes: usize,
740 ) -> Result<Self> {
741 let mut store = InMemoryStore::with_capacity(bytes);
742 store.seed(records.into_iter().collect()).map_err(|_| {
743 Error::detailed(
744 ErrorCategory::LimitExceeded,
745 None,
746 None,
747 format!("the records exceed the memory Lace's capacity of {bytes} bytes"),
748 )
749 })?;
750 Self::try_from_store(Box::new(store), false)
751 }
752 pub fn with_operand_cache(self, entries: usize) -> Self {
756 self.owner.operand_cache.set_capacity(entries);
757 self
758 }
759 pub fn handle(&self) -> LaceHandle {
760 LaceHandle {
761 owner: self.owner.clone(),
762 }
763 }
764 pub fn held_bytes(&self) -> Option<usize> {
768 let store = self.owner.lifecycle.lock().unwrap().store.clone()?;
769 let guard = store.lock().unwrap();
770 guard.held_bytes()
771 }
772
773 pub async fn close(self) -> Result<()> {
776 self.owner.close().await
777 }
778}
779
780#[derive(Clone)]
782pub struct LaceHandle {
783 pub(super) owner: Arc<LaceOwner>,
784}
785
786pub struct ConnectedPeer {
788 pub(super) lease: ConnectedTransportLease,
789 pub(super) inputs: Box<ConnectedCarrierInputs>,
790}
791
792#[derive(Clone)]
798pub struct LocalPeer {
799 handle: LaceHandle,
800 operand: InterlaceModule,
801}
802impl LocalPeer {
803 pub fn new(handle: LaceHandle, policy: impl crate::IntoPolicy) -> Result<Self> {
805 Ok(Self {
806 handle,
807 operand: policy.into_policy()?,
808 })
809 }
810 pub(super) fn operand(&self) -> &InterlaceModule {
811 &self.operand
812 }
813}
814
815pub enum Participant {
817 Local(LaceHandle),
818 LocalPeer(LocalPeer),
819 Connected(ConnectedPeer),
820}
821impl From<LaceHandle> for Participant {
822 fn from(value: LaceHandle) -> Self {
823 Self::Local(value)
824 }
825}
826impl From<LocalPeer> for Participant {
827 fn from(value: LocalPeer) -> Self {
828 Self::LocalPeer(value)
829 }
830}
831impl From<ConnectedPeer> for Participant {
832 fn from(value: ConnectedPeer) -> Self {
833 Self::Connected(value)
834 }
835}
836impl Participant {
837 pub(super) fn is_local(&self) -> bool {
838 !matches!(self, Self::Connected(_))
839 }
840}
841
842pub(super) struct ConnectedCarrierInputs {
843 pub(super) by_secret: Option<BySecret>,
844 pub(super) expected_peer_by: Option<crate::ByValue>,
845 pub(super) peer_proof: PeerProofAssurance,
846 pub(super) stream_encodings: Vec<lace_sans_io::iltp::StreamEncoding>,
847 pub(super) port: Option<crate::PortText>,
848 pub(super) nonce_seed: lace_sans_io::core::NonceSeed,
849}
850
851pub(super) struct ConnectedTransportLease {
852 pub(super) transport: Box<transport::ByteStreamCarrier>,
853 pub(super) role: CarrierRole,
854}
855
856pub(super) enum ParticipantLease {
857 Local(LaceHandle),
858 Connected {
859 peer: Box<ConnectedTransportLease>,
860 inputs: Box<ConnectedCarrierInputs>,
861 },
862}
863pub(super) fn claim_participant(participant: Participant) -> Result<ParticipantLease> {
864 match participant {
865 Participant::Local(handle) | Participant::LocalPeer(LocalPeer { handle, .. }) => {
866 handle.owner.claim()?;
867 Ok(ParticipantLease::Local(handle))
868 }
869 Participant::Connected(peer) => Ok(ParticipantLease::Connected {
870 peer: Box::new(peer.lease),
871 inputs: peer.inputs,
872 }),
873 }
874}
875
876pub(super) fn empty_direct_authorities() -> [SessionAuthority; 2] {
877 [SessionAuthority::default(), SessionAuthority::default()]
878}
879
880pub(super) enum OperationConfig {
881 Direct(Box<DirectConfig>),
882 Connected {
883 local_operand: lace_sans_io::logic::InterlaceModule,
884 limits: InterlaceResourceLimits,
885 },
886}
887
888pub(super) type Selector =
891 Box<dyn FnMut(Option<&crate::PortText>) -> Option<(LaceHandle, crate::SessionOperand)> + Send>;
892
893pub(super) enum ConnectedLocal {
897 Fixed(LaceHandle, lace_sans_io::logic::InterlaceModule),
898 Selected(Selector),
899}