Skip to main content

apalis_core/backend/
sink.rs

1use std::{
2    pin::Pin,
3    task::{Context, Poll},
4};
5
6use crate::{
7    backend::{
8        Backend, BackendConfig, WireFormatBackend,
9        codec::Codec,
10        finalize::{Durable, Ephemeral},
11    },
12    error::BoxDynError,
13    task::{Task, builder::TaskBuilder},
14};
15use futures_channel::mpsc::SendError;
16use futures_core::Stream;
17use futures_sink::Sink;
18use futures_util::SinkExt;
19use futures_util::StreamExt;
20use futures_util::stream;
21
22/// Error type for TaskSink operations
23#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum TaskSinkError<PushError> {
26    /// Error occurred while pushing the task
27    #[error("Failed to push task: {0}")]
28    PushError(#[from] PushError),
29    /// Error occurred during encoding/decoding of the task
30    #[error("Failed to encode/decode task: {0}")]
31    CodecError(BoxDynError),
32
33    /// Error occurred while sending new task
34    #[error("Failed to send new task: {0}")]
35    SendError(SendError),
36}
37
38/// A sink for submitting tasks to a backend.
39///
40/// `TaskSink` provides two levels of task submission:
41///
42/// - The [`Sink`] methods ([`start_send`], [`poll_ready`], [`poll_flush`], and
43///   [`poll_close`]) provide low-level, poll-based control over submission.
44/// - The convenience methods ([`push`], [`push_bulk`], [`push_stream`], [`push_task`],
45///   and [`push_all`]) provide asynchronous ways to submit tasks without
46///   manually driving the sink.
47///
48/// # Task types
49///
50/// `Args` is the type accepted by the backend's high-level submission methods,
51/// while `Task<Args>` represents a fully constructed task with its associated
52/// task metadata.
53///
54/// `Kind` identifies the kind of backend being targeted.
55///
56/// [`start_send`]: Sink::start_send
57/// [`poll_ready`]: Sink::poll_ready
58/// [`poll_flush`]: Sink::poll_flush
59/// [`poll_close`]: Sink::poll_close
60/// [`push`]: TaskSink::push
61/// [`push_bulk`]: TaskSink::push_bulk
62/// [`push_stream`]: TaskSink::push_stream
63/// [`push_task`]: TaskSink::push_task
64/// [`push_all`]: TaskSink::push_all
65pub trait TaskSink<Args, Kind>: Backend {
66    /// Begins sending a task to the sink.
67    ///
68    /// This method is the counterpart to [`Sink::start_send`].
69    /// The caller must ensure that the sink is ready to accept the task by
70    /// successfully polling [`Sink::poll_ready`] first.
71    ///
72    /// The task may be buffered internally and may not be persisted until
73    /// [`Sink::poll_flush`] is driven to completion.
74    fn start_send(self: Pin<&mut Self>, item: Task<Args>)
75    -> Result<(), TaskSinkError<Self::Error>>;
76
77    /// Polls the sink until it is ready to accept another task.
78    ///
79    /// Returns [`Poll::Ready`] when a subsequent call to [`start_send`] may
80    /// be made.
81    ///
82    /// When [`Poll::Pending`] is returned, the sink is not currently ready and
83    /// the caller must wait for the provided waker to be notified before
84    /// polling again.
85    ///
86    /// [`start_send`]: Sink::start_send
87    fn poll_ready(
88        self: Pin<&mut Self>,
89        cx: &mut Context<'_>,
90    ) -> Poll<Result<(), TaskSinkError<Self::Error>>>;
91
92    /// Polls the sink until all previously submitted tasks have been flushed.
93    ///
94    /// A successful [`Poll::Ready`] indicates that all tasks accepted by the
95    /// sink have been flushed to the backend.
96    ///
97    /// This does not close the sink; additional tasks may be submitted after
98    /// a successful flush.
99    fn poll_flush(
100        self: Pin<&mut Self>,
101        cx: &mut Context<'_>,
102    ) -> Poll<Result<(), TaskSinkError<Self::Error>>>;
103
104    /// Polls the sink until it has been closed.
105    ///
106    /// Once closed, the sink must no longer accept new tasks.
107    ///
108    /// Implementations should flush any pending tasks before completing the
109    /// close operation.
110    fn poll_close(
111        self: Pin<&mut Self>,
112        cx: &mut Context<'_>,
113    ) -> Poll<Result<(), TaskSinkError<Self::Error>>>;
114
115    /// Pushes a single task into the backend.
116    ///
117    /// The returned future completes when the task has been accepted by the
118    /// backend.
119    fn push(
120        &mut self,
121        task: Args,
122    ) -> impl Future<Output = Result<(), TaskSinkError<Self::Error>>> + Send;
123
124    /// Pushes multiple tasks into the backend.
125    ///
126    /// Implementations may use a backend-specific bulk operation to submit
127    /// the tasks more efficiently than calling [`TaskSink::push`] for each task.
128    fn push_bulk(
129        &mut self,
130        tasks: Vec<Args>,
131    ) -> impl Future<Output = Result<(), TaskSinkError<Self::Error>>> + Send;
132
133    /// Pushes tasks from a stream into the backend.
134    ///
135    /// The stream is consumed until it is exhausted or an error occurs.
136    /// Implementations may process the stream incrementally rather than
137    /// collecting all tasks before submission.
138    fn push_stream(
139        &mut self,
140        tasks: impl Stream<Item = Args> + Unpin + Send,
141    ) -> impl Future<Output = Result<(), TaskSinkError<Self::Error>>> + Send;
142
143    /// Pushes a fully constructed task into the backend.
144    ///
145    /// Use this method when the task has already been constructed and its
146    /// metadata should be preserved rather than generated by the backend.
147    fn push_task(
148        &mut self,
149        task: Task<Args>,
150    ) -> impl Future<Output = Result<(), TaskSinkError<Self::Error>>> + Send;
151
152    /// Pushes fully constructed tasks from a stream into the backend.
153    ///
154    /// The stream is consumed until it is exhausted or an error occurs.
155    /// Implementations may process the stream incrementally rather than
156    /// collecting all tasks before submission.
157    fn push_all(
158        &mut self,
159        tasks: impl Stream<Item = Task<Args>> + Unpin + Send,
160    ) -> impl Future<Output = Result<(), TaskSinkError<Self::Error>>> + Send;
161}
162impl<Args, S, E, C> TaskSink<Args, Durable> for S
163where
164    S: Sink<Task<C::Compact>, Error = E>
165        + Unpin
166        + Backend<Error = E>
167        + WireFormatBackend<Codec = C>
168        + BackendConfig<Args = Args, Kind = Durable>
169        + Send,
170    Args: Send,
171    C::Compact: Send,
172    C: Codec<Args> + Clone + Send + Sync,
173    E: Send,
174    C::Error: std::error::Error + Send + Sync + 'static,
175{
176    fn start_send(
177        self: Pin<&mut Self>,
178        item: Task<Args>,
179    ) -> Result<(), TaskSinkError<Self::Error>> {
180        let codec = self.codec();
181        let task = item.try_map_args(|t| {
182            codec
183                .encode(&t)
184                .map_err(|e| TaskSinkError::CodecError(e.into()))
185        })?;
186        Sink::start_send(self, task).map_err(|e| TaskSinkError::PushError(e))
187    }
188
189    fn poll_ready(
190        self: Pin<&mut Self>,
191        cx: &mut Context<'_>,
192    ) -> Poll<Result<(), TaskSinkError<Self::Error>>> {
193        Sink::poll_ready(self, cx).map_err(|e| TaskSinkError::PushError(e))
194    }
195
196    fn poll_flush(
197        self: Pin<&mut Self>,
198        cx: &mut Context<'_>,
199    ) -> Poll<Result<(), TaskSinkError<Self::Error>>> {
200        Sink::poll_flush(self, cx).map_err(|e| TaskSinkError::PushError(e))
201    }
202
203    fn poll_close(
204        self: Pin<&mut Self>,
205        cx: &mut Context<'_>,
206    ) -> Poll<Result<(), TaskSinkError<Self::Error>>> {
207        Sink::poll_close(self, cx).map_err(|e| TaskSinkError::PushError(e))
208    }
209    async fn push(&mut self, task: Args) -> Result<(), TaskSinkError<Self::Error>> {
210        use futures_util::SinkExt;
211        let encoded = self
212            .codec()
213            .encode(&task)
214            .map_err(|e| TaskSinkError::CodecError(e.into()))?;
215        self.send(TaskBuilder::new(encoded).build()).await?;
216        Ok(())
217    }
218
219    async fn push_bulk(&mut self, tasks: Vec<Args>) -> Result<(), TaskSinkError<Self::Error>> {
220        use futures_util::SinkExt;
221        let tasks = tasks
222            .into_iter()
223            .map(TaskBuilder::new)
224            .map(|task| {
225                task.try_map_args(|t| {
226                    self.codec()
227                        .encode(&t)
228                        .map_err(|e| TaskSinkError::CodecError(e.into()))
229                })
230                .map(|t| t.build())
231            })
232            .collect::<Result<Vec<_>, _>>()?;
233        self.send_all(&mut stream::iter(tasks.into_iter().map(Ok)))
234            .await?;
235        Ok(())
236    }
237
238    async fn push_stream(
239        &mut self,
240        tasks: impl Stream<Item = Args> + Unpin + Send,
241    ) -> Result<(), TaskSinkError<Self::Error>> {
242        let codec = self.codec().clone();
243        self.sink_map_err(|e| TaskSinkError::PushError(e))
244            .send_all(&mut tasks.map(TaskBuilder::new).map(|task| {
245                task.try_map_args(|t| {
246                    codec
247                        .encode(&t)
248                        .map_err(|e| TaskSinkError::CodecError(e.into()))
249                })
250                .map(|t| t.build())
251            }))
252            .await
253    }
254
255    async fn push_task(&mut self, task: Task<Args>) -> Result<(), TaskSinkError<Self::Error>> {
256        use futures_util::SinkExt;
257        let codec = self.codec();
258        let task = task.try_map_args(|t| {
259            codec
260                .encode(&t)
261                .map_err(|e| TaskSinkError::CodecError(e.into()))
262        })?;
263        self.sink_map_err(|e| TaskSinkError::PushError(e))
264            .send(task)
265            .await
266    }
267
268    async fn push_all(
269        &mut self,
270        tasks: impl Stream<Item = Task<Args>> + Unpin + Send,
271    ) -> Result<(), TaskSinkError<Self::Error>> {
272        use futures_util::SinkExt;
273        let codec = self.codec().clone();
274        let mut encoded = tasks.map(|task| {
275            task.try_map_args(|t| {
276                codec
277                    .encode(&t)
278                    .map_err(|e| TaskSinkError::CodecError(e.into()))
279            })
280        });
281        self.sink_map_err(|e| TaskSinkError::PushError(e))
282            .send_all(&mut encoded)
283            .await
284    }
285}
286
287impl<Args, S, E> TaskSink<Args, Ephemeral> for S
288where
289    S: Sink<Task<Args>, Error = E>
290        + Unpin
291        + Backend<Error = E>
292        + BackendConfig<Args = Args, Kind = Ephemeral>
293        + Send,
294    Args: Send,
295    E: Send,
296{
297    fn start_send(
298        self: Pin<&mut Self>,
299        item: Task<Args>,
300    ) -> Result<(), TaskSinkError<Self::Error>> {
301        Sink::start_send(self, item).map_err(|e| TaskSinkError::PushError(e))
302    }
303    fn poll_ready(
304        self: Pin<&mut Self>,
305        cx: &mut Context<'_>,
306    ) -> Poll<Result<(), TaskSinkError<Self::Error>>> {
307        Sink::poll_ready(self, cx).map_err(|e| TaskSinkError::PushError(e))
308    }
309
310    fn poll_flush(
311        self: Pin<&mut Self>,
312        cx: &mut Context<'_>,
313    ) -> Poll<Result<(), TaskSinkError<Self::Error>>> {
314        Sink::poll_flush(self, cx).map_err(|e| TaskSinkError::PushError(e))
315    }
316
317    fn poll_close(
318        self: Pin<&mut Self>,
319        cx: &mut Context<'_>,
320    ) -> Poll<Result<(), TaskSinkError<Self::Error>>> {
321        Sink::poll_close(self, cx).map_err(|e| TaskSinkError::PushError(e))
322    }
323
324    async fn push(&mut self, args: Args) -> Result<(), TaskSinkError<Self::Error>> {
325        let task = TaskBuilder::new(args).build();
326        self.send(task)
327            .await
328            .map_err(|e| TaskSinkError::PushError(e))
329    }
330
331    async fn push_bulk(&mut self, tasks: Vec<Args>) -> Result<(), TaskSinkError<Self::Error>> {
332        let tasks = tasks
333            .into_iter()
334            .map(TaskBuilder::new)
335            .map(|t| Ok::<_, E>(t.build()))
336            .collect::<Result<Vec<_>, _>>()?;
337        self.send_all(&mut stream::iter(tasks.into_iter().map(Ok)))
338            .await
339            .map_err(|e| TaskSinkError::PushError(e))?;
340        Ok(())
341    }
342
343    async fn push_stream(
344        &mut self,
345        tasks: impl Stream<Item = Args> + Unpin + Send,
346    ) -> Result<(), TaskSinkError<Self::Error>> {
347        self.sink_map_err(|e| TaskSinkError::PushError(e))
348            .send_all(&mut tasks.map(TaskBuilder::new).map(|task| Ok(task.build())))
349            .await
350    }
351
352    async fn push_task(&mut self, task: Task<Args>) -> Result<(), TaskSinkError<Self::Error>> {
353        use futures_util::SinkExt;
354        self.sink_map_err(|e| TaskSinkError::PushError(e))
355            .send(task)
356            .await
357    }
358
359    async fn push_all(
360        &mut self,
361        tasks: impl Stream<Item = Task<Args>> + Unpin + Send,
362    ) -> Result<(), TaskSinkError<Self::Error>> {
363        use futures_util::SinkExt;
364
365        self.sink_map_err(|e| TaskSinkError::PushError(e))
366            .send_all(&mut tasks.map(Ok))
367            .await
368    }
369}