Serialize cache clear against all cache workers

This commit is contained in:
Terranom674
2026-09-05 23:42:07 +02:00
parent 50f4343029
commit 5a9980de24

View File

@@ -60,6 +60,114 @@ function bratonien_tools_atomic_cache_remove_tree($root, array &$failed)
if (!@rmdir($root) && is_dir($root)) $failed[] = $root; if (!@rmdir($root) && is_dir($root)) $failed[] = $root;
} }
function bratonien_tools_atomic_cache_acquire_lock($path, $label, $timeout_seconds=10.0)
{
$directory = dirname($path);
if (!is_dir($directory) && !@mkdir($directory, 0775, true) && !is_dir($directory))
{
throw new RuntimeException($label.'-Lockverzeichnis konnte nicht angelegt werden.');
}
$handle = @fopen($path, 'c');
if (!$handle)
{
throw new RuntimeException($label.'-Lock konnte nicht geöffnet werden: '.$path);
}
$deadline = microtime(true) + max(0.1, (float)$timeout_seconds);
do
{
if (@flock($handle, LOCK_EX | LOCK_NB))
{
return $handle;
}
usleep(100000);
}
while (microtime(true) < $deadline);
fclose($handle);
throw new RuntimeException($label.' ist noch aktiv. Der Cache wurde nicht angefasst; bitte den laufenden Vorgang kurz abschließen lassen und erneut leeren.');
}
function bratonien_tools_atomic_cache_release_locks(array &$locks)
{
foreach (array_reverse($locks) as $handle)
{
if (!is_resource($handle)) continue;
@flock($handle, LOCK_UN);
fclose($handle);
}
$locks = array();
}
function bratonien_tools_atomic_cache_prepare_webdav_guards(array &$locks, array &$cancel_files)
{
if (!function_exists('bratonien_tools_webdav_warmup_connections')) return 0;
$connections = bratonien_tools_webdav_warmup_connections();
if (!$connections) return 0;
ksort($connections, SORT_NUMERIC);
// Jeder aktive WebDAV-Worker bekommt zuerst sein reguläres Abbruchsignal.
// Ein bereits gestarteter 10er-Batch darf sauber fertig werden; ein neuer
// Batch darf danach nicht mehr beginnen.
if (function_exists('bratonien_tools_request_webdav_cache_cancel'))
{
bratonien_tools_request_webdav_cache_cancel();
}
// Das Signal wird für alle Verbindungen gesetzt, auch wenn deren Statusdatei
// veraltet sein sollte. Sobald wir anschließend den Prozess-Lock besitzen,
// kann garantiert kein WebDAV-Worker während des Cache-Leerens schreiben.
foreach ($connections as $connection_id=>$runtime)
{
$cancel = function_exists('bratonien_tools_webdav_warmup_cancel_file_for_connection')
? bratonien_tools_webdav_warmup_cancel_file_for_connection($connection_id)
: PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.cancel-'.(int)$connection_id;
if (@file_put_contents($cancel, (string)time()."\n", LOCK_EX) === false)
{
throw new RuntimeException('WebDAV-Abbruchsignal für Verbindung #'.(int)$connection_id.' konnte nicht geschrieben werden.');
}
@chmod($cancel, 0664);
$cancel_files[] = $cancel;
}
foreach ($connections as $connection_id=>$runtime)
{
$state_dir = rtrim((string)($runtime['state_dir'] ?? ''), '/');
if ($state_dir === '')
{
throw new RuntimeException('WebDAV-State-Verzeichnis für Verbindung #'.(int)$connection_id.' fehlt.');
}
$locks[] = bratonien_tools_atomic_cache_acquire_lock(
$state_dir.'/webdav-cache-warmup.lock',
'WebDAV-Cache-Worker #'.(int)$connection_id,
10.0
);
}
return count($connections);
}
function bratonien_tools_atomic_cache_prepare_presentation_guard(array &$locks)
{
if (!function_exists('bratonien_tools_presentation_refresh_queue_dir')) return false;
$queue_dir = bratonien_tools_presentation_refresh_queue_dir();
if (!is_dir($queue_dir)) return false;
// Der Presentation-Worker erzeugt Bratonien-Wasserzeichen-Vorschauen im
// selben Derivatbaum. Deshalb muss auch er während des atomaren Leerens
// vollständig draußen bleiben.
$locks[] = bratonien_tools_atomic_cache_acquire_lock(
rtrim($queue_dir, '/').'/.worker.lock',
'Vorschau-Aktualisierung',
10.0
);
return true;
}
function bratonien_tools_clear_image_cache_atomic() function bratonien_tools_clear_image_cache_atomic()
{ {
global $conf; global $conf;
@@ -69,6 +177,14 @@ function bratonien_tools_clear_image_cache_atomic()
throw new RuntimeException('PWG_DERIVATIVE_DIR ist nicht definiert.'); throw new RuntimeException('PWG_DERIVATIVE_DIR ist nicht definiert.');
} }
$held_locks = array();
$webdav_cancel_files = array();
try
{
// 1. Lokalen Cache-Builder sauber anhalten und seinen Prozess-Lock selbst
// halten. Damit kann zwischen Prüfung und atomarem Rename kein neuer
// lokaler Builder starten.
if (bratonien_tools_main_cache_process_active() || bratonien_tools_main_cache_is_running()) if (bratonien_tools_main_cache_process_active() || bratonien_tools_main_cache_is_running())
{ {
bratonien_tools_request_main_cache_cancel(); bratonien_tools_request_main_cache_cancel();
@@ -78,6 +194,20 @@ function bratonien_tools_clear_image_cache_atomic()
} }
} }
@unlink(bratonien_tools_main_cache_cancel_file()); @unlink(bratonien_tools_main_cache_cancel_file());
$held_locks[] = bratonien_tools_atomic_cache_acquire_lock(
bratonien_tools_main_cache_lock_file(),
'Lokaler Piwigo-Cache-Worker',
10.0
);
// 2. WebDAV-Warmup regulär abbrechen und danach alle Verbindungs-Locks
// exklusiv halten. Der laufende Batch wird nicht hart beendet.
bratonien_tools_atomic_cache_prepare_webdav_guards($held_locks, $webdav_cancel_files);
// 3. Seit der Presentation-Refresh-Einführung kann auch dieser Worker
// Derivate erzeugen. Ohne seinen Lock konnte ein gerade geleerter Cache
// sofort wieder gefüllt werden und wie ein fehlgeschlagenes Löschen wirken.
bratonien_tools_atomic_cache_prepare_presentation_guard($held_locks);
$piwigo_root = realpath(PHPWG_ROOT_PATH); $piwigo_root = realpath(PHPWG_ROOT_PATH);
if ($piwigo_root === false) if ($piwigo_root === false)
@@ -153,7 +283,7 @@ function bratonien_tools_clear_image_cache_atomic()
bratonien_tools_write_main_cache_status(array( bratonien_tools_write_main_cache_status(array(
'state'=>'idle', 'state'=>'idle',
'message'=>'Bildcache wurde atomar geleert. Kein manueller Cache-Aufbau aktiv.', 'message'=>'Bildcache wurde atomar geleert. Kein Cache-Worker schreibt während des Löschvorgangs in den Derivatbaum.',
)); ));
if ($failed) if ($failed)
@@ -180,7 +310,7 @@ function bratonien_tools_clear_image_cache_atomic()
if ($active['files'] > 0) if ($active['files'] > 0)
{ {
$message .= sprintf( $message .= sprintf(
' Während bzw. unmittelbar nach dem Umschalten wurden bereits %d neue Derivatdatei(en) durch laufende Anfragen erzeugt; diese gehören zum neuen Cache und sind kein Rest des gelöschten Bestands.', ' Unmittelbar nach dem atomaren Umschalten wurden bereits %d neue Derivatdatei(en) durch normale Piwigo-Anfragen erzeugt; die eigenen Cache-Worker waren während des Löschens gesperrt und der alte Cachebaum wurde vollständig entfernt.',
$active['files'] $active['files']
); );
} }
@@ -191,3 +321,15 @@ function bratonien_tools_clear_image_cache_atomic()
return array('message'=>$message); return array('message'=>$message);
} }
finally
{
// Die Cancel-Dateien bleiben so lange bestehen, bis wir alle Worker-Locks
// wieder freigeben. Erst danach darf ein bewusst neu gestarteter Lauf den
// leeren Cache erneut aufbauen.
foreach ($webdav_cancel_files as $cancel)
{
@unlink($cancel);
}
bratonien_tools_atomic_cache_release_locks($held_locks);
}
}