cloud_sdk/transport/streaming/io/
asynchronous.rs1use super::{
2 AbortGuard, AsyncStreamSink, AsyncStreamSource, LocalAsyncStreamSink, LocalAsyncStreamSource,
3 ScratchGuard, StreamExecutionError, StreamRead,
4};
5use crate::transport::{
6 StreamAttempt, StreamCompletion, StreamOutcome, StreamPartialState, StreamPolicy,
7 StreamProgressError,
8};
9use core::{
10 future::Future,
11 pin::Pin,
12 task::{Context, Poll},
13};
14
15const MAX_CALLBACKS_BEFORE_YIELD: u16 = 64;
16
17pub async fn drive_local_stream<S, D>(
19 policy: StreamPolicy,
20 source: &mut S,
21 sink: &mut D,
22 scratch: &mut [u8],
23 outcome: &mut StreamOutcome,
24) -> Result<StreamCompletion, StreamExecutionError<S::Error, D::Error>>
25where
26 S: LocalAsyncStreamSource,
27 D: LocalAsyncStreamSink,
28{
29 *outcome = StreamOutcome::new();
30 if scratch.is_empty() {
31 return Err(StreamExecutionError::EmptyScratch);
32 }
33 let mut scratch = ScratchGuard::new(scratch);
34 let mut guard = AbortGuard::new(sink, abort_local::<D>);
35 let mut attempt = StreamAttempt::new(policy, outcome);
36 let mut cooperation = CooperativeBudget::new();
37 let completion = loop {
38 attempt
39 .begin_source_observation()
40 .map_err(StreamExecutionError::Progress)?;
41 let read_limit = core::cmp::min(scratch.bytes().len(), policy.limits().max_chunk_bytes());
42 let Some(output) = scratch.bytes().get_mut(..read_limit) else {
43 attempt.mark_failed();
44 return Err(StreamExecutionError::EmptyScratch);
45 };
46 let read = match source.read_chunk_local(output).await {
47 Ok(read) => read,
48 Err(error) => {
49 attempt.mark_failed();
50 return Err(StreamExecutionError::Source(error));
51 }
52 };
53 match read {
54 StreamRead::End => {
55 let completion = attempt.finish().map_err(StreamExecutionError::Progress)?;
56 cooperation.after_callback().await;
57 break completion;
58 }
59 StreamRead::Wait => {
60 attempt
61 .observe_wait()
62 .map_err(StreamExecutionError::Progress)?;
63 cooperation.after_callback().await;
64 }
65 StreamRead::Chunk(len) => {
66 validate_source_length(len, output.len(), &mut attempt)?;
67 attempt
68 .begin_chunk(len)
69 .map_err(StreamExecutionError::Progress)?;
70 cooperation.after_callback().await;
71 let mut offset = 0_usize;
72 while offset < len {
73 let Some(input) = output.get(offset..len) else {
74 attempt.mark_failed();
75 return Err(arithmetic_error());
76 };
77 attempt
78 .begin_sink_observation()
79 .map_err(StreamExecutionError::Progress)?;
80 guard.record_write_attempt(policy);
81 let accepted = match guard.sink().write_chunk_local(input).await {
82 Ok(accepted) => accepted,
83 Err(error) => {
84 attempt.mark_failed();
85 return Err(StreamExecutionError::Sink(error));
86 }
87 };
88 advance::<S::Error, D::Error>(&mut attempt, &mut offset, accepted)?;
89 cooperation.after_callback().await;
90 }
91 }
92 }
93 };
94 if let Err(error) = guard.sink().commit_local().await {
95 attempt.mark_failed();
96 return Err(StreamExecutionError::Sink(error));
97 }
98 attempt
99 .commit_sink()
100 .map_err(StreamExecutionError::Progress)?;
101 guard.disarm();
102 Ok(completion)
103}
104
105pub async fn drive_async_stream<S, D>(
107 policy: StreamPolicy,
108 source: &mut S,
109 sink: &mut D,
110 scratch: &mut [u8],
111 outcome: &mut StreamOutcome,
112) -> Result<StreamCompletion, StreamExecutionError<S::Error, D::Error>>
113where
114 S: AsyncStreamSource + Send,
115 D: AsyncStreamSink + Send,
116{
117 *outcome = StreamOutcome::new();
118 if scratch.is_empty() {
119 return Err(StreamExecutionError::EmptyScratch);
120 }
121 let mut scratch = ScratchGuard::new(scratch);
122 let mut guard = AbortGuard::new(sink, abort_async::<D>);
123 let mut attempt = StreamAttempt::new(policy, outcome);
124 let mut cooperation = CooperativeBudget::new();
125 let completion = loop {
126 attempt
127 .begin_source_observation()
128 .map_err(StreamExecutionError::Progress)?;
129 let read_limit = core::cmp::min(scratch.bytes().len(), policy.limits().max_chunk_bytes());
130 let Some(output) = scratch.bytes().get_mut(..read_limit) else {
131 attempt.mark_failed();
132 return Err(StreamExecutionError::EmptyScratch);
133 };
134 let read = match source.read_chunk(output).await {
135 Ok(read) => read,
136 Err(error) => {
137 attempt.mark_failed();
138 return Err(StreamExecutionError::Source(error));
139 }
140 };
141 match read {
142 StreamRead::End => {
143 let completion = attempt.finish().map_err(StreamExecutionError::Progress)?;
144 cooperation.after_callback().await;
145 break completion;
146 }
147 StreamRead::Wait => {
148 attempt
149 .observe_wait()
150 .map_err(StreamExecutionError::Progress)?;
151 cooperation.after_callback().await;
152 }
153 StreamRead::Chunk(len) => {
154 validate_source_length(len, output.len(), &mut attempt)?;
155 attempt
156 .begin_chunk(len)
157 .map_err(StreamExecutionError::Progress)?;
158 cooperation.after_callback().await;
159 let mut offset = 0_usize;
160 while offset < len {
161 let Some(input) = output.get(offset..len) else {
162 attempt.mark_failed();
163 return Err(arithmetic_error());
164 };
165 attempt
166 .begin_sink_observation()
167 .map_err(StreamExecutionError::Progress)?;
168 guard.record_write_attempt(policy);
169 let accepted = match guard.sink().write_chunk(input).await {
170 Ok(accepted) => accepted,
171 Err(error) => {
172 attempt.mark_failed();
173 return Err(StreamExecutionError::Sink(error));
174 }
175 };
176 advance::<S::Error, D::Error>(&mut attempt, &mut offset, accepted)?;
177 cooperation.after_callback().await;
178 }
179 }
180 }
181 };
182 if let Err(error) = guard.sink().commit().await {
183 attempt.mark_failed();
184 return Err(StreamExecutionError::Sink(error));
185 }
186 attempt
187 .commit_sink()
188 .map_err(StreamExecutionError::Progress)?;
189 guard.disarm();
190 Ok(completion)
191}
192
193fn validate_source_length<S, D>(
194 len: usize,
195 capacity: usize,
196 attempt: &mut StreamAttempt<'_>,
197) -> Result<(), StreamExecutionError<S, D>> {
198 if len > capacity {
199 attempt.mark_failed();
200 return Err(StreamExecutionError::InvalidSourceLength);
201 }
202 Ok(())
203}
204
205fn advance<S, E>(
206 attempt: &mut StreamAttempt<'_>,
207 offset: &mut usize,
208 accepted: usize,
209) -> Result<(), StreamExecutionError<S, E>> {
210 attempt
211 .advance(accepted)
212 .map_err(StreamExecutionError::Progress)?;
213 *offset = offset.checked_add(accepted).ok_or_else(|| {
214 attempt.mark_failed();
215 arithmetic_error()
216 })?;
217 Ok(())
218}
219
220const fn arithmetic_error<S, D>() -> StreamExecutionError<S, D> {
221 StreamExecutionError::Progress(StreamProgressError::ArithmeticOverflow)
222}
223
224fn abort_local<S: LocalAsyncStreamSink>(sink: &mut S, state: StreamPartialState) {
225 sink.abort_local(state);
226}
227
228fn abort_async<S: AsyncStreamSink>(sink: &mut S, state: StreamPartialState) {
229 sink.abort(state);
230}
231
232struct CooperativeBudget {
233 completed_callbacks: u16,
234}
235
236impl CooperativeBudget {
237 const fn new() -> Self {
238 Self {
239 completed_callbacks: 0,
240 }
241 }
242
243 async fn after_callback(&mut self) {
244 if self.completed_callbacks == MAX_CALLBACKS_BEFORE_YIELD - 1 {
245 self.completed_callbacks = 0;
246 YieldOnce { yielded: false }.await;
247 } else if let Some(next) = self.completed_callbacks.checked_add(1) {
248 self.completed_callbacks = next;
249 } else {
250 self.completed_callbacks = 0;
251 YieldOnce { yielded: false }.await;
252 }
253 }
254}
255
256struct YieldOnce {
257 yielded: bool,
258}
259
260impl Future for YieldOnce {
261 type Output = ();
262
263 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
264 if self.yielded {
265 Poll::Ready(())
266 } else {
267 self.yielded = true;
268 context.waker().wake_by_ref();
269 Poll::Pending
270 }
271 }
272}