cloud_sdk/transport/streaming/
progress.rs1use super::policy::partial_state;
4use super::{StreamCompletion, StreamFraming, StreamKind, StreamPolicy};
5
6#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct StreamProgress {
9 bytes: u64,
10 chunks: u32,
11 observations: u32,
12 consecutive_zero_progress: u16,
13 pending_bytes: usize,
14}
15
16impl StreamProgress {
17 #[must_use]
19 pub const fn bytes(self) -> u64 {
20 self.bytes
21 }
22
23 #[must_use]
25 pub const fn chunks(self) -> u32 {
26 self.chunks
27 }
28
29 #[must_use]
31 pub const fn observations(self) -> u32 {
32 self.observations
33 }
34
35 #[must_use]
37 pub const fn consecutive_zero_progress(self) -> u16 {
38 self.consecutive_zero_progress
39 }
40
41 #[must_use]
43 pub const fn pending_bytes(self) -> usize {
44 self.pending_bytes
45 }
46}
47
48#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
50pub enum StreamState {
51 NotStarted,
53 Active,
55 Complete,
57 Cancelled,
59 Failed,
61}
62
63#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
65pub enum StreamPartialState {
66 Clean,
68 RollbackRequired,
70 Dirty,
72}
73
74#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub struct StreamOutcome {
77 state: StreamState,
78 progress: StreamProgress,
79 partial: StreamPartialState,
80}
81
82impl StreamOutcome {
83 #[must_use]
85 pub const fn new() -> Self {
86 Self {
87 state: StreamState::NotStarted,
88 progress: StreamProgress {
89 bytes: 0,
90 chunks: 0,
91 observations: 0,
92 consecutive_zero_progress: 0,
93 pending_bytes: 0,
94 },
95 partial: StreamPartialState::Clean,
96 }
97 }
98
99 #[must_use]
101 pub const fn state(self) -> StreamState {
102 self.state
103 }
104
105 #[must_use]
107 pub const fn progress(self) -> StreamProgress {
108 self.progress
109 }
110
111 #[must_use]
113 pub const fn partial_state(self) -> StreamPartialState {
114 self.partial
115 }
116}
117
118impl Default for StreamOutcome {
119 fn default() -> Self {
120 Self::new()
121 }
122}
123
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub enum StreamProgressError {
127 AttemptClosed,
129 BackpressurePending,
131 NoPendingChunk,
133 NoSinkObservation,
135 SinkObservationPending,
137 NoSourceObservation,
139 SourceObservationPending,
141 InvalidSinkProgress,
143 ChunkTooLarge,
145 ChunkLimitExceeded,
147 ObservationLimitExceeded,
149 ByteLimitExceeded,
151 DeclaredLengthExceeded,
153 DeclaredLengthMismatch,
155 UnexpectedEventEnd,
157 ZeroProgressLimitExceeded,
159 ArithmeticOverflow,
161}
162
163impl_static_error!(StreamProgressError,
164 Self::AttemptClosed => "stream attempt is closed",
165 Self::BackpressurePending => "stream chunk remains pending",
166 Self::NoPendingChunk => "stream has no pending chunk",
167 Self::NoSinkObservation => "stream sink observation was not preflighted",
168 Self::SinkObservationPending => "stream sink observation remains unclassified",
169 Self::NoSourceObservation => "stream source observation was not preflighted",
170 Self::SourceObservationPending => "stream source observation remains unclassified",
171 Self::InvalidSinkProgress => "stream sink reported invalid progress",
172 Self::ChunkTooLarge => "stream chunk exceeds the operation limit",
173 Self::ChunkLimitExceeded => "stream chunk budget is exhausted",
174 Self::ObservationLimitExceeded => "stream observation budget is exhausted",
175 Self::ByteLimitExceeded => "stream byte budget is exhausted",
176 Self::DeclaredLengthExceeded => "stream exceeds its declared length",
177 Self::DeclaredLengthMismatch => "stream length differs from its declaration",
178 Self::UnexpectedEventEnd => "caller-cancelled event stream ended unexpectedly",
179 Self::ZeroProgressLimitExceeded => "stream made no progress beyond its tolerance",
180 Self::ArithmeticOverflow => "stream accounting overflowed",
181);
182
183pub struct StreamAttempt<'outcome> {
189 policy: StreamPolicy,
190 progress: StreamProgress,
191 outcome: &'outcome mut StreamOutcome,
192 closed: bool,
193 failed: bool,
194 end_validated: bool,
195 source_observation_pending: bool,
196 sink_observation_pending: bool,
197 sink_write_attempted: bool,
198}
199
200impl<'outcome> StreamAttempt<'outcome> {
201 #[must_use]
203 pub fn new(policy: StreamPolicy, outcome: &'outcome mut StreamOutcome) -> Self {
204 *outcome = StreamOutcome {
205 state: StreamState::Active,
206 progress: StreamProgress::default(),
207 partial: StreamPartialState::Clean,
208 };
209 Self {
210 policy,
211 progress: StreamProgress::default(),
212 outcome,
213 closed: false,
214 failed: false,
215 end_validated: false,
216 source_observation_pending: false,
217 sink_observation_pending: false,
218 sink_write_attempted: false,
219 }
220 }
221
222 #[must_use]
224 pub const fn progress(&self) -> StreamProgress {
225 self.progress
226 }
227
228 pub fn begin_source_observation(&mut self) -> Result<(), StreamProgressError> {
233 self.ensure_open()?;
234 if self.progress.pending_bytes != 0 {
235 return self.reject(StreamProgressError::BackpressurePending);
236 }
237 if self.source_observation_pending {
238 return self.reject(StreamProgressError::SourceObservationPending);
239 }
240 if self.sink_observation_pending {
241 return self.reject(StreamProgressError::SinkObservationPending);
242 }
243 let observations = self.next_observation()?;
244 self.progress.observations = observations;
245 self.source_observation_pending = true;
246 self.sync_active();
247 Ok(())
248 }
249
250 pub fn begin_chunk(&mut self, len: usize) -> Result<(), StreamProgressError> {
255 self.ensure_open()?;
256 if !self.source_observation_pending {
257 return self.reject(StreamProgressError::NoSourceObservation);
258 }
259 if self.progress.pending_bytes != 0 {
260 return self.reject(StreamProgressError::BackpressurePending);
261 }
262 let limits = self.policy.limits();
263 if len > limits.max_chunk_bytes() {
264 return self.reject(StreamProgressError::ChunkTooLarge);
265 }
266 let Some(chunks) = self.progress.chunks.checked_add(1) else {
267 return self.reject(StreamProgressError::ArithmeticOverflow);
268 };
269 if chunks > limits.max_chunks() {
270 return self.reject(StreamProgressError::ChunkLimitExceeded);
271 }
272 let Ok(offered) = u64::try_from(len) else {
273 return self.reject(StreamProgressError::ArithmeticOverflow);
274 };
275 let Some(projected) = self.progress.bytes.checked_add(offered) else {
276 return self.reject(StreamProgressError::ArithmeticOverflow);
277 };
278 if projected > limits.max_bytes() {
279 return self.reject(StreamProgressError::ByteLimitExceeded);
280 }
281 if let StreamFraming::Declared(declared) = self.policy.framing()
282 && projected > declared
283 {
284 return self.reject(StreamProgressError::DeclaredLengthExceeded);
285 }
286 let zero = if len == 0 {
287 self.next_zero_progress()?
288 } else {
289 self.progress.consecutive_zero_progress
290 };
291 self.progress.chunks = chunks;
292 self.progress.consecutive_zero_progress = zero;
293 self.progress.pending_bytes = len;
294 self.source_observation_pending = false;
295 self.sync_active();
296 Ok(())
297 }
298
299 pub fn begin_sink_observation(&mut self) -> Result<(), StreamProgressError> {
302 self.ensure_open()?;
303 if self.source_observation_pending {
304 return self.reject(StreamProgressError::SourceObservationPending);
305 }
306 if self.sink_observation_pending {
307 return self.reject(StreamProgressError::SinkObservationPending);
308 }
309 if self.progress.pending_bytes == 0 {
310 return self.reject(StreamProgressError::NoPendingChunk);
311 }
312 let observations = self.next_observation()?;
313 self.progress.observations = observations;
314 self.sink_write_attempted = true;
315 self.sink_observation_pending = true;
316 self.sync_active();
317 Ok(())
318 }
319
320 pub fn advance(&mut self, accepted: usize) -> Result<(), StreamProgressError> {
322 self.ensure_open()?;
323 if !self.sink_observation_pending {
324 return self.reject(StreamProgressError::NoSinkObservation);
325 }
326 if self.progress.pending_bytes == 0 {
327 return self.reject(StreamProgressError::NoPendingChunk);
328 }
329 if accepted > self.progress.pending_bytes {
330 return self.reject(StreamProgressError::InvalidSinkProgress);
331 }
332 let Ok(accepted_u64) = u64::try_from(accepted) else {
333 return self.reject(StreamProgressError::ArithmeticOverflow);
334 };
335 let Some(bytes) = self.progress.bytes.checked_add(accepted_u64) else {
336 return self.reject(StreamProgressError::ArithmeticOverflow);
337 };
338 let Some(pending) = self.progress.pending_bytes.checked_sub(accepted) else {
339 return self.reject(StreamProgressError::ArithmeticOverflow);
340 };
341 let zero = if accepted == 0 {
342 self.next_zero_progress()?
343 } else {
344 0
345 };
346 self.progress.bytes = bytes;
347 self.progress.pending_bytes = pending;
348 self.progress.consecutive_zero_progress = zero;
349 self.sink_observation_pending = false;
350 self.sync_active();
351 Ok(())
352 }
353
354 pub fn observe_wait(&mut self) -> Result<(), StreamProgressError> {
356 self.ensure_open()?;
357 if !self.source_observation_pending {
358 return self.reject(StreamProgressError::NoSourceObservation);
359 }
360 let zero = self.next_zero_progress()?;
361 self.progress.consecutive_zero_progress = zero;
362 self.source_observation_pending = false;
363 self.sync_active();
364 Ok(())
365 }
366
367 pub fn mark_failed(&mut self) {
369 if self.closed {
370 return;
371 }
372 self.failed = true;
373 self.sync(StreamState::Failed);
374 }
375
376 pub fn finish(&mut self) -> Result<StreamCompletion, StreamProgressError> {
378 self.ensure_open()?;
379 if !self.source_observation_pending {
380 return self.reject(StreamProgressError::NoSourceObservation);
381 }
382 if matches!(self.policy.kind(), StreamKind::CallerCancelledEvent) {
383 return self.reject(StreamProgressError::UnexpectedEventEnd);
384 }
385 if self.progress.pending_bytes != 0 {
386 return self.reject(StreamProgressError::BackpressurePending);
387 }
388 if let StreamFraming::Declared(declared) = self.policy.framing()
389 && self.progress.bytes != declared
390 {
391 return self.reject(StreamProgressError::DeclaredLengthMismatch);
392 }
393 self.source_observation_pending = false;
394 self.end_validated = true;
395 self.sync_active();
396 Ok(StreamCompletion {
397 progress: self.progress,
398 sink_mode: self.policy.sink_mode(),
399 })
400 }
401
402 pub fn commit_sink(&mut self) -> Result<(), StreamProgressError> {
404 if self.closed || self.failed {
405 return Err(StreamProgressError::AttemptClosed);
406 }
407 if !self.end_validated {
408 return self.reject(StreamProgressError::AttemptClosed);
409 }
410 self.closed = true;
411 self.sync(StreamState::Complete);
412 Ok(())
413 }
414
415 fn ensure_open(&mut self) -> Result<(), StreamProgressError> {
416 if self.closed || self.failed || self.end_validated {
417 return Err(StreamProgressError::AttemptClosed);
418 }
419 Ok(())
420 }
421
422 fn next_observation(&mut self) -> Result<u32, StreamProgressError> {
423 let Some(next) = self.progress.observations.checked_add(1) else {
424 return self.reject(StreamProgressError::ArithmeticOverflow);
425 };
426 if next > self.policy.limits().max_observations() {
427 return self.reject(StreamProgressError::ObservationLimitExceeded);
428 }
429 Ok(next)
430 }
431
432 fn next_zero_progress(&mut self) -> Result<u16, StreamProgressError> {
433 let Some(next) = self.progress.consecutive_zero_progress.checked_add(1) else {
434 return self.reject(StreamProgressError::ArithmeticOverflow);
435 };
436 if next > self.policy.limits().max_consecutive_zero_progress() {
437 return self.reject(StreamProgressError::ZeroProgressLimitExceeded);
438 }
439 Ok(next)
440 }
441
442 fn reject<T>(&mut self, error: StreamProgressError) -> Result<T, StreamProgressError> {
443 self.failed = true;
444 self.sync(StreamState::Failed);
445 Err(error)
446 }
447
448 fn sync_active(&mut self) {
449 self.sync(StreamState::Active);
450 }
451
452 fn sync(&mut self, state: StreamState) {
453 let partial = if matches!(state, StreamState::Complete) || !self.sink_write_attempted {
454 StreamPartialState::Clean
455 } else {
456 partial_state(self.policy.sink_mode(), true)
457 };
458 *self.outcome = StreamOutcome {
459 state,
460 progress: self.progress,
461 partial,
462 };
463 }
464}
465
466impl Drop for StreamAttempt<'_> {
467 fn drop(&mut self) {
468 if !self.closed {
469 let state = if self.failed {
470 StreamState::Failed
471 } else {
472 StreamState::Cancelled
473 };
474 self.sync(state);
475 }
476 }
477}