From 7996cfbb06c48c207e3e324f8c8688b6263a9648 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it <75116288+FreddleSpl0it@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:21:05 +0200 Subject: [PATCH] [Web][Dovecot] Add global bandwidth limit for syncjobs --- data/Dockerfiles/dovecot/imapsync_runner.pl | 27 ++++++++++++++++--- data/web/inc/functions.mailbox.inc.php | 10 +++++++ data/web/lang/lang.de-de.json | 1 + data/web/lang/lang.en-gb.json | 1 + .../templates/admin/tab-config-syncjobs.twig | 6 +++++ 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/dovecot/imapsync_runner.pl b/data/Dockerfiles/dovecot/imapsync_runner.pl index 07341f748..f8782c997 100644 --- a/data/Dockerfiles/dovecot/imapsync_runner.pl +++ b/data/Dockerfiles/dovecot/imapsync_runner.pl @@ -77,6 +77,11 @@ sub run_one_job { print $passfile1 "$password1\n"; print $passfile2 trim($master_pass) . "\n"; + my $effective_bps = $maxbytespersecond; + if ($effective_bps eq "0" && $global_max_bps > 0) { + $effective_bps = $global_max_bps; + } + my @custom_params_a = qqw($custom_params); my $custom_params_ref = \@custom_params_a; @@ -89,7 +94,7 @@ sub run_one_job { ($exclude eq "" ? () : ("--exclude", $exclude)), ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), ($maxage eq "0" ? () : ('--maxage', $maxage)), - ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)), + ($effective_bps eq "0" ? () : ('--maxbytespersecond', $effective_bps)), ($delete2duplicates ne "1" ? () : ('--delete2duplicates')), ($subscribeall ne "1" ? () : ('--subscribeall')), ($delete1 ne "1" ? () : ('--delete')), @@ -146,6 +151,7 @@ my $lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); $lockmgr->lock($lock_file) || die "can't lock ${lock_file}"; my $max_parallel = 1; +our $global_max_bps = 0; try { my $redis = Redis->new( server => 'redis-mailcow:6379', @@ -157,9 +163,13 @@ try { if (defined $val && $val =~ /^\d+$/) { $max_parallel = int($val); } + my $bps = $redis->get('SYNCJOBS_MAX_BPS'); + if (defined $bps && $bps =~ /^\d+$/) { + $global_max_bps = int($bps); + } $redis->quit(); } catch { - warn "Could not read SYNCJOBS_MAX_PARALLEL from Redis, defaulting to 1: $_"; + warn "Could not read settings from Redis, using defaults: $_"; }; $max_parallel = 1 if $max_parallel < 1; $max_parallel = 50 if $max_parallel > 50; @@ -227,7 +237,18 @@ foreach my $job (@jobs) { mysql_auto_reconnect => 1, mysql_enable_utf8mb4 => 1 }); - run_one_job($child_dbh, $job, $master_user, $master_pass); + eval { + run_one_job($child_dbh, $job, $master_user, $master_pass); + }; + if ($@) { + warn "run_one_job died for job $job->[0]: $@"; + eval { + my $err_sth = $child_dbh->prepare("UPDATE imapsync SET last_run = NOW(), is_running = 0, success = 0, returned_text = ? WHERE id = ?"); + $err_sth->bind_param(1, "Runner error: $@"); + $err_sth->bind_param(2, $job->[0]); + $err_sth->execute(); + }; + } $child_dbh->disconnect(); $pm->finish; diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 1073ec84d..921b6d752 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -2277,8 +2277,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $max_parallel = intval($_data['max_parallel']); if ($max_parallel < 1) { $max_parallel = 1; } if ($max_parallel > 50) { $max_parallel = 50; } + $max_bps = intval($_data['max_bps']); + if ($max_bps < 0) { $max_bps = 0; } try { $redis->Set('SYNCJOBS_MAX_PARALLEL', $max_parallel); + $redis->Set('SYNCJOBS_MAX_BPS', $max_bps); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -4660,6 +4663,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $max_parallel = null; } $settings['max_parallel'] = intval($max_parallel) ?: 1; + try { + $max_bps = $redis->Get('SYNCJOBS_MAX_BPS'); + } + catch (RedisException $e) { + $max_bps = null; + } + $settings['max_bps'] = intval($max_bps) ?: 0; return $settings; break; case 'syncjobs': diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 122531ad1..ac9fd5ee6 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -373,6 +373,7 @@ "subject": "Betreff", "success": "Erfolg", "syncjobs": "Sync-Jobs", + "syncjobs_max_bps": "Globales Bandbreitenlimit (Bytes/s)
Maximale Transferrate pro Sync-Job in Bytes pro Sekunde. 0 = kein Limit. Wird durch ein pro Sync-Job gesetztes Limit überschrieben.", "syncjobs_max_parallel": "Maximale Anzahl paralleler Sync-Jobs
Wie viele imapsync-Prozesse dürfen gleichzeitig laufen. 1 = sequentiell (aktuelles Verhalten).", "sys_mails": "System-E-Mails", "task": "Aufgabe", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index eaa478a60..0b86e5bee 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -383,6 +383,7 @@ "subject": "Subject", "success": "Success", "syncjobs": "Sync jobs", + "syncjobs_max_bps": "Global bandwidth limit (bytes/s)
Maximum transfer rate per sync job in bytes per second. 0 = no limit. Overridden by a per-job limit.", "syncjobs_max_parallel": "Maximum parallel sync jobs
How many imapsync processes are allowed to run in parallel. 1 = sequential (legacy behavior).", "sys_mails": "System mails", "task": "Task", diff --git a/data/web/templates/admin/tab-config-syncjobs.twig b/data/web/templates/admin/tab-config-syncjobs.twig index 1e2c6e064..fe9091a71 100644 --- a/data/web/templates/admin/tab-config-syncjobs.twig +++ b/data/web/templates/admin/tab-config-syncjobs.twig @@ -14,6 +14,12 @@ +
+ +
+ +
+