From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 3BFF01FF139 for ; Tue, 10 Feb 2026 16:07:15 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E7F256ECB; Tue, 10 Feb 2026 16:07:59 +0100 (CET) From: Robert Obkircher To: pbs-devel@lists.proxmox.com Subject: [PATCH v6 proxmox-backup 13/18] client: don't poll terminated source in ChunkStream Date: Tue, 10 Feb 2026 16:06:29 +0100 Message-ID: <20260210150642.469670-14-r.obkircher@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260210150642.469670-1-r.obkircher@proxmox.com> References: <20260210150642.469670-1-r.obkircher@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1770735961570 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.057 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: EIICLVCBIAES2XWLYRUUNJGLBSP4ESNV X-Message-ID-Hash: EIICLVCBIAES2XWLYRUUNJGLBSP4ESNV X-MailFrom: r.obkircher@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The ChunkStream may return a partial chunk after the input is done. This is fine, but it must remember to not poll the input again when it is polled one more time. The DummyInput of the test case now verifies that it isn't polled after it returned None. Signed-off-by: Robert Obkircher --- pbs-client/src/chunk_stream.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pbs-client/src/chunk_stream.rs b/pbs-client/src/chunk_stream.rs index 94a45ac7..9763bbd7 100644 --- a/pbs-client/src/chunk_stream.rs +++ b/pbs-client/src/chunk_stream.rs @@ -34,6 +34,7 @@ impl InjectionData { /// Split input stream into dynamic sized chunks pub struct ChunkStream { input: S, + input_is_done: bool, chunker: Box, buffer: BytesMut, scan_pos: usize, @@ -51,6 +52,7 @@ impl ChunkStream { let chunk_size = chunk_size.unwrap_or(4 * 1024 * 1024); Self { input, + input_is_done: false, chunker: if let Some(suggested) = suggested_boundaries { Box::new(PayloadChunker::new(chunk_size, suggested)) } else { @@ -162,12 +164,16 @@ where } } + if this.input_is_done { + return Poll::Ready(None); + } match ready!(Pin::new(&mut this.input).try_poll_next(cx)) { Some(Err(err)) => { return Poll::Ready(Some(Err(err.into()))); } None => { this.scan_pos = 0; + this.input_is_done = true; if !this.buffer.is_empty() { return Poll::Ready(Some(Ok(this.buffer.split()))); } else { @@ -254,11 +260,12 @@ mod test { struct DummyInput { data: Vec, + done: bool, } impl DummyInput { fn new(data: Vec) -> Self { - Self { data } + Self { data, done: false } } } @@ -268,7 +275,11 @@ mod test { fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { let this = self.get_mut(); match this.data.len() { - 0 => Poll::Ready(None), + 0 => { + assert!(!this.done); + this.done = true; + Poll::Ready(None) + } size if size > 10 => Poll::Ready(Some(Ok(this.data.split_off(10)))), _ => Poll::Ready(Some(Ok(std::mem::take(&mut this.data)))), } -- 2.47.3