From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 790D61FF0E6 for ; Fri, 24 Jul 2026 13:09:00 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id C811921490; Fri, 24 Jul 2026 13:08:59 +0200 (CEST) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v4 2/3] api: add heartbeat endpoint for backup reader/writer http/2 clients Date: Fri, 24 Jul 2026 13:07:34 +0200 Message-ID: <20260724110735.464113-3-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260724110735.464113-1-c.ebner@proxmox.com> References: <20260724110735.464113-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1784891304991 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.161 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_LOW -0.7 Sender listed at https://www.dnswl.org/, low trust 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: DERE6AZ4QSHXB3DH7NFL7UBNWNNC3OXA X-Message-ID-Hash: DERE6AZ4QSHXB3DH7NFL7UBNWNNC3OXA X-MailFrom: c.ebner@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: Add dedicated API endpoints to send http level heartbeat requests for the backup reader and backup writer, used to keep otherwise idle tcp connections to be dropped by proxies. By making heartbeats part of the H2 service, these endpoints are only available for active sessions after the http/2 upgrade. Heartbeat requests can happen with a frequency as high as 1/s, adding individual lines to the task log. Therefore, use the http/2 service filter function of the rest server to exclude these request from being logged by matching method and path accordingly. Signed-off-by: Christian Ebner --- src/api2/backup/mod.rs | 15 +++++++++++++-- src/api2/helpers.rs | 14 +++++++++++++- src/api2/reader/mod.rs | 15 +++++++++++++-- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/api2/backup/mod.rs b/src/api2/backup/mod.rs index 6d4fcb307..bbfcb4f8d 100644 --- a/src/api2/backup/mod.rs +++ b/src/api2/backup/mod.rs @@ -1,5 +1,7 @@ //! Backup protocol (HTTP2 upgrade) +use std::sync::Arc; + use anyhow::{Context, Error, bail, format_err}; use futures::*; use hex::FromHex; @@ -240,8 +242,13 @@ fn upgrade_to_backup_protocol( "starting new {worker_type} on datastore '{store}'{origin}: {path:?}", )); - let service = - H2Service::new(env.clone(), worker.clone(), &BACKUP_API_ROUTER, debug); + let service = H2Service::new( + env.clone(), + worker.clone(), + &BACKUP_API_ROUTER, + debug, + Some(Arc::new(crate::api2::helpers::heartbeat_request_filter)), + ); let abort_future = worker.abort_future(); @@ -396,6 +403,10 @@ const BACKUP_API_SUBDIRS: SubdirMap = &[ .post(&API_METHOD_CREATE_FIXED_INDEX) .put(&API_METHOD_FIXED_APPEND), ), + ( + "heartbeat", + &Router::new().get(&crate::api2::helpers::API_METHOD_HEARTBEAT), + ), ( "previous", &Router::new().download(&API_METHOD_DOWNLOAD_PREVIOUS), diff --git a/src/api2/helpers.rs b/src/api2/helpers.rs index aa582a53c..2e38776fe 100644 --- a/src/api2/helpers.rs +++ b/src/api2/helpers.rs @@ -5,7 +5,8 @@ use futures::stream::TryStreamExt; use hyper::{Response, StatusCode, header}; use proxmox_http::Body; -use proxmox_router::http_bail; +use proxmox_router::{RpcEnvironment, http_bail}; +use proxmox_schema::api; pub async fn create_download_response(path: PathBuf) -> Result, Error> { let file = match tokio::fs::File::open(path.clone()).await { @@ -28,3 +29,14 @@ pub async fn create_download_response(path: PathBuf) -> Result, E .body(body) .unwrap()) } + +#[api()] +/// HTTP level heartbeat to avoid proxies closing long running idle backup reader/writer connections. +pub fn heartbeat(_rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> { + Ok(()) +} + +// filter heartbeat requests from being written to task log +pub(super) fn heartbeat_request_filter(method: &hyper::Method, path: &str) -> bool { + !(method == hyper::Method::GET && path == "/heartbeat") +} diff --git a/src/api2/reader/mod.rs b/src/api2/reader/mod.rs index 4321d2286..f4982af6d 100644 --- a/src/api2/reader/mod.rs +++ b/src/api2/reader/mod.rs @@ -1,5 +1,7 @@ //! Backup reader/restore protocol (HTTP2 upgrade) +use std::sync::Arc; + use anyhow::{Context, Error, bail, format_err}; use futures::*; use hex::FromHex; @@ -173,8 +175,13 @@ fn upgrade_to_backup_reader_protocol( "starting new backup reader datastore '{store}': {path:?}" )); - let service = - H2Service::new(env.clone(), worker.clone(), &READER_API_ROUTER, debug); + let service = H2Service::new( + env.clone(), + worker.clone(), + &READER_API_ROUTER, + debug, + Some(Arc::new(crate::api2::helpers::heartbeat_request_filter)), + ); let mut abort_future = worker .abort_future() @@ -228,6 +235,10 @@ const READER_API_SUBDIRS: SubdirMap = &[ "download", &Router::new().download(&API_METHOD_DOWNLOAD_FILE), ), + ( + "heartbeat", + &Router::new().get(&crate::api2::helpers::API_METHOD_HEARTBEAT), + ), ("speedtest", &Router::new().download(&API_METHOD_SPEEDTEST)), ]; -- 2.47.3