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 #[tracing::instrument(target = "harmos::operations", name = "operations.join", level = "debug", skip_all)]
153 pub async fn join(self) -> Result<crate::operations::Response<O::Response>> {
154 self.await
155 }
156
157 pub async fn cancel_and_join(self) -> Result<crate::operations::Response<O::Response>> {
158 self.cancel();
159 self.await
160 }
161
162 pub fn detach(mut self) -> OperationInstanceId {
166 self.released = true;
167 if let Outcome::Resident { lease, .. } = &mut self.outcome
168 && let Some(lease) = lease.take()
169 {
170 lease.detach();
171 }
172 self.id
173 }
174
175 pub async fn release(mut self) -> Result<()> {
179 self.released = true;
180 let lease = match &mut self.outcome {
181 Outcome::Resident { lease, .. } => lease.take(),
182 Outcome::Request(_) => None,
183 };
184 match lease {
185 Some(lease) => lease.release().await,
186 None => {
187 self.control.cancel();
188 self.settle().await
189 }
190 }
191 }
192
193 #[tracing::instrument(target = "harmos::operations", name = "operations.settle", level = "debug", skip_all)]
196 pub async fn settle(&self) -> Result<()> {
197 if let Outcome::Resident { lease: Some(lease), .. } = &self.outcome {
198 return lease.settlement().await;
199 }
200 self.control.settle().await.map_err(|source| Error::OperationSettlement { source })
201 }
202}
203
204impl<O: OperationDeclaration> OperationInstance<O> {
205 pub(super) fn unguarded(mut self) -> Self {
208 self.released = true;
209 self
210 }
211}
212
213impl<O: OperationDeclaration> Future for OperationInstance<O> {
214 type Output = Result<crate::operations::Response<O::Response>>;
215 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
216 let instance = self.get_mut();
217 let answer = match &mut instance.outcome {
218 Outcome::Request(outcome) => match Pin::new(outcome).poll(cx) {
219 Poll::Ready(Ok(answer)) => answer,
220 Poll::Ready(Err(_)) => Err(crate::Refusal::Unavailable.into()),
221 Poll::Pending => return Poll::Pending,
222 },
223 Outcome::Resident { settled, .. } => match settled.get_mut().expect("resident settlement is healthy").as_mut().poll(cx) {
224 Poll::Ready(answer) => answer,
225 Poll::Pending => return Poll::Pending,
226 },
227 };
228 instance.released = true;
229 Poll::Ready(answer)
230 }
231}
232
233impl<O: OperationDeclaration> Drop for OperationInstance<O> {
234 fn drop(&mut self) {
235 if matches!(self.outcome, Outcome::Request(_)) && !self.released && !self.control.is_settled() {
237 self.control.cancel();
238 }
239 }
240}
241
242impl<O> harmos_sdk::operations::Instance<O> for OperationInstance<O>
243where
244 O: OperationDeclaration,
245{
246 type Error = Error;
247
248 fn id(&self) -> OperationInstanceId {
249 self.id
250 }
251
252 fn status(&self) -> impl Future<Output = Result<harmos_domain::operations::instance::Status>> + Send {
253 std::future::ready(Ok(self.control.status().portable()))
254 }
255
256 fn cancel(&self) -> impl Future<Output = Result<()>> + Send {
257 OperationInstance::cancel(self);
258 std::future::ready(Ok(()))
259 }
260
261 fn join(self) -> impl Future<Output = Result<crate::operations::Response<O::Response>>> + Send {
262 self
263 }
264
265 fn detach(self) -> OperationInstanceId {
266 OperationInstance::detach(self)
267 }
268}
269
270impl Context<'_> {
271 pub fn context(&self) -> &super::CallContext {
272 self.operations.call_context()
273 }
274
275 pub fn with_value<T: super::ContextValue>(&self, value: T) -> Result<Self> {
278 let context = self.context().with_value(value)?;
279 let mut child = self.clone();
280 child.operations = child.operations.with_context(context);
281 Ok(child)
282 }
283
284 pub fn id(&self) -> OperationInstanceId {
285 self.id
286 }
287
288 pub(crate) fn owned_instance(&self, id: OperationInstanceId) -> Option<Arc<crate::supervision::instance::Instance>> {
290 self.operations.instances.owned(id, &self.origin.identity)
291 }
292
293 pub fn attempt(&self) -> usize {
294 self.attempt
295 }
296
297 pub fn scope(&self) -> &Scope<'_> {
298 &self.scope
299 }
300
301 pub fn is_host_call(&self) -> bool {
302 self.origin.identity == super::caller::Identity::Host
303 }
304
305 pub(crate) fn admit_stream_grant(&self, grant: &harmos_domain::plugin::Grant) -> Result<()> {
307 self.source.check()?;
308 match &self.origin.grants {
309 Some(authority) => authority.admit(Some(grant)),
310 None if self.is_host_call() => Ok(()),
311 None => Err(Error::PermissionDenied),
312 }
313 }
314
315 #[cfg(any(all(feature = "plugin-guest", not(target_arch = "wasm32")), feature = "plugin-sidecar"))]
317 pub(super) fn admit_grant(&self, grant: Option<&harmos_domain::plugin::Grant>) -> Result<()> {
318 match (&self.origin.grants, grant) {
319 (Some(authority), grant) => authority.admit(grant),
320 (None, None) => Ok(()),
321 (None, Some(_)) => Err(Error::PermissionDenied),
322 }
323 }
324
325 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
326 pub fn caller_scope(&self) -> Option<&crate::plugin::PluginScope> {
327 self.origin.scope.as_ref()
328 }
329
330 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
331 pub(crate) fn retains_publication(&self, scope: &crate::plugin::PluginScope) -> bool {
332 self.source.check().is_ok_and(|origin| {
333 (origin.has_lifecycle_loan() || origin.resident) && origin.scope.as_ref().is_some_and(|caller| caller.same_activation(scope))
334 })
335 }
336
337 pub fn submit_at<O: OperationDeclaration>(&self, id: &str, request: O) -> Result<OperationInstance<O>> {
338 self.operations.admit_at::<O, ()>(id, (request.into(), ()), Some(&self.scope), self.admission.held(), &self.source, None)
339 }
340
341 pub fn submit<O: OperationDeclaration>(&self, request: O) -> Result<OperationInstance<O>> {
342 self.operations.submit_scoped::<O, ()>((request.into(), ()), &self.scope, &self.admission, &self.source, None)
343 }
344
345 pub fn submit_endpoints<O: OperationDeclaration, P: Send + 'static>(&self, request: O, endpoints: P) -> Result<OperationInstance<O>> {
346 self.operations.submit_scoped::<O, P>((request.into(), endpoints), &self.scope, &self.admission, &self.source, None)
347 }
348
349 pub fn submit_charged<O: OperationDeclaration, P: Send + 'static>(
350 &self, request: super::StreamItem<O>, endpoints: P,
351 ) -> Result<OperationInstance<O>> {
352 self.operations.submit_scoped::<O, P>(
353 (super::binding::Request::charged(request)?, endpoints),
354 &self.scope,
355 &self.admission,
356 &self.source,
357 None,
358 )
359 }
360
361 pub fn submit_caused<O: OperationDeclaration>(&self, request: O, cause: Position) -> Result<OperationInstance<O>> {
362 self.operations.submit_scoped::<O, ()>((request.into(), ()), &self.scope, &self.admission, &self.source, Some(cause))
363 }
364
365 pub async fn call_caused<O: OperationDeclaration>(
366 &self, request: O, cause: Position,
367 ) -> Result<crate::operations::Response<O::Response>> {
368 self.submit_caused::<O>(request, cause)?.await
369 }
370
371 pub async fn call<O: OperationDeclaration>(&self, request: O) -> Result<crate::operations::Response<O::Response>> {
372 self.submit(request)?.await
373 }
374}
375
376pub(crate) fn fresh_instance() -> Result<OperationInstanceId> {
379 NEXT_INSTANCE
380 .try_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
381 .map(OperationInstanceId::new)
382 .map_err(|_| Error::ResourceExhausted { resource: "operation instance identities" })
383}
384
385