blob: 7cd4ec303b0d28c3c71eca00db654a77699444dc [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Committed demand, one-item feedback backpressure and original executor custody.
use super::*;
use tokio::sync::Semaphore;
#[derive(Clone)]
pub(crate) struct Demand {
pub action: Action,
pub request: Uuid,
}
pub(super) struct Flow {
pub stop: CancellationToken,
pub(super) ack: Arc<Semaphore>,
pub(super) failure: Option<String>,
pub(super) pending_feedback: bool,
}
impl Flow {
pub fn new() -> Self {
Self {
stop: CancellationToken::new(),
ack: Arc::new(Semaphore::new(0)),
failure: None,
pending_feedback: false,
}
}
}
impl Drop for Flow {
fn drop(&mut self) {
self.stop.cancel();
}
}
impl<V: ?Sized + 'static> Application<V> {
pub(crate) fn subscription_wanted(&self, consumer: &Mount, key: &str, request: Uuid) -> bool {
!self.is_retiring(consumer)
&& self
.views
.get(&consumer.id())
.and_then(|view| view.subscriptions.get(key))
.is_some_and(|demand| demand.request == request)
}
pub(super) fn update_wanted(&self, consumer: &Mount, key: &str, id: Uuid) -> bool {
!self.is_retiring(consumer)
&& self.actions.running.get(&id).is_some_and(|running| {
&running.consumer == consumer
&& running.key == key
&& (running.mutation.is_some() || self.subscription_wanted(consumer, key, id))
})
}
pub(crate) fn sync_subscription_demand(&self) {
for (id, running) in &self.actions.running {
if let Some(flow) = &running.stream
&& !self.subscription_wanted(&running.consumer, &running.key, *id)
{
flow.stop.cancel();
}
if let Some(session) = &running.mutation
&& self.is_retiring(&running.consumer)
{
session.progress.stop.cancel();
}
}
}
pub(crate) fn acknowledge_update(&mut self, id: Uuid) {
if let Some(flow) = self.actions.running.get_mut(&id).and_then(|r| {
r.stream
.as_mut()
.or_else(|| r.mutation.as_mut().map(|s| &mut s.progress))
}) && flow.pending_feedback
&& !flow.stop.is_cancelled()
{
flow.pending_feedback = false;
flow.ack.add_permits(1);
}
}
pub(crate) fn fail_update(&mut self, id: Uuid, reason: String) {
if let Some(flow) = self.actions.running.get_mut(&id).and_then(|r| {
r.stream
.as_mut()
.or_else(|| r.mutation.as_mut().map(|s| &mut s.progress))
}) {
flow.failure.get_or_insert(reason);
flow.stop.cancel();
}
}
pub(super) fn finish_subscription(
&mut self,
id: Uuid,
completion: StreamCompletion,
) -> Result<Option<ActionTask>> {
let running = self
.actions
.running
.get(&id)
.ok_or_else(|| Error::Invalid("unknown subscription".into()))?;
if running.completed || running.stream.is_none() {
return Err(Error::Invalid(
"request is not an active subscription".into(),
));
}
match completion {
StreamCompletion::Finished(mut completion) => {
if let Some(reason) = running
.stream
.as_ref()
.and_then(|flow| flow.failure.clone())
{
completion.result = Err(reason);
}
self.finish_query(id, completion)?;
Ok(None)
}
StreamCompletion::Item { value, next } => {
if running
.stream
.as_ref()
.is_some_and(|flow| flow.pending_feedback && !flow.stop.is_cancelled())
{
return Err(Error::Invalid(
"subscription feedback has not committed".into(),
));
}
let wanted = self.subscription_wanted(&running.consumer, &running.key, id);
let Export::Service { methods, .. } = running.execution.value() else {
unreachable!()
};
let valid = methods[&running.method]
.output
.validate(&value)
.map_err(|e| Error::Invalid(e.to_string()));
let consumer = running.consumer.clone();
let key = running.key.clone();
if let Err(error) = valid {
self.fail_update(id, error.to_string());
}
let flow = self
.actions
.running
.get_mut(&id)
.expect("running")
.stream
.as_mut()
.expect("checked stream");
if wanted && !flow.stop.is_cancelled() {
flow.pending_feedback = true;
self.actions.outcomes.push_back(Outcome {
continuations: Vec::new(),
consumer,
key,
outcome: ActionOutcome::Updated { value },
request: Some(id),
});
} else {
flow.stop.cancel();
}
let stop = flow.stop.clone();
let ack = flow.ack.clone();
Ok(Some(ActionTask {
id,
future: Box::pin(async move {
// Every successor remains with the original foreground owner,
// including after the last surface has gone. Stop bypasses
// ACK but still polls the source's actual close/cleanup path.
tokio::select! {
biased;
() = stop.cancelled() => {},
permit = ack.acquire_owned() => { if let Ok(permit) = permit { permit.forget(); } },
}
TaskCompletion::Subscription(next.await)
}),
}))
}
}
}
}