Fix SignManifest 500 sur ZIPs de 14 Go via OVH mutualisé
Bug rapporté depuis le backoffice : cliquer « 🔁 Hash » sur une nouvelle version renvoyait un 500 Internal Server Error générique (« Please contact the server administrator at postmaster@asterionvr.com »). L'opérateur ne pouvait donc plus signer une release après upload SFTP du ZIP. Root cause : hash_file('sha256', $zip) sur un fichier de 14 Go via le SAN mutualisé OVH prend 5-10 min. Le max_execution_time PHP par défaut (30-60s) tue le process bien avant. Apache remonte alors 500 avec son boilerplate par défaut, sans log utile pour l'opérateur. Fix en défense en profondeur : 1. set_time_limit(0) + ignore_user_abort(true) au début de run(). Couvre TOUS les callers (admin web, cron, CLI). ignore_user_abort évite qu'un refresh de l'onglet backoffice interrompe un hash en cours (10 min = ~un café — l'opérateur peut être tenté de refresh). 2. Passage de hash_file() → hash_init + hash_update_stream en boucle 16 Mo par chunk. Deux bénéfices : • flush() entre chaque chunk = heartbeat pour le proxy Apache/OVH front, évite un timeout côté serveur web même si PHP a le droit de continuer. • set_time_limit(300) glissant à chaque chunk = si un chunk prend +5 min c'est vraiment un disque HS, pas juste un gros fichier — on n'est pas bloqué sur un unique timer géant. Mémoire : hash_update_stream() ne buffere pas, streaming pur, aucun risque d'OOM même sur 14 Go. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -83,7 +83,37 @@ final class SignManifest
|
||||
}
|
||||
|
||||
$start = microtime(true);
|
||||
$sha = hash_file('sha256', $path);
|
||||
// Streamed hash en chunks 16 Mo. Deux bénéfices vs hash_file() atomique :
|
||||
// 1. On peut envoyer un heartbeat (flush()) entre chunks pour que le
|
||||
// proxy Apache/OVH ne timeout pas la requête pendant les ~5-10 min
|
||||
// que prend un SHA-256 sur un ZIP de 14 Go via SAN mutualisé.
|
||||
// 2. On peut relever set_time_limit() à chaque chunk (fenêtre glissante)
|
||||
// au lieu de faire un unique set_time_limit(0) à la caller.
|
||||
// Mémoire : hash_update_stream() ne buffere pas — c'est du streaming pur.
|
||||
$fp = @fopen($path, 'rb');
|
||||
if ($fp === false) {
|
||||
return ['sha256' => '', 'fromCache' => false, 'durationMs' => 0];
|
||||
}
|
||||
try {
|
||||
$ctx = hash_init('sha256');
|
||||
// 16 Mo = compromis entre nombre d'appels PHP et pression CPU par read()
|
||||
$chunkBytes = 16 * 1024 * 1024;
|
||||
while (!feof($fp)) {
|
||||
hash_update_stream($ctx, $fp, $chunkBytes);
|
||||
// Fenêtre glissante : autorise ~5 min de plus avant que PHP ne
|
||||
// timeout. Sur un fichier de 14 Go / chunks 16 Mo = ~900 itérations,
|
||||
// donc si un chunk prend >5 min c'est vraiment que le disque est HS.
|
||||
@set_time_limit(300);
|
||||
// Heartbeat côté front — évite Apache RequestTimeout / OVH proxy
|
||||
// timeout sur les grosses requêtes. Silencieux si output buffering
|
||||
// est actif (pas fatal).
|
||||
@ob_flush();
|
||||
@flush();
|
||||
}
|
||||
$sha = hash_final($ctx);
|
||||
} finally {
|
||||
fclose($fp);
|
||||
}
|
||||
$duration = (int)((microtime(true) - $start) * 1000);
|
||||
|
||||
$cache[$key] = ['size' => $size, 'mtime' => $mtime, 'sha256' => $sha];
|
||||
@@ -114,6 +144,16 @@ final class SignManifest
|
||||
*/
|
||||
public function run(string $scope = 'all', bool $force = false, ?string $onlyVersion = null): array
|
||||
{
|
||||
// Sur OVH mutualisé, un SHA-256 d'un ZIP de 14 Go peut prendre 5-10 min via
|
||||
// le SAN partagé. Le max_execution_time par défaut (30-60s) tue le process
|
||||
// → Apache retourne 500 Internal Server Error avec le boilerplate
|
||||
// postmaster@… — c'est le mode d'échec principal du bouton « Hash » au
|
||||
// backoffice. On désactive la limite ici (couvre AUSSI les callers CLI et
|
||||
// cron, pas seulement l'admin web). ignore_user_abort évite qu'un refresh
|
||||
// ou une fermeture d'onglet côté opérateur interrompe un hash en cours.
|
||||
@set_time_limit(0);
|
||||
@ignore_user_abort(true);
|
||||
|
||||
if (!is_file($this->manifestPath)) {
|
||||
$this->out("Manifest introuvable : {$this->manifestPath}");
|
||||
return ['ok' => false, 'log' => $this->log];
|
||||
|
||||
Reference in New Issue
Block a user