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