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 6F7E71FF133 for ; Mon, 11 May 2026 15:46:23 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id DBE611ABBC; Mon, 11 May 2026 15:46:21 +0200 (CEST) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v2 2/3] api: add heartbeat endpoint for backup reader/writer http/2 clients Date: Mon, 11 May 2026 15:46:09 +0200 Message-ID: <20260511134610.675164-3-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260511134610.675164-1-c.ebner@proxmox.com> References: <20260511134610.675164-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: 1778507062528 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.070 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [helpers.rs,mod.rs] Message-ID-Hash: TXJT7FG7Q25COMWYPU3ABUHSQZJSZNRK X-Message-ID-Hash: TXJT7FG7Q25COMWYPU3ABUHSQZJSZNRK 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 86ec49487..7fd9af6a3 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 f346b0cca..4b0634515 100644 --- a/src/api2/helpers.rs +++ b/src/api2/helpers.rs @@ -5,7 +5,8 @@ use futures::stream::TryStreamExt; use hyper::{header, Response, StatusCode}; use proxmox_http::Body; -use proxmox_router::http_bail; +use proxmox_router::{http_bail, RpcEnvironment}; +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 a814ba5f7..ab74eb1ec 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