harmos_runtime/operations/
handler.rs1pub use harmos_domain::operations::instance::Id as OperationInstanceId;
15
16use std::future::Future;
20use std::pin::Pin;
21use std::sync::{
22 Arc,
23 atomic::{AtomicU64, Ordering},
24};
25use std::task::{Context as TaskContext, Poll};
26use tokio::sync::oneshot;
28
29use super::OperationManager;
31use super::admission::Admission;
32use super::caller::{Origin, Source};
33use crate::operations::OperationDeclaration;
34use crate::{Error, Result, operations::Scope, state::Position};
35
36static NEXT_INSTANCE: AtomicU64 = AtomicU64::new(1);
39static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
48pub struct OperationGeneration(u64);
49
50#[must_use = "dropping an unsettled operation instance requests its cancellation"]
55pub struct OperationInstance<O: OperationDeclaration> {
56 pub(crate) id: OperationInstanceId,
57 pub(super) generation: OperationGeneration,
58 pub(crate) outcome: Outcome<O::Response>,
59 pub(super) control: Arc<crate::supervision::instance::Instance>,
60 pub(super) released: bool,
61}
62
63pub(crate) enum Outcome<T: Send + Sync + 'static> {
64 Request(oneshot::Receiver<Result<crate::operations::Response<T>>>),
65 Resident {
66 lease: Option<super::resident::Lease>,
67 settled: std::sync::Mutex<Settling<T>>,
69 },
70}
71
72pub(crate) type Settling<T> = Pin<Box<dyn Future<Output = Result<crate::operations::Response<T>>> + Send>>;
73
74#[derive(Clone)]
75pub struct Context<'a> {
76 pub(crate) id: OperationInstanceId,
77 pub(crate) attempt: usize,
78 pub(crate) scope: Scope<'a>,
79 pub(crate) operations: OperationManager,
80 pub(super) admission: Admission,
81 pub(super) source: Source,
82 pub(super) origin: Origin,
83
84 #[cfg(feature = "plugin-sidecar")]
85 pub(super) retirement: bool,
86}
87
88impl OperationGeneration {
95 pub(super) fn fresh() -> Result<Self> {
96 NEXT_GENERATION
97 .try_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
98 .map(Self)
99 .map_err(|_| Error::ResourceExhausted { resource: "operation provider generations" })
100 }
101
102 pub fn value(self) -> u64 {
103 self.0
104 }
105}
106
107impl<O: OperationDeclaration> OperationInstance<O> {
108 pub fn id(&self) -> OperationInstanceId {
109 self.id
110 }
111
112 pub fn generation(&self) -> OperationGeneration {
113 self.generation
114 }
115
116 pub fn cancel(&self) {
118 match &self.outcome {
119 Outcome::Resident { lease: Some(lease), .. } => lease.stop(),
120 _ => self.control.cancel(),
121 }
122 }
123
124 pub fn status(&self) -> super::OperationStatus {
125 self.control.status()
126 }
127
128 pub fn subscribe(&self) -> tokio::sync::watch::Receiver<super::OperationStatus> {
129 self.control.settlement()
130 }
131
132 pub async fn ready(&self) -> Result<()> {
135 let mut status = self.control.settlement();
136 loop {
137 let current = status.borrow_and_update().clone();
138 match current.phase() {
139 super::OperationPhase::Running => return Ok(()),
140 phase if phase.is_terminal() || current.cancellation_requested() || current.outcome().is_some() => {
141 self.settle().await?;
142 return Err(Error::NotReady { domain: "operation activation" });
143 }
144 _ => {}
145 }
146 if status.changed().await.is_err() {
147 return Err(Error::NotReady { domain: "operation activation" });
148 }
149 }
150 }
151
152 pub async fn join(self) -> Result<crate::operations::Response<O::Response>> {
153 self.await
154 }
155
156 pub async fn cancel_and_join(self) -> Result<crate::operations::Response<O::Response>> {
157 self.cancel();
158 self.await
159 }
160
161 pub fn detach(mut self) -> OperationInstanceId {
165 self.released = true;
166 if let Outcome::Resident { lease, .. } = &mut self.outcome
167 && let Some(lease) = lease.take()
168 {
169 lease.detach();
170 }
171 self.id
172 }
173
174 pub async fn release(mut self) -> Result<()> {
178 self.released = true;
179 let lease = match &mut self.outcome {
180 Outcome::Resident { lease, .. } => lease.take(),
181 Outcome::Request(_) => None,
182 };
183 match lease {
184 Some(lease) => lease.release().await,
185 None => {
186 self.control.cancel();
187 self.settle().await
188 }
189 }
190 }
191
192 pub async fn settle(&self) -> Result<()> {
195 if let Outcome::Resident { lease: Some(lease), .. } = &self.outcome {
196 return lease.settlement().await;
197 }
198 self.control.settle().await.map_err(|source| Error::OperationSettlement { source })
199 }
200}
201
202impl<O: OperationDeclaration> OperationInstance<O> {
203 pub(super) fn unguarded(mut self) -> Self {
206 self.released = true;
207 self
208 }
209}
210
211impl<O: OperationDeclaration> Future for OperationInstance<O> {
212 type Output = Result<crate::operations::Response<O::Response>>;
213 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
214 let instance = self.get_mut();
215 let answer = match &mut instance.outcome {
216 Outcome::Request(outcome) => match Pin::new(outcome).poll(cx) {
217 Poll::Ready(Ok(answer)) => answer,
218 Poll::Ready(Err(_)) => Err(crate::Refusal::Unavailable.into()),
219 Poll::Pending => return Poll::Pending,
220 },
221 Outcome::Resident { settled, .. } => match settled.get_mut().expect("resident settlement is healthy").as_mut().poll(cx) {
222 Poll::Ready(answer) => answer,
223 Poll::Pending => return Poll::Pending,
224 },
225 };
226 instance.released = true;
227 Poll::Ready(answer)
228 }
229}
230
231impl<O: OperationDeclaration> Drop for OperationInstance<O> {
232 fn drop(&mut self) {
233 if matches!(self.outcome, Outcome::Request(_)) && !self.released && !self.control.is_settled() {
235 self.control.cancel();
236 }
237 }
238}
239
240impl<O> harmos_sdk::operations::Instance<O> for OperationInstance<O>
241where
242 O: OperationDeclaration,
243{
244 type Error = Error;
245
246 fn id(&self) -> OperationInstanceId {
247 self.id
248 }
249
250 fn status(&self) -> impl Future<Output = Result<harmos_domain::operations::instance::Status>> + Send {
251 std::future::ready(Ok(self.control.status().portable()))
252 }
253
254 fn cancel(&self) -> impl Future<Output = Result<()>> + Send {
255 OperationInstance::cancel(self);
256 std::future::ready(Ok(()))
257 }
258
259 fn join(self) -> impl Future<Output = Result<crate::operations::Response<O::Response>>> + Send {
260 self
261 }
262
263 fn detach(self) -> OperationInstanceId {
264 OperationInstance::detach(self)
265 }
266}
267
268impl Context<'_> {
269 pub fn context(&self) -> &super::CallContext {
270 self.operations.call_context()
271 }
272
273 pub fn with_value<T: super::ContextValue>(&self, value: T) -> Result<Self> {
276 let context = self.context().with_value(value)?;
277 let mut child = self.clone();
278 child.operations = child.operations.with_context(context);
279 Ok(child)
280 }
281
282 pub fn id(&self) -> OperationInstanceId {
283 self.id
284 }
285
286 pub(crate) fn owned_instance(&self, id: OperationInstanceId) -> Option<Arc<crate::supervision::instance::Instance>> {
288 self.operations.instances.owned(id, &self.origin.identity)
289 }
290
291 pub fn attempt(&self) -> usize {
292 self.attempt
293 }
294
295 pub fn scope(&self) -> &Scope<'_> {
296 &self.scope
297 }
298
299 pub fn is_host_call(&self) -> bool {
300 self.origin.identity == super::caller::Identity::Host
301 }
302
303 pub(crate) fn admit_stream_grant(&self, grant: &harmos_domain::plugin::Grant) -> Result<()> {
305 self.source.check()?;
306 match &self.origin.grants {
307 Some(authority) => authority.admit(Some(grant)),
308 None if self.is_host_call() => Ok(()),
309 None => Err(Error::PermissionDenied),
310 }
311 }
312
313 #[cfg(any(all(feature = "plugin-guest", not(target_arch = "wasm32")), feature = "plugin-sidecar"))]
315 pub(super) fn admit_grant(&self, grant: Option<&harmos_domain::plugin::Grant>) -> Result<()> {
316 match (&self.origin.grants, grant) {
317 (Some(authority), grant) => authority.admit(grant),
318 (None, None) => Ok(()),
319 (None, Some(_)) => Err(Error::PermissionDenied),
320 }
321 }
322
323 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
324 pub fn caller_scope(&self) -> Option<&crate::plugin::PluginScope> {
325 self.origin.scope.as_ref()
326 }
327
328 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
329 pub(crate) fn retains_publication(&self, scope: &crate::plugin::PluginScope) -> bool {
330 self.source.check().is_ok_and(|origin| {
331 (origin.has_lifecycle_loan() || origin.resident) && origin.scope.as_ref().is_some_and(|caller| caller.same_activation(scope))
332 })
333 }
334
335 pub fn submit_at<O: OperationDeclaration>(&self, id: &str, request: O) -> Result<OperationInstance<O>> {
336 self.operations.admit_at::<O, ()>(id, (request.into(), ()), Some(&self.scope), self.admission.held(), &self.source, None)
337 }
338
339 pub fn submit<O: OperationDeclaration>(&self, request: O) -> Result<OperationInstance<O>> {
340 self.operations.submit_scoped::<O, ()>((request.into(), ()), &self.scope, &self.admission, &self.source, None)
341 }
342
343 pub fn submit_endpoints<O: OperationDeclaration, P: Send + 'static>(&self, request: O, endpoints: P) -> Result<OperationInstance<O>> {
344 self.operations.submit_scoped::<O, P>((request.into(), endpoints), &self.scope, &self.admission, &self.source, None)
345 }
346
347 pub fn submit_charged<O: OperationDeclaration, P: Send + 'static>(
348 &self, request: super::StreamItem<O>, endpoints: P,
349 ) -> Result<OperationInstance<O>> {
350 self.operations.submit_scoped::<O, P>(
351 (super::binding::Request::charged(request)?, endpoints),
352 &self.scope,
353 &self.admission,
354 &self.source,
355 None,
356 )
357 }
358
359 pub fn submit_caused<O: OperationDeclaration>(&self, request: O, cause: Position) -> Result<OperationInstance<O>> {
360 self.operations.submit_scoped::<O, ()>((request.into(), ()), &self.scope, &self.admission, &self.source, Some(cause))
361 }
362
363 pub async fn call_caused<O: OperationDeclaration>(
364 &self, request: O, cause: Position,
365 ) -> Result<crate::operations::Response<O::Response>> {
366 self.submit_caused::<O>(request, cause)?.await
367 }
368
369 pub async fn call<O: OperationDeclaration>(&self, request: O) -> Result<crate::operations::Response<O::Response>> {
370 self.submit(request)?.await
371 }
372}
373
374pub(crate) fn fresh_instance() -> Result<OperationInstanceId> {
377 NEXT_INSTANCE
378 .try_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
379 .map(OperationInstanceId::new)
380 .map_err(|_| Error::ResourceExhausted { resource: "operation instance identities" })
381}
382
383