Skip to main content

lace_tokio/
operations.rs

1//! Application-facing bounded/open and porcelain operations.
2
3use super::driver::DriverCore;
4use super::policy::{all_records, private_operands};
5use super::runtime::*;
6use super::*;
7use lace_sans_io::logic::{rx_disabled, tx_disabled};
8use lace_sans_io::runtime_values::{OpenEvent, PeerDiagnostics, finish_bounded, store_outcome};
9
10pub use bounded::BoundedInterlace;
11
12mod bounded;
13#[cfg(test)]
14mod store_stream_tests;
15#[cfg(test)]
16mod tests;
17
18#[must_use = "drive or drop the open interlace"]
19pub struct OpenInterlace {
20    pub(super) driver: DriverCore,
21}
22
23pub struct ForEachStream {
24    pub(super) interlace: OpenInterlace,
25    observer: Lace,
26}
27
28/// The open counterpart of [`store`]: offers records one at a time; see
29/// [`store_stream`].
30pub struct StoreStream {
31    interlace: OpenInterlace,
32    provider: Lace,
33    /// The failure that ended the stream, returned by later calls; while an
34    /// offer runs, the failure a cancelled offer leaves.
35    failure: Option<Error>,
36}
37
38/// One route for the bare-position operand pair by participant position. A
39/// `LocalPeer` position supplies its own operand instead; direct places both in
40/// slot order; connected keeps the local participant's operand.
41fn route(
42    first: Participant,
43    second: Participant,
44    mut operands: [InterlaceModule; 2],
45    limits: InterlaceResourceLimits,
46) -> Result<OpenInterlace> {
47    let local_peer = match (&first, &second) {
48        (Participant::LocalPeer(_), Participant::LocalPeer(_)) => {
49            return Err(Error::policy("an interlace accepts at most one LocalPeer"));
50        }
51        (Participant::LocalPeer(_), Participant::Connected(_))
52        | (Participant::Connected(_), Participant::LocalPeer(_)) => {
53            return Err(Error::policy(
54                "a LocalPeer cannot interlace with a ConnectedPeer",
55            ));
56        }
57        (Participant::LocalPeer(peer), _) => Some((0, peer.operand().clone())),
58        (_, Participant::LocalPeer(peer)) => Some((1, peer.operand().clone())),
59        _ => None,
60    };
61    if let Some((slot, operand)) = &local_peer {
62        operands[*slot] = operand.clone();
63    }
64    let config = if first.is_local() && second.is_local() {
65        OperationConfig::Direct(Box::new(
66            DirectConfig::new(operands, empty_direct_authorities()).with_limits(limits),
67        ))
68    } else {
69        let [first_operand, second_operand] = operands;
70        let local_operand = if first.is_local() {
71            first_operand
72        } else {
73            second_operand
74        };
75        validate_operand_blob_size(&local_operand)?;
76        OperationConfig::Connected {
77            local_operand,
78            limits,
79        }
80    };
81    Ok(OpenInterlace {
82        driver: DriverCore::new(
83            claim_participant(first)?,
84            claim_participant(second)?,
85            config,
86            local_peer,
87        )?,
88    })
89}
90
91fn exhausted() -> Error {
92    Error::integration("interlace exhausted before fixed point")
93}
94
95/// Drive a private capture against `target`, handing each record admitted to the
96/// capture to `saved`, then close the capture.
97async fn capture(
98    capture: Lace,
99    target: Participant,
100    policy: impl IntoPolicy,
101    limits: InterlaceResourceLimits,
102    saved: impl FnMut(Record),
103) -> Result<BoundedInterlaceOutcome> {
104    let operands = private_operands(policy.into_policy()?, tx_disabled())?;
105    let first = Participant::from(capture.handle());
106    let mut operation = BoundedInterlace::owning(route(first, target, operands, limits)?);
107    let collected = operation.collect(saved).await;
108    drop(operation);
109    Ok(finish_bounded(
110        collected,
111        capture.close().await.err(),
112        PeerDiagnostics::default(),
113    ))
114}
115
116pub async fn bounded_interlace(
117    first: impl Into<Participant>,
118    second: impl Into<Participant>,
119    policy: impl IntoPolicy,
120) -> Result<BoundedInterlaceOutcome> {
121    bounded_interlace_with_options(first, second, policy, InterlaceOperationOptions::default())
122        .await
123}
124pub async fn bounded_interlace_with_options(
125    first: impl Into<Participant>,
126    second: impl Into<Participant>,
127    policy: impl IntoPolicy,
128    operation: InterlaceOperationOptions,
129) -> Result<BoundedInterlaceOutcome> {
130    Ok(BoundedInterlace::new(first, second, policy, operation)?
131        .run()
132        .await)
133}
134
135pub async fn open_interlace(
136    first: impl Into<Participant>,
137    second: impl Into<Participant>,
138    policy: impl IntoPolicy,
139) -> Result<OpenInterlace> {
140    open_interlace_with_options(first, second, policy, InterlaceOperationOptions::default()).await
141}
142pub async fn open_interlace_with_options(
143    first: impl Into<Participant>,
144    second: impl Into<Participant>,
145    policy: impl IntoPolicy,
146    operation: InterlaceOperationOptions,
147) -> Result<OpenInterlace> {
148    let module = policy.into_policy()?;
149    let operands = [module.clone(), module];
150    route(first.into(), second.into(), operands, operation.limits)
151}
152/// Open an interlace between a local Lace `selector` chooses, position First,
153/// and `peer`, position Second. Every logical session takes its operand and
154/// optional expected peer By from `selector`, called with the port of that
155/// session's peer preface; the first call also chooses the Lace, which serves
156/// the whole carrier, so a later call must return it again. `None`, or
157/// another Lace, refuses the session as `PeerProtocol`. Until the first
158/// selection the operation owns the transport and its prefacing deadline but
159/// no Lace. `peer` must not carry an expected peer By, and the operation takes
160/// no policy replacement.
161pub async fn open_interlace_with_selector(
162    peer: ConnectedPeer,
163    selector: impl FnMut(Option<&PortText>) -> Option<(LaceHandle, SessionOperand)> + Send + 'static,
164    operation: InterlaceOperationOptions,
165) -> Result<OpenInterlace> {
166    let peer = claim_participant(peer.into())?;
167    Ok(OpenInterlace {
168        driver: DriverCore::selecting(peer, Box::new(selector), operation.limits)?,
169    })
170}
171impl OpenInterlace {
172    /// The port the peer's latest preface sent; `None` on a direct pair,
173    /// before the first peer preface, or when the peer sent none.
174    pub fn peer_port(&self) -> Option<PortText> {
175        self.driver.peer_port()
176    }
177    /// The By the live peer proved in the latest logical session under
178    /// confidential or channel-bound assurance, for diagnostics; `None` on a
179    /// direct pair, before that proof validates, or without one.
180    pub fn peer_by(&self) -> Option<ByValue> {
181        self.driver.peer_by()
182    }
183    /// The 065 encoding of the local-to-peer direction the latest preface
184    /// pair selected, for diagnostics; `None` on a direct pair or before the
185    /// first peer preface.
186    pub fn outgoing_encoding(&self) -> Option<StreamEncoding> {
187        self.driver.outgoing_encoding()
188    }
189    /// The 065 encoding of the peer-to-local direction, as
190    /// [`Self::outgoing_encoding`].
191    pub fn incoming_encoding(&self) -> Option<StreamEncoding> {
192        self.driver.incoming_encoding()
193    }
194    /// Prepare and synchronously stage a hard policy replacement.
195    pub fn replace_policy(&mut self, policy: impl IntoPolicy) -> Result<()> {
196        let module = policy.into_policy()?;
197        self.driver.replace_policy(module)
198    }
199    /// Return one observation, one terminal error, or post-terminal exhaustion.
200    pub async fn next_event(&mut self) -> Result<Option<OpenEvent>> {
201        self.driver.next_event().await
202    }
203    /// Cancel this operation, settle started work and connected transport
204    /// ownership, and return its final local-close boundary.
205    pub async fn close(self) -> Result<OpenBoundaryOutcome> {
206        let reason = self.driver.close_cancelled().await?;
207        Ok(OpenBoundaryOutcome {
208            boundary: OpenBoundary::Closed(reason.clone()),
209            observations: vec![OpenEvent::Closed { reason }],
210        })
211    }
212    /// Consume `next_event` through the next fixed point or clean closure.
213    pub async fn run_until_fixed_point(&mut self) -> Result<OpenBoundaryOutcome> {
214        let mut observations = Vec::new();
215        loop {
216            match self.next_event().await? {
217                Some(event @ OpenEvent::FixedPoint) => {
218                    observations.push(event);
219                    return Ok(OpenBoundaryOutcome {
220                        boundary: OpenBoundary::FixedPoint,
221                        observations,
222                    });
223                }
224                Some(OpenEvent::Closed { reason }) => {
225                    observations.push(OpenEvent::Closed {
226                        reason: reason.clone(),
227                    });
228                    return Ok(OpenBoundaryOutcome {
229                        boundary: OpenBoundary::Closed(reason),
230                        observations,
231                    });
232                }
233                Some(event) => observations.push(event),
234                None => return Err(exhausted()),
235            }
236        }
237    }
238}
239
240pub async fn list(target: impl Into<Participant>, policy: impl IntoPolicy) -> Result<ListOutcome> {
241    list_with_options(target, policy, InterlaceOperationOptions::default()).await
242}
243pub async fn list_with_options(
244    target: impl Into<Participant>,
245    policy: impl IntoPolicy,
246    operation: InterlaceOperationOptions,
247) -> Result<ListOutcome> {
248    let mut records = Vec::new();
249    let outcome = capture(
250        Lace::empty_memory(),
251        target.into(),
252        policy,
253        operation.limits,
254        |record| records.push(record),
255    )
256    .await?;
257    Ok(ListOutcome {
258        terminal: outcome.terminal,
259        observations: outcome.observations,
260        records,
261    })
262}
263
264pub async fn get(target: impl Into<Participant>, policy: impl IntoPolicy) -> Result<GetOutcome> {
265    get_with_options(target, policy, InterlaceOperationOptions::default()).await
266}
267pub async fn get_with_options(
268    target: impl Into<Participant>,
269    policy: impl IntoPolicy,
270    operation: InterlaceOperationOptions,
271) -> Result<GetOutcome> {
272    let mut first = None;
273    let outcome = capture(
274        Lace::empty_memory(),
275        target.into(),
276        policy,
277        operation.limits,
278        |record| {
279            first.get_or_insert(record);
280        },
281    )
282    .await?;
283    Ok(GetOutcome {
284        terminal: outcome.terminal,
285        observations: outcome.observations,
286        record: first,
287    })
288}
289
290pub async fn store(
291    target: impl Into<Participant>,
292    records: impl IntoRecordBatch,
293) -> Result<StoreOutcome> {
294    store_with_options(target, records, InterlaceOperationOptions::default()).await
295}
296pub async fn store_with_options(
297    target: impl Into<Participant>,
298    records: impl IntoRecordBatch,
299    operation: InterlaceOperationOptions,
300) -> Result<StoreOutcome> {
301    store_inner(target.into(), records.into_records(), operation.limits).await
302}
303async fn store_inner(
304    target: Participant,
305    records: Vec<Record>,
306    limits: InterlaceResourceLimits,
307) -> Result<StoreOutcome> {
308    if records.is_empty() {
309        return Ok(StoreOutcome {
310            terminal: Terminal::Complete,
311            observations: Vec::new(),
312            records: Vec::new(),
313        });
314    }
315    let supplied = records
316        .iter()
317        .map(|record| record.hash())
318        .collect::<Vec<_>>();
319    let operands = private_operands(all_records().clone(), rx_disabled())?;
320    let source = Lace::memory(records);
321    let first = Participant::from(source.handle());
322    let mut operation = BoundedInterlace::owning(route(first, target, operands, limits)?);
323    let collected = operation.collect(drop).await;
324    drop(operation);
325    let outcome = finish_bounded(
326        collected,
327        source.close().await.err(),
328        PeerDiagnostics::default(),
329    );
330    Ok(store_outcome(outcome, supplied))
331}
332
333/// Open `store`'s interlace against `target` with an empty private provider
334/// and drive it to its first fixed point; then [`StoreStream::offer`] each
335/// record.
336pub async fn store_stream(target: impl Into<Participant>) -> Result<StoreStream> {
337    store_stream_with_options(target, InterlaceOperationOptions::default()).await
338}
339pub async fn store_stream_with_options(
340    target: impl Into<Participant>,
341    operation: InterlaceOperationOptions,
342) -> Result<StoreStream> {
343    let target = target.into();
344    let operands = private_operands(all_records().clone(), rx_disabled())?;
345    let provider = Lace::empty_memory();
346    let first = Participant::from(provider.handle());
347    let interlace = match route(first, target, operands, operation.limits) {
348        Ok(interlace) => interlace,
349        Err(error) => {
350            return Err(
351                Error::combine([error].into_iter().chain(provider.close().await.err())).unwrap(),
352            );
353        }
354    };
355    let mut stream = StoreStream {
356        interlace,
357        provider,
358        failure: None,
359    };
360    match stream.fixed_point(None).await {
361        Ok(_) => Ok(stream),
362        Err(error) => {
363            stream.failure = Some(error.clone());
364            Err(stream.close().await.err().unwrap_or(error))
365        }
366    }
367}
368impl StoreStream {
369    /// Store `record` into the provider and drive to the next fixed point:
370    /// `Saved` when the provider sent it, `Skipped` when it did not or the
371    /// provider already held it. An error, or cancelling this future, ends
372    /// the stream; later calls return the error, and records saved before it
373    /// stay saved.
374    pub async fn offer(&mut self, record: Record) -> Result<StoreRecordDisposition> {
375        if let Some(error) = &self.failure {
376            return Err(error.clone());
377        }
378        self.failure = Some(Error::integration(
379            "a cancelled offer left the store stream unsettled",
380        ));
381        let result = self.offer_inner(record).await;
382        self.failure = result.as_ref().err().cloned();
383        result
384    }
385
386    async fn offer_inner(&mut self, record: Record) -> Result<StoreRecordDisposition> {
387        let hash = record.hash();
388        let outcome = self
389            .provider
390            .owner
391            .start_store_execution_at(
392                StoreOp::StoreRecords {
393                    records: vec![record],
394                },
395                Some(ParticipantPosition::First),
396            )
397            .receive()
398            .await?;
399        if let Some(error) = outcome.failure {
400            return Err(error);
401        }
402        match outcome.response {
403            Some(Ok(StoreResult::StoredRecords { outcomes, .. })) => match outcomes.as_slice() {
404                [StoreRecordOutcome::Duplicate { .. }] => Ok(StoreRecordDisposition::Skipped),
405                [StoreRecordOutcome::Stored { .. }] => Ok(if self.fixed_point(Some(hash)).await? {
406                    StoreRecordDisposition::Saved
407                } else {
408                    StoreRecordDisposition::Skipped
409                }),
410                _ => Err(Error::integration(
411                    "provider store returned the wrong outcomes",
412                )),
413            },
414            _ => Err(Error::integration(
415                "provider store returned the wrong result",
416            )),
417        }
418    }
419
420    /// Drive to the next fixed point; whether the provider sent `hash`
421    /// before it.
422    async fn fixed_point(&mut self, hash: Option<RecordHash>) -> Result<bool> {
423        let mut sent = false;
424        loop {
425            match self.interlace.next_event().await? {
426                Some(OpenEvent::FixedPoint) => return Ok(sent),
427                Some(OpenEvent::RecordSent {
428                    position: ParticipantPosition::First,
429                    record_hash,
430                }) => sent |= Some(record_hash) == hash,
431                Some(OpenEvent::Closed { reason }) => {
432                    return Err(Error::peer_protocol(format!(
433                        "store stream closed before an offer's fixed point: {reason:?}"
434                    )));
435                }
436                Some(_) => {}
437                None => return Err(exhausted()),
438            }
439        }
440    }
441
442    /// Cancel the interlace and close the provider, returning the failure
443    /// that ended the stream with any cleanup failure.
444    pub async fn close(self) -> Result<()> {
445        let StoreStream {
446            interlace,
447            provider,
448            failure,
449        } = self;
450        let interlace = interlace.close().await.err();
451        let provider = provider.close().await.err();
452        Error::combine(failure.into_iter().chain(interlace).chain(provider)).map_or(Ok(()), Err)
453    }
454}
455
456pub async fn for_each(
457    target: impl Into<Participant>,
458    policy: impl IntoPolicy,
459) -> Result<ForEachStream> {
460    for_each_with_options(target, policy, InterlaceOperationOptions::default()).await
461}
462pub async fn for_each_with_options(
463    target: impl Into<Participant>,
464    policy: impl IntoPolicy,
465    operation: InterlaceOperationOptions,
466) -> Result<ForEachStream> {
467    let target = target.into();
468    let observer = Lace::empty_memory();
469    let operands = private_operands(policy.into_policy()?, tx_disabled())?;
470    let first = Participant::from(observer.handle());
471    Ok(ForEachStream {
472        interlace: route(first, target, operands, operation.limits)?,
473        observer,
474    })
475}
476impl ForEachStream {
477    /// Cancel the observer operation and return its final local-close event.
478    pub async fn close(self) -> Result<OpenEvent> {
479        let ForEachStream {
480            interlace,
481            observer,
482        } = self;
483        let interlace_result = interlace.driver.close_cancelled().await;
484        let observer_result = observer.close().await;
485        if let Some(error) = Error::combine(
486            interlace_result
487                .as_ref()
488                .err()
489                .cloned()
490                .into_iter()
491                .chain(observer_result.err()),
492        ) {
493            return Err(error);
494        }
495        Ok(OpenEvent::Closed {
496            reason: interlace_result?,
497        })
498    }
499
500    pub async fn next_event(&mut self) -> Result<Option<OpenEvent>> {
501        loop {
502            match self.interlace.driver.next_event().await? {
503                Some(
504                    event @ (OpenEvent::RecordSaved {
505                        position: ParticipantPosition::First,
506                        ..
507                    }
508                    | OpenEvent::FixedPoint
509                    | OpenEvent::Closed { .. }),
510                ) => return Ok(Some(event)),
511                Some(_) => {}
512                None => return Ok(None),
513            }
514        }
515    }
516}