Skip to main content

aion_server/worker/
grpc_task_delivery.rs

1//! The gRPC arm of the delivery seam: push the task onto the worker's stream.
2//!
3//! This is the transport the dispatcher always used, unchanged in what it does
4//! — the change is that it now *says* what it did, so the caller acts on a type
5//! instead of on the absence of a sender.
6
7use async_trait::async_trait;
8
9use aion_proto::ProtoActivityTask;
10
11use super::delivery_intent::SharedDeliveryIntent;
12use super::registry::{WorkerHandle, WorkerMessage};
13use super::task_delivery::{DeliveryAccepted, TaskDelivery, WorkerTaskDelivery};
14
15/// Delivers by pushing a [`WorkerMessage::ActivityTask`] onto the worker's
16/// registered stream.
17///
18/// # Only two outcomes are reachable here, and that is not an oversight
19///
20/// `mpsc::Sender::send().await` fails **only** when the receiver is gone, i.e.
21/// the worker's stream is closed — which is exactly
22/// [`Undeliverable::WorkerUnreachable`](super::task_delivery::Undeliverable::WorkerUnreachable).
23/// A *full* channel does not error; it applies backpressure and the send waits.
24/// So this transport never produces
25/// [`DeliveryFailed`](super::task_delivery::Undeliverable::DeliveryFailed).
26///
27/// I record that because my own design note previously claimed a full channel
28/// would map to `DeliveryFailed`. It would — under `try_send`. Under the
29/// `send().await` this path has always used, that outcome does not exist, and
30/// switching to `try_send` to manufacture it would turn a worker that is merely
31/// busy into a failed delivery. `DeliveryFailed` exists for the blocking
32/// transport, which really can fail while its worker is alive.
33///
34/// # The intent is asked once, and the backpressure wait is not re-polled
35///
36/// The caller's intent is checked immediately before the push. If the caller
37/// loses its claim *during* the backpressure wait inside `send().await`, this
38/// arm does not notice: there is no poll boundary to re-ask at, and reaching for
39/// one would mean abandoning a send mid-flight.
40///
41/// That is safe, and by two independent mechanisms rather than one:
42///
43/// 1. **The result cannot be recorded.** The completion token is one per pass,
44///    minted by the caller and revoked once when the pass places nothing, so a
45///    task that lands after its pass withdrew carries an authorization the
46///    fences no longer honour.
47/// 2. **The external effect cannot be duplicated.** The task's
48///    `idempotency_key` is derived from `(workflow_id, run_id, activity_id)`
49///    alone — attempts and execution generations deliberately do not participate
50///    (`aion-proto/src/worker.rs:143-146`) — so a redelivery by whichever pass
51///    re-claimed the row carries the *same* key and is deduplicated at the
52///    worker.
53///
54/// The token covers the recording, the idempotency key covers the effect. If
55/// either ever stops holding, that is a finding about the fences or the key —
56/// not a reason to start polling a send.
57#[derive(Debug, Clone, Copy, Default)]
58pub struct GrpcTaskDelivery;
59
60#[async_trait]
61impl WorkerTaskDelivery for GrpcTaskDelivery {
62    async fn deliver(
63        &self,
64        worker: &WorkerHandle,
65        task: &ProtoActivityTask,
66        intent: &SharedDeliveryIntent,
67        accepted: &dyn DeliveryAccepted,
68    ) -> TaskDelivery {
69        let Some(sender) = worker.sender() else {
70            // A worker with no stream sender is not a gRPC-delivered worker, so
71            // this transport genuinely cannot reach it. It is NOT the worker's
72            // fault and NOT evidence the worker is gone — which is why the
73            // dispatcher no longer routes here by default. Reaching this arm at
74            // all now means a caller chose the wrong transport for the worker it
75            // selected, so it reports a delivery failure and leaves the
76            // registration standing rather than destroying a healthy worker.
77            return TaskDelivery::failed(
78                "worker is not delivered over gRPC; the gRPC transport cannot reach it",
79            );
80        };
81        // The one wait this transport has is the backpressure wait inside
82        // `send().await` on a full channel. Asking the caller's intent before
83        // entering it is the same question the blocking transport re-asks
84        // throughout its own wait: a pass that has already lost its claim must
85        // not enqueue work it no longer owns.
86        if !intent.still_wanted() {
87            return TaskDelivery::failed("caller abandoned the delivery before it was enqueued");
88        }
89        match sender
90            .send(WorkerMessage::ActivityTask(Box::new(task.clone())))
91            .await
92        {
93            Ok(()) => {
94                // The stream took the frame: the worker holds the attempt from
95                // here, and the lease is recorded before anything else is
96                // said about it (WA-010 R3).
97                accepted.accepted().await;
98                TaskDelivery::Delivered
99            }
100            Err(error) => TaskDelivery::unreachable(format!("worker stream closed: {error}")),
101        }
102    }
103}