1use std::{any::Any, marker::PhantomData, rc::Rc, time::Duration};
2
3use super::{
4 CancellationToken, DiagnosticAdmission, DiagnosticEvent, DiagnosticOutcome, DiagnosticSource,
5 ErasedDomainResult, InvocationContext, NativeAppRuntime, NativeEndpointBinding,
6 RequestCapability, RequestId, RuntimeFailure, diagnostics::diagnostic_operation,
7 ensure_context_active,
8};
9
10pub(crate) fn invoke_erased_dependency(
11 endpoint: NativeEndpointBinding,
12 runtime: Rc<NativeAppRuntime>,
13 caller_instance: String,
14 operation: String,
15 context: InvocationContext,
16 request: Box<dyn Any>,
17) -> super::LocalBoxFuture<'static, Result<ErasedDomainResult, RuntimeFailure>> {
18 Box::pin(async move {
19 let _snapshot_call = super::transition::SnapshotCall::new(&runtime);
20 let capability = endpoint.state.capability_id;
21 let context = context
22 .for_caller(&caller_instance)
23 .for_target(capability, &operation);
24 if runtime.shutdown_started.get() && !context.is_shutdown_dependency_call() {
25 return Err(RuntimeFailure::AdmissionClosed);
26 }
27 runtime
28 .diagnostics
29 .record_invocation(&caller_instance, &endpoint.plugin_instance);
30 let snapshot = endpoint
31 .state
32 .snapshot()
33 .ok_or(RuntimeFailure::Unavailable { capability })?;
34 let admission =
35 endpoint
36 .admission(&operation)
37 .ok_or_else(|| RuntimeFailure::UnknownOperation {
38 capability,
39 operation: operation.clone(),
40 })?;
41 let permit = admission
42 .acquire(capability, &operation, &context, &runtime.driver)
43 .await?;
44 if !endpoint.state.is_current(snapshot.generation) {
45 return Err(RuntimeFailure::Unavailable { capability });
46 }
47 ensure_context_active(&runtime.driver, &context)?;
48 let generation_cancellation = if context.is_shutdown_dependency_call() {
49 CancellationToken::new()
50 } else {
51 snapshot.cancellation
52 };
53 super::settlement::request(
54 &runtime,
55 &endpoint.plugin_instance,
56 &operation,
57 &context,
58 generation_cancellation,
59 capability,
60 permit,
61 |context| snapshot.endpoint.invoke(&operation, request, context),
62 )
63 .await?
64 })
65}
66
67#[derive(Debug)]
69pub struct NativeRequestHandle<C: RequestCapability> {
70 pub(super) endpoints: Vec<NativeEndpointBinding>,
71 pub(super) runtime: Rc<NativeAppRuntime>,
72 pub(super) caller_instance: Rc<str>,
73 pub(super) caller_is_planned: bool,
74 pub(super) allow_before_ready: bool,
75 pub(super) capability: PhantomData<fn() -> C>,
76}
77
78impl<C: RequestCapability> Clone for NativeRequestHandle<C> {
79 fn clone(&self) -> Self {
80 Self {
81 endpoints: self.endpoints.clone(),
82 runtime: self.runtime.clone(),
83 caller_instance: self.caller_instance.clone(),
84 caller_is_planned: self.caller_is_planned,
85 allow_before_ready: self.allow_before_ready,
86 capability: PhantomData,
87 }
88 }
89}
90
91impl<C: RequestCapability> NativeRequestHandle<C> {
92 pub(super) fn from_endpoints(
93 endpoints: &[NativeEndpointBinding],
94 runtime: Rc<NativeAppRuntime>,
95 caller_instance: &str,
96 allow_before_ready: bool,
97 ) -> Self {
98 let caller_is_planned = runtime.plan.plugin_instance(caller_instance).is_some();
99 Self {
100 endpoints: endpoints.to_vec(),
101 runtime,
102 caller_instance: Rc::from(caller_instance),
103 caller_is_planned,
104 allow_before_ready,
105 capability: PhantomData,
106 }
107 }
108
109 pub fn binding_count(&self) -> usize {
111 self.endpoints.len()
112 }
113
114 fn diagnostic_requirement(&self) -> Option<String> {
115 let first = self.endpoints.first()?;
116 self.endpoints
117 .iter()
118 .all(|endpoint| endpoint.requirement_id == first.requirement_id)
119 .then(|| first.requirement_id.clone())
120 }
121
122 fn diagnostic_caller_instance(&self) -> Option<String> {
123 self.caller_is_planned
124 .then(|| self.caller_instance.to_string())
125 }
126
127 pub async fn invoke(
129 &self,
130 operation: &str,
131 request: C::Request,
132 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure> {
133 let context = self.next_context();
134 self.invoke_with_context(operation, context, request).await
135 }
136
137 pub async fn invoke_with_context(
139 &self,
140 operation: &str,
141 context: InvocationContext,
142 request: C::Request,
143 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure> {
144 let _snapshot_call = super::transition::SnapshotCall::new(&self.runtime);
145 let context = context
146 .for_caller(&self.caller_instance)
147 .for_target(C::ID, operation);
148 if let Some(endpoint) = self.endpoints.first() {
149 self.runtime
150 .diagnostics
151 .record_invocation(&self.caller_instance, &endpoint.plugin_instance);
152 }
153 let invocation_diagnostics = self
154 .runtime
155 .diagnostics
156 .has_interested_observer(DiagnosticSource::Invocation);
157 let started_at = invocation_diagnostics.then(|| (self.runtime.driver.now)());
158 let operation_name = invocation_diagnostics
159 .then(|| {
160 self.endpoints
161 .first()
162 .and_then(|endpoint| diagnostic_operation(endpoint.state.operations, operation))
163 })
164 .flatten();
165 if let Some(started_at) = started_at {
166 self.runtime
167 .diagnostics
168 .emit(DiagnosticSource::Invocation, started_at, |_| {
169 DiagnosticEvent::InvocationStarted {
170 requirement_id: self.diagnostic_requirement(),
171 request_id: context.request_id(),
172 caller_instance: self.diagnostic_caller_instance(),
173 provider_instance: self
174 .endpoints
175 .first()
176 .map(|endpoint| endpoint.plugin_instance.clone()),
177 capability: C::ID,
178 operation: operation_name,
179 }
180 });
181 }
182 let request_id = context.request_id();
183 let result = self
184 .invoke_with_context_inner(operation, context, request)
185 .await;
186 let outcome = request_diagnostic_outcome(&result);
187 if let Some(started_at) = started_at {
188 let completed_at = (self.runtime.driver.now)();
189 self.runtime
190 .diagnostics
191 .emit(DiagnosticSource::Invocation, completed_at, |_| {
192 DiagnosticEvent::InvocationCompleted {
193 requirement_id: self.diagnostic_requirement(),
194 request_id,
195 caller_instance: self.diagnostic_caller_instance(),
196 provider_instance: self
197 .endpoints
198 .first()
199 .map(|endpoint| endpoint.plugin_instance.clone()),
200 capability: C::ID,
201 operation: operation_name,
202 outcome,
203 elapsed: completed_at.saturating_sub(started_at),
204 }
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 if let Some(admission) = diagnostic_admission(error) {
216 self.runtime.diagnostics.emit(
217 DiagnosticSource::Admission,
218 (self.runtime.driver.now)(),
219 |_| DiagnosticEvent::AdmissionRejected {
220 requirement_id: self.diagnostic_requirement(),
221 request_id,
222 caller_instance: self.diagnostic_caller_instance(),
223 provider_instance: self
224 .endpoints
225 .first()
226 .map(|endpoint| endpoint.plugin_instance.clone()),
227 capability: C::ID,
228 operation: operation_name,
229 outcome: admission,
230 },
231 );
232 }
233 }
234 result
235 }
236
237 async fn invoke_with_context_inner(
238 &self,
239 operation: &str,
240 context: InvocationContext,
241 request: C::Request,
242 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure> {
243 if (self.runtime.shutdown_started.get()
244 && !(self.allow_before_ready && context.is_shutdown_dependency_call()))
245 || (!self.allow_before_ready && self.runtime.admission.is_closed())
246 {
247 return Err(RuntimeFailure::AdmissionClosed);
248 }
249 let endpoint = match self.endpoints.as_slice() {
250 [] => return Err(RuntimeFailure::Unavailable { capability: C::ID }),
251 [endpoint] => endpoint,
252 endpoints => {
253 return Err(RuntimeFailure::AmbiguousBinding {
254 capability: C::ID,
255 providers: endpoints.len(),
256 });
257 }
258 };
259 let snapshot = endpoint
260 .state
261 .snapshot()
262 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?;
263 let admission =
264 endpoint
265 .admission(operation)
266 .ok_or_else(|| RuntimeFailure::UnknownOperation {
267 capability: C::ID,
268 operation: operation.to_owned(),
269 })?;
270 let permit = admission
271 .acquire(C::ID, operation, &context, &self.runtime.driver)
272 .await?;
273 if !endpoint.state.is_current(snapshot.generation) {
274 return Err(RuntimeFailure::Unavailable { capability: C::ID });
275 }
276 ensure_context_active(&self.runtime.driver, &context)?;
277 let generation_cancellation = if context.is_shutdown_dependency_call() {
278 CancellationToken::new()
279 } else {
280 snapshot.cancellation
281 };
282 let outcome = super::settlement::request(
283 &self.runtime,
284 &endpoint.plugin_instance,
285 operation,
286 &context,
287 generation_cancellation,
288 C::ID,
289 permit,
290 |context| {
291 C::invoke_native(
292 snapshot.endpoint.as_ref(),
293 operation,
294 request,
295 context.clone(),
296 )
297 },
298 )
299 .await??;
300 Ok(outcome)
301 }
302
303 pub async fn invoke_many(
305 &self,
306 operation: &str,
307 request: C::Request,
308 ) -> Result<Vec<Result<C::Response, C::DomainError>>, RuntimeFailure>
309 where
310 C::Request: Clone,
311 {
312 let context = self.next_context();
313 self.invoke_many_with_context(operation, context, request)
314 .await
315 }
316
317 pub async fn invoke_many_with_context(
319 &self,
320 operation: &str,
321 context: InvocationContext,
322 request: C::Request,
323 ) -> Result<Vec<Result<C::Response, C::DomainError>>, RuntimeFailure>
324 where
325 C::Request: Clone,
326 {
327 let context = context.for_caller(&self.caller_instance);
328 for endpoint in &self.endpoints {
329 self.runtime
330 .diagnostics
331 .record_invocation(&self.caller_instance, &endpoint.plugin_instance);
332 }
333 let invocation_diagnostics = self
334 .runtime
335 .diagnostics
336 .has_interested_observer(DiagnosticSource::Invocation);
337 let started_at = invocation_diagnostics.then(|| (self.runtime.driver.now)());
338 let operation_name = invocation_diagnostics
339 .then(|| {
340 self.endpoints
341 .first()
342 .and_then(|endpoint| diagnostic_operation(endpoint.state.operations, operation))
343 })
344 .flatten();
345 let request_id = context.request_id();
346 if let Some(started_at) = started_at {
347 self.runtime
348 .diagnostics
349 .emit(DiagnosticSource::Invocation, started_at, |_| {
350 DiagnosticEvent::InvocationStarted {
351 requirement_id: self.diagnostic_requirement(),
352 request_id,
353 caller_instance: self.diagnostic_caller_instance(),
354 provider_instance: None,
355 capability: C::ID,
356 operation: operation_name,
357 }
358 });
359 }
360 let result = self
361 .invoke_many_with_context_inner(operation, context, request)
362 .await;
363 let outcome = many_request_diagnostic_outcome(&result);
364 if let Some(started_at) = started_at {
365 let completed_at = (self.runtime.driver.now)();
366 self.runtime
367 .diagnostics
368 .emit(DiagnosticSource::Invocation, completed_at, |_| {
369 DiagnosticEvent::InvocationCompleted {
370 requirement_id: self.diagnostic_requirement(),
371 request_id,
372 caller_instance: self.diagnostic_caller_instance(),
373 provider_instance: None,
374 capability: C::ID,
375 operation: operation_name,
376 outcome,
377 elapsed: completed_at.saturating_sub(started_at),
378 }
379 });
380 }
381 if let Err(error) = &result {
382 self.runtime
383 .diagnostics
384 .emit_runtime_failure((self.runtime.driver.now)(), None, error);
385 if let Some(admission) = diagnostic_admission(error) {
386 self.runtime.diagnostics.emit(
387 DiagnosticSource::Admission,
388 (self.runtime.driver.now)(),
389 |_| DiagnosticEvent::AdmissionRejected {
390 requirement_id: self.diagnostic_requirement(),
391 request_id,
392 caller_instance: self.diagnostic_caller_instance(),
393 provider_instance: None,
394 capability: C::ID,
395 operation: operation_name,
396 outcome: admission,
397 },
398 );
399 }
400 }
401 result
402 }
403
404 async fn invoke_many_with_context_inner(
405 &self,
406 operation: &str,
407 context: InvocationContext,
408 request: C::Request,
409 ) -> Result<Vec<Result<C::Response, C::DomainError>>, RuntimeFailure>
410 where
411 C::Request: Clone,
412 {
413 let _snapshot_call = super::transition::SnapshotCall::new(&self.runtime);
414 if (self.runtime.shutdown_started.get()
415 && !(self.allow_before_ready && context.is_shutdown_dependency_call()))
416 || (!self.allow_before_ready && self.runtime.admission.is_closed())
417 {
418 return Err(RuntimeFailure::AdmissionClosed);
419 }
420 if self.endpoints.is_empty() {
421 return Ok(Vec::new());
422 }
423 let mut outcomes = Vec::with_capacity(self.endpoints.len());
424 for endpoint in &self.endpoints {
425 let snapshot = endpoint
426 .state
427 .snapshot()
428 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?;
429 let admission =
430 endpoint
431 .admission(operation)
432 .ok_or_else(|| RuntimeFailure::UnknownOperation {
433 capability: C::ID,
434 operation: operation.to_owned(),
435 })?;
436 let permit = admission
437 .acquire(C::ID, operation, &context, &self.runtime.driver)
438 .await?;
439 if !endpoint.state.is_current(snapshot.generation) {
440 return Err(RuntimeFailure::Unavailable { capability: C::ID });
441 }
442 ensure_context_active(&self.runtime.driver, &context)?;
443 let generation_cancellation = if context.is_shutdown_dependency_call() {
444 CancellationToken::new()
445 } else {
446 snapshot.cancellation
447 };
448 let outcome = super::settlement::request(
449 &self.runtime,
450 &endpoint.plugin_instance,
451 operation,
452 &context,
453 generation_cancellation,
454 C::ID,
455 permit,
456 |context| {
457 C::invoke_native(
458 snapshot.endpoint.as_ref(),
459 operation,
460 request.clone(),
461 context.clone(),
462 )
463 },
464 )
465 .await??;
466 outcomes.push(outcome);
467 }
468 Ok(outcomes)
469 }
470
471 pub fn invocation_context(
473 &self,
474 deadline: Option<Duration>,
475 cancellation: CancellationToken,
476 ) -> InvocationContext {
477 InvocationContext::new(self.next_request_id(), deadline, cancellation)
478 }
479
480 pub(super) fn next_context(&self) -> InvocationContext {
481 self.invocation_context(None, CancellationToken::new())
482 .with_shared_caller_instance(self.caller_instance.clone())
483 }
484
485 pub(super) fn next_request_id(&self) -> RequestId {
486 let request_id = self.runtime.request_ids.get();
487 self.runtime.request_ids.set(request_id.saturating_add(1));
488 request_id
489 }
490}
491
492fn request_diagnostic_outcome<Response, DomainError>(
493 result: &Result<Result<Response, DomainError>, RuntimeFailure>,
494) -> DiagnosticOutcome {
495 match result {
496 Ok(Ok(_)) => DiagnosticOutcome::Succeeded,
497 Ok(Err(_)) => DiagnosticOutcome::DomainError,
498 Err(error) => DiagnosticOutcome::RuntimeFailure(error.into()),
499 }
500}
501
502fn many_request_diagnostic_outcome<Response, DomainError>(
503 result: &Result<Vec<Result<Response, DomainError>>, RuntimeFailure>,
504) -> DiagnosticOutcome {
505 match result {
506 Ok(outcomes) if outcomes.iter().any(Result::is_err) => DiagnosticOutcome::DomainError,
507 Ok(_) => DiagnosticOutcome::Succeeded,
508 Err(error) => DiagnosticOutcome::RuntimeFailure(error.into()),
509 }
510}
511
512fn diagnostic_admission(error: &RuntimeFailure) -> Option<DiagnosticAdmission> {
513 match error {
514 RuntimeFailure::AdmissionClosed => Some(DiagnosticAdmission::Closed),
515 RuntimeFailure::ResourceExhausted { .. } => Some(DiagnosticAdmission::Exhausted),
516 RuntimeFailure::Unavailable { .. } => Some(DiagnosticAdmission::Unavailable),
517 _ => None,
518 }
519}