1use std::{any::Any, cell::Cell, fmt, marker::PhantomData, rc::Rc};
2
3use futures::{FutureExt, future::LocalBoxFuture};
4
5use super::{
6 DiagnosticEvent, DiagnosticOutcome, DiagnosticSource, InvocationContext, NativeAppRuntime,
7 NativeStreamEndpointBinding, RequestPermit, RuntimeFailure, diagnostics::diagnostic_operation,
8 schedule_plugin_supervision_after_failure,
9};
10
11pub trait StreamCapability: 'static {
13 type OpenRequest: 'static;
15 type Message: 'static;
17 type DomainError: 'static;
19 const ID: &'static str;
21 const DESCRIPTOR_VERSION: &'static str;
23}
24
25#[derive(Clone, Debug, PartialEq)]
27pub enum StreamEvent<M, E> {
28 Message(M),
30 PeerHalfClosed,
32 Terminal(Result<(), E>),
34}
35
36pub enum NativeStreamItem {
38 Message(Box<dyn Any>),
40 PeerHalfClosed,
42 Terminal(Result<(), Box<dyn Any>>),
44}
45
46impl fmt::Debug for NativeStreamItem {
47 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::Message(_) => formatter.write_str("Message(<erased>)"),
50 Self::PeerHalfClosed => formatter.write_str("PeerHalfClosed"),
51 Self::Terminal(Ok(())) => formatter.write_str("Terminal(Ok(()))"),
52 Self::Terminal(Err(_)) => formatter.write_str("Terminal(Err(<erased>))"),
53 }
54 }
55}
56
57pub trait NativeStreamSession: fmt::Debug {
59 fn send(&self, message: Box<dyn Any>) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>>;
61 fn receive(&self) -> LocalBoxFuture<'static, Result<NativeStreamItem, RuntimeFailure>>;
63 fn close_send(&self) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>>;
65 fn cancel(&self);
67}
68
69pub type NativeStreamOpenFuture = LocalBoxFuture<
71 'static,
72 Result<Result<Box<dyn NativeStreamSession>, Box<dyn Any>>, RuntimeFailure>,
73>;
74
75pub trait NativeStreamEndpoint: fmt::Debug {
77 fn capability_id(&self) -> &'static str;
79 fn descriptor_version(&self) -> &'static str;
81 fn operations(&self) -> &'static [&'static str];
83 fn open(
85 &self,
86 operation: &str,
87 request: Box<dyn Any>,
88 context: InvocationContext,
89 ) -> NativeStreamOpenFuture;
90}
91
92#[derive(Debug)]
94pub struct NativeStreamHandle<C: StreamCapability> {
95 endpoints: Vec<NativeStreamEndpointBinding>,
96 runtime: Rc<NativeAppRuntime>,
97 caller_instance: String,
98 allow_before_ready: bool,
99 capability: PhantomData<fn() -> C>,
100}
101
102impl<C: StreamCapability> NativeStreamHandle<C> {
103 pub(crate) fn from_endpoints(
104 endpoints: &[NativeStreamEndpointBinding],
105 runtime: Rc<NativeAppRuntime>,
106 caller_instance: &str,
107 allow_before_ready: bool,
108 ) -> Self {
109 Self {
110 endpoints: endpoints.to_vec(),
111 runtime,
112 caller_instance: caller_instance.to_owned(),
113 allow_before_ready,
114 capability: PhantomData,
115 }
116 }
117
118 pub fn binding_count(&self) -> usize {
120 self.endpoints.len()
121 }
122
123 pub async fn open(
125 &self,
126 operation: &str,
127 request: C::OpenRequest,
128 ) -> Result<Result<NativeStream<C>, C::DomainError>, RuntimeFailure> {
129 let context = self.next_context();
130 self.open_with_context(operation, context, request).await
131 }
132
133 pub async fn open_with_context(
135 &self,
136 operation: &str,
137 context: InvocationContext,
138 request: C::OpenRequest,
139 ) -> Result<Result<NativeStream<C>, C::DomainError>, RuntimeFailure> {
140 let context = context
141 .for_caller(&self.caller_instance)
142 .for_target(C::ID, operation);
143 let started_at = (self.runtime.driver.now)();
144 let operation_name = self
145 .endpoints
146 .first()
147 .and_then(|endpoint| diagnostic_operation(endpoint.state.operations, operation));
148 let request_id = context.request_id();
149 self.runtime
150 .diagnostics
151 .emit(DiagnosticSource::Invocation, started_at, |_| {
152 DiagnosticEvent::InvocationStarted {
153 requirement_id: self
154 .endpoints
155 .first()
156 .map(|endpoint| endpoint.requirement_id.clone()),
157 request_id,
158 caller_instance: Some(self.caller_instance.clone()),
159 provider_instance: self
160 .endpoints
161 .first()
162 .map(|endpoint| endpoint.plugin_instance.clone()),
163 capability: C::ID,
164 operation: operation_name,
165 }
166 });
167 let result = self
168 .open_with_context_inner(operation, context, request)
169 .await;
170 let outcome = match &result {
171 Ok(Ok(_)) => DiagnosticOutcome::Succeeded,
172 Ok(Err(_)) => DiagnosticOutcome::DomainError,
173 Err(error) => DiagnosticOutcome::RuntimeFailure(error.into()),
174 };
175 self.runtime.diagnostics.emit(
176 DiagnosticSource::Invocation,
177 (self.runtime.driver.now)(),
178 |_| DiagnosticEvent::InvocationCompleted {
179 requirement_id: self
180 .endpoints
181 .first()
182 .map(|endpoint| endpoint.requirement_id.clone()),
183 request_id,
184 caller_instance: Some(self.caller_instance.clone()),
185 provider_instance: self
186 .endpoints
187 .first()
188 .map(|endpoint| endpoint.plugin_instance.clone()),
189 capability: C::ID,
190 operation: operation_name,
191 outcome,
192 elapsed: (self.runtime.driver.now)().saturating_sub(started_at),
193 },
194 );
195 if let Err(error) = &result {
196 self.runtime.diagnostics.emit_runtime_failure(
197 (self.runtime.driver.now)(),
198 self.endpoints
199 .first()
200 .map(|endpoint| endpoint.plugin_instance.as_str()),
201 error,
202 );
203 }
204 result
205 }
206
207 async fn open_with_context_inner(
208 &self,
209 operation: &str,
210 context: InvocationContext,
211 request: C::OpenRequest,
212 ) -> Result<Result<NativeStream<C>, C::DomainError>, RuntimeFailure> {
213 if self.runtime.shutdown_started.get()
214 || (!self.allow_before_ready && self.runtime.admission.is_closed())
215 {
216 return Err(RuntimeFailure::AdmissionClosed);
217 }
218 let endpoint = match self.endpoints.as_slice() {
219 [] => return Err(RuntimeFailure::Unavailable { capability: C::ID }),
220 [endpoint] => endpoint,
221 endpoints => {
222 return Err(RuntimeFailure::AmbiguousBinding {
223 capability: C::ID,
224 providers: endpoints.len(),
225 });
226 }
227 };
228 let snapshot = endpoint
229 .state
230 .snapshot()
231 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?;
232 let admission = endpoint
233 .admission(operation)
234 .ok_or_else(|| RuntimeFailure::UnknownOperation {
235 capability: C::ID,
236 operation: operation.to_owned(),
237 })?
238 .clone();
239 let permit = admission
240 .acquire(C::ID, operation, &context, &self.runtime.driver)
241 .await?;
242 if !endpoint.state.is_current(snapshot.generation) {
243 return Err(RuntimeFailure::Unavailable { capability: C::ID });
244 }
245 let generation_cancellation = snapshot.cancellation.clone();
246 let endpoint_impl = snapshot.endpoint.clone();
247 let operation_name = operation.to_owned();
248 let (outcome, permit) = super::settlement::operation(
249 &self.runtime,
250 &endpoint.plugin_instance,
251 &context,
252 snapshot.cancellation,
253 C::ID,
254 move |execution_context| {
255 async move {
256 (
257 endpoint_impl
258 .open(&operation_name, Box::new(request), execution_context)
259 .await,
260 permit,
261 )
262 }
263 .boxed_local()
264 },
265 )
266 .await
267 .map_err(|error| {
268 schedule_plugin_supervision_after_failure(
269 &self.runtime,
270 &endpoint.plugin_instance,
271 error,
272 )
273 })?;
274 let outcome = outcome.map_err(|error| {
275 schedule_plugin_supervision_after_failure(
276 &self.runtime,
277 &endpoint.plugin_instance,
278 error,
279 )
280 })?;
281 match outcome {
282 Ok(session) => Ok(Ok(NativeStream::new(
283 session,
284 self.runtime.clone(),
285 generation_cancellation,
286 endpoint.plugin_instance.clone(),
287 context,
288 permit,
289 ))),
290 Err(error) => Ok(Err(error
291 .downcast::<C::DomainError>()
292 .map(|error| *error)
293 .map_err(|_| RuntimeFailure::ProtocolViolation { capability: C::ID })?)),
294 }
295 }
296
297 fn next_context(&self) -> InvocationContext {
298 InvocationContext::new(
299 self.next_request_id(),
300 None,
301 super::CancellationToken::new(),
302 )
303 .with_caller_instance(self.caller_instance.clone())
304 }
305
306 fn next_request_id(&self) -> super::RequestId {
307 let request_id = self.runtime.request_ids.get();
308 self.runtime.request_ids.set(request_id.saturating_add(1));
309 request_id
310 }
311}
312
313#[derive(Debug)]
315pub struct NativeStream<C: StreamCapability> {
316 inner: Rc<dyn NativeStreamSession>,
317 runtime: Rc<NativeAppRuntime>,
318 generation_cancellation: super::CancellationToken,
319 plugin_instance: String,
320 context: InvocationContext,
321 _permit: RequestPermit,
322 local_half_closed: Cell<bool>,
323 peer_half_closed: Cell<bool>,
324 terminal_seen: Cell<bool>,
325 cancelled: Cell<bool>,
326 capability: PhantomData<fn() -> C>,
327}
328
329impl<C: StreamCapability> NativeStream<C> {
330 fn new(
331 session: Box<dyn NativeStreamSession>,
332 runtime: Rc<NativeAppRuntime>,
333 generation_cancellation: super::CancellationToken,
334 plugin_instance: String,
335 context: InvocationContext,
336 permit: RequestPermit,
337 ) -> Self {
338 Self {
339 inner: Rc::from(session),
340 runtime,
341 generation_cancellation,
342 plugin_instance,
343 context,
344 _permit: permit,
345 local_half_closed: Cell::new(false),
346 peer_half_closed: Cell::new(false),
347 terminal_seen: Cell::new(false),
348 cancelled: Cell::new(false),
349 capability: PhantomData,
350 }
351 }
352
353 pub async fn send(&self, message: C::Message) -> Result<(), RuntimeFailure> {
355 if let Some(error) = self.cancelled_outcome() {
356 return Err(error);
357 }
358 if self.local_half_closed.get() || self.terminal_seen.get() {
359 return Err(Self::protocol_violation());
360 }
361 let inner = self.inner.clone();
362 super::settlement::operation(
363 &self.runtime,
364 &self.plugin_instance,
365 &self.context,
366 self.generation_cancellation.clone(),
367 C::ID,
368 move |_| inner.send(Box::new(message)),
369 )
370 .await
371 .map_err(|error| self.finish_with_error(error))?
372 .map_err(|error| self.finish_with_error(error))
373 }
374
375 pub async fn receive(&self) -> Result<StreamEvent<C::Message, C::DomainError>, RuntimeFailure> {
377 if let Some(error) = self.cancelled_outcome() {
378 return Err(error);
379 }
380 if self.terminal_seen.get() {
381 return Err(Self::protocol_violation());
382 }
383 let inner = self.inner.clone();
384 let item = super::settlement::operation(
385 &self.runtime,
386 &self.plugin_instance,
387 &self.context,
388 self.generation_cancellation.clone(),
389 C::ID,
390 move |_| inner.receive(),
391 )
392 .await
393 .map_err(|error| self.finish_with_error(error))?
394 .map_err(|error| self.finish_with_error(error))?;
395 match item {
396 super::NativeStreamItem::Message(message) => {
397 if self.peer_half_closed.get() {
398 return Err(self.finish_with_error(Self::protocol_violation()));
399 }
400 message
401 .downcast::<C::Message>()
402 .map(|message| StreamEvent::Message(*message))
403 .map_err(|_| self.finish_with_error(Self::protocol_violation()))
404 }
405 super::NativeStreamItem::PeerHalfClosed => {
406 if self.peer_half_closed.replace(true) {
407 return Err(self.finish_with_error(Self::protocol_violation()));
408 }
409 Ok(StreamEvent::PeerHalfClosed)
410 }
411 super::NativeStreamItem::Terminal(outcome) => {
412 if self.terminal_seen.replace(true) {
413 return Err(self.finish_with_error(Self::protocol_violation()));
414 }
415 let outcome = match outcome {
416 Ok(()) => Ok(()),
417 Err(error) => Err(error
418 .downcast::<C::DomainError>()
419 .map(|error| *error)
420 .map_err(|_| self.finish_with_error(Self::protocol_violation()))?),
421 };
422 Ok(StreamEvent::Terminal(outcome))
423 }
424 }
425 }
426
427 pub async fn close_send(&self) -> Result<(), RuntimeFailure> {
429 if let Some(error) = self.cancelled_outcome() {
430 return Err(error);
431 }
432 if self.terminal_seen.get() || self.local_half_closed.replace(true) {
433 return Err(Self::protocol_violation());
434 }
435 let inner = self.inner.clone();
436 let result = super::settlement::operation(
437 &self.runtime,
438 &self.plugin_instance,
439 &self.context,
440 self.generation_cancellation.clone(),
441 C::ID,
442 move |_| inner.close_send(),
443 )
444 .await
445 .map_err(|error| self.finish_with_error(error))?
446 .map_err(|error| self.finish_with_error(error));
447 let resource_exhausted = result
448 .as_ref()
449 .err()
450 .is_some_and(|error| matches!(error, RuntimeFailure::ResourceExhausted { .. }));
451 if resource_exhausted {
452 self.local_half_closed.set(false);
453 }
454 result
455 }
456
457 pub fn cancel(&self) {
459 if !self.terminal_seen.get() && !self.cancelled.replace(true) {
460 self.context.cancellation().cancel();
461 self.inner.cancel();
462 }
463 }
464
465 pub const fn request_id(&self) -> super::RequestId {
467 self.context.request_id()
468 }
469
470 fn protocol_violation() -> RuntimeFailure {
471 RuntimeFailure::ProtocolViolation { capability: C::ID }
472 }
473
474 fn cancelled_outcome(&self) -> Option<RuntimeFailure> {
475 if !self.cancelled.get() {
476 return None;
477 }
478 if self.terminal_seen.replace(true) {
479 Some(Self::protocol_violation())
480 } else {
481 Some(RuntimeFailure::Cancelled {
482 request_id: self.context.request_id(),
483 })
484 }
485 }
486
487 fn schedule_failure(&self, error: RuntimeFailure) -> RuntimeFailure {
488 schedule_plugin_supervision_after_failure(&self.runtime, &self.plugin_instance, error)
489 }
490
491 fn finish_with_error(&self, error: RuntimeFailure) -> RuntimeFailure {
492 let error = self.schedule_failure(error);
493 self.runtime.diagnostics.emit_runtime_failure(
494 (self.runtime.driver.now)(),
495 Some(&self.plugin_instance),
496 &error,
497 );
498 if !matches!(error, RuntimeFailure::ResourceExhausted { .. }) {
499 self.terminal_seen.set(true);
500 if !self.cancelled.replace(true) {
501 self.inner.cancel();
503 }
504 }
505 error
506 }
507}
508
509impl<C: StreamCapability> Drop for NativeStream<C> {
510 fn drop(&mut self) {
511 if !self.cancelled.replace(true) && !self.terminal_seen.get() {
512 self.inner.cancel();
513 }
514 }
515}
516
517pub type StreamSession<C> = NativeStream<C>;