cloud_sdk/transport/streaming/io/
blocking.rs1use super::{
2 AbortGuard, BlockingStreamSink, BlockingStreamSource, ScratchGuard, StreamExecutionError,
3 StreamRead,
4};
5use crate::transport::{
6 StreamAttempt, StreamCompletion, StreamOutcome, StreamPartialState, StreamPolicy,
7 StreamProgressError,
8};
9
10pub fn drive_blocking_stream<S, D>(
12 policy: StreamPolicy,
13 source: &mut S,
14 sink: &mut D,
15 scratch: &mut [u8],
16 outcome: &mut StreamOutcome,
17) -> Result<StreamCompletion, StreamExecutionError<S::Error, D::Error>>
18where
19 S: BlockingStreamSource,
20 D: BlockingStreamSink,
21{
22 *outcome = StreamOutcome::new();
23 if scratch.is_empty() {
24 return Err(StreamExecutionError::EmptyScratch);
25 }
26 let mut scratch = ScratchGuard::new(scratch);
27 let mut guard = AbortGuard::new(sink, abort_blocking::<D>);
28 let mut attempt = StreamAttempt::new(policy, outcome);
29 let completion = pump(policy, source, &mut guard, &mut scratch, &mut attempt)?;
30 if let Err(error) = guard.sink().commit() {
31 attempt.mark_failed();
32 return Err(StreamExecutionError::Sink(error));
33 }
34 attempt
35 .commit_sink()
36 .map_err(StreamExecutionError::Progress)?;
37 guard.disarm();
38 Ok(completion)
39}
40
41fn pump<S, D>(
42 policy: StreamPolicy,
43 source: &mut S,
44 sink: &mut AbortGuard<'_, D>,
45 scratch: &mut ScratchGuard<'_>,
46 attempt: &mut StreamAttempt<'_>,
47) -> Result<StreamCompletion, StreamExecutionError<S::Error, D::Error>>
48where
49 S: BlockingStreamSource,
50 D: BlockingStreamSink,
51{
52 loop {
53 attempt
54 .begin_source_observation()
55 .map_err(StreamExecutionError::Progress)?;
56 let read_limit = core::cmp::min(scratch.bytes().len(), policy.limits().max_chunk_bytes());
57 let Some(output) = scratch.bytes().get_mut(..read_limit) else {
58 attempt.mark_failed();
59 return Err(StreamExecutionError::EmptyScratch);
60 };
61 let read = source.read_chunk(output).map_err(|error| {
62 attempt.mark_failed();
63 StreamExecutionError::Source(error)
64 })?;
65 match read {
66 StreamRead::End => return attempt.finish().map_err(StreamExecutionError::Progress),
67 StreamRead::Wait => attempt
68 .observe_wait()
69 .map_err(StreamExecutionError::Progress)?,
70 StreamRead::Chunk(len) => {
71 if len > output.len() {
72 attempt.mark_failed();
73 return Err(StreamExecutionError::InvalidSourceLength);
74 }
75 transfer(policy, sink, output, len, attempt)?;
76 }
77 }
78 }
79}
80
81fn transfer<S, D>(
82 policy: StreamPolicy,
83 sink: &mut AbortGuard<'_, D>,
84 chunk: &[u8],
85 len: usize,
86 attempt: &mut StreamAttempt<'_>,
87) -> Result<(), StreamExecutionError<S, D::Error>>
88where
89 D: BlockingStreamSink,
90{
91 attempt
92 .begin_chunk(len)
93 .map_err(StreamExecutionError::Progress)?;
94 let mut offset = 0_usize;
95 while offset < len {
96 let Some(input) = chunk.get(offset..len) else {
97 attempt.mark_failed();
98 return Err(StreamExecutionError::Progress(
99 StreamProgressError::ArithmeticOverflow,
100 ));
101 };
102 attempt
103 .begin_sink_observation()
104 .map_err(StreamExecutionError::Progress)?;
105 sink.record_write_attempt(policy);
106 let accepted = sink.sink().write_chunk(input).map_err(|error| {
107 attempt.mark_failed();
108 StreamExecutionError::Sink(error)
109 })?;
110 attempt
111 .advance(accepted)
112 .map_err(StreamExecutionError::Progress)?;
113 offset = offset.checked_add(accepted).ok_or_else(|| {
114 attempt.mark_failed();
115 StreamExecutionError::Progress(StreamProgressError::ArithmeticOverflow)
116 })?;
117 }
118 Ok(())
119}
120
121fn abort_blocking<S: BlockingStreamSink>(sink: &mut S, state: StreamPartialState) {
122 sink.abort(state);
123}