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 9C64A1FF138 for ; Tue, 21 Jul 2026 13:48:58 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 78267214B8; Tue, 21 Jul 2026 13:48:58 +0200 (CEST) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v3 2/3] api: add heartbeat endpoint for backup reader/writer http/2 clients Date: Tue, 21 Jul 2026 13:48:05 +0200 Message-ID: <20260721114806.505047-3-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260721114806.505047-1-c.ebner@proxmox.com> References: <20260721114806.505047-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: 1784634478815 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.197 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: YKTW6NF7C7RFUBGELZMBXES5KD5VOWRQ X-Message-ID-Hash: YKTW6NF7C7RFUBGELZMBXES5KD5VOWRQ 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 | 13 +++++++++++-- src/api2/helpers.rs | 14 +++++++++++++- src/api2/reader/mod.rs | 13 +++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/api2/backup/mod.rs b/src/api2/backup/mod.rs index 6d4fcb307..630e42271 100644 --- a/src/api2/backup/mod.rs +++ b/src/api2/backup/mod.rs @@ -240,8 +240,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(crate::api2::helpers::heartbeat_request_filter), + ); let abort_future = worker.abort_future(); @@ -396,6 +401,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..56e1c5135 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..a3cd51760 100644 --- a/src/api2/reader/mod.rs +++ b/src/api2/reader/mod.rs @@ -173,8 +173,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(crate::api2::helpers::heartbeat_request_filter), + ); let mut abort_future = worker .abort_future() @@ -228,6 +233,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