| 851 | } |
| 852 | |
| 853 | fn requeue(&mut self, id: SubmissionId) { |
| 854 | // Invariant: each `ReadyChannel` owns its own frame cursor. This |
| 855 | // encoder keeps at most one `current_channel` open at a time; when it |
| 856 | // closes (by size or timeout) it moves to `ready_channels` as the |
| 857 | // newest entry. `pending_ref.channel_idx` therefore always points to |
| 858 | // a specific, independent slot in `ready_channels`. Resetting the |
| 859 | // cursor on that slot does not affect any other channel and FIFO |
| 860 | // ordering across channels is preserved by construction. |
| 861 | let Some(pending_ref) = self.pending.remove(&id) else { |
| 862 | warn!(id = ?id, "requeue called for unknown submission id"); |
| 863 | return; |
| 864 | }; |
| 865 | |
| 866 | let chan_idx = pending_ref.channel_idx; |
| 867 | if chan_idx >= self.ready_channels.len() { |
| 868 | warn!(id = ?id, chan_idx = %chan_idx, "requeue: channel index out of bounds; submission lost"); |
| 869 | return; |
| 870 | } |
| 871 | |
| 872 | let channel = &mut self.ready_channels[chan_idx]; |
| 873 | channel.pending_confirmations = channel.pending_confirmations.saturating_sub(1); |
| 874 | // Rewind cursor to the first frame of the requeued submission so all frames |
| 875 | // in the batch are retried together. |
| 876 | if pending_ref.frame_start < channel.cursor { |
| 877 | channel.cursor = pending_ref.frame_start; |
| 878 | } |
| 879 | // Frames are back in pending state; re-increment the gauge. |
| 880 | BatcherMetrics::pending_frames().increment(pending_ref.frame_count as f64); |
| 881 | |
| 882 | debug!( |
| 883 | id = ?id, |
| 884 | frame_start = %pending_ref.frame_start, |
| 885 | frame_count = %pending_ref.frame_count, |
| 886 | "requeued submission frames back to pending" |
| 887 | ); |
| 888 | } |
| 889 | |
| 890 | fn force_close_channel(&mut self) { |
| 891 | debug!("force-closing current channel"); |