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