From d5fdaac7256bb3d6b1fa7f52938435bb31d1c1d3 Mon Sep 17 00:00:00 2001 From: Stefan Helmer Date: Wed, 24 Jun 2026 15:41:33 +0200 Subject: [PATCH 1/2] Fix race condition in local_archive strategy on parallel deploys When deploying to multiple hosts in parallel, deploy:update_code runs once per host in its own worker, but all workers shared the same local archive file. The first host to finish unlinked it, so slower hosts failed with "No such file or directory"; a rebuild could also truncate an in-flight upload. Build the archive into a unique per-host file instead. Refs #4232 --- recipe/deploy/update_code.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/recipe/deploy/update_code.php b/recipe/deploy/update_code.php index 5ec662179..efff557f9 100644 --- a/recipe/deploy/update_code.php +++ b/recipe/deploy/update_code.php @@ -81,11 +81,12 @@ if ($strategy === 'local_archive') { $gitRoot = runLocally("git rev-parse --show-toplevel"); - runLocally("git -C " . quote($gitRoot) . " archive $targetWithDir -o archive.tar"); - upload("$gitRoot/archive.tar", '{{release_path}}/archive.tar'); + $archive = tempnam(sys_get_temp_dir(), 'deployer_archive_'); + runLocally("git -C " . quote($gitRoot) . " archive $targetWithDir -o " . quote($archive)); + upload($archive, '{{release_path}}/archive.tar'); run("tar -xf {{release_path}}/archive.tar -C {{release_path}}"); run("rm {{release_path}}/archive.tar"); - unlink("$gitRoot/archive.tar"); + unlink($archive); $rev = quote(runLocally("git rev-list $target -1")); } else { From 9e3d6fdd5a75fb7f3396ecaa363ba4f3f61274c5 Mon Sep 17 00:00:00 2001 From: Stefan Helmer Date: Thu, 25 Jun 2026 13:03:03 +0200 Subject: [PATCH 2/2] Add regression test for local_archive parallel deploy race Refs #4232 --- tests/spec/LocalArchiveTest.php | 46 +++++++++++++++++++++++++++++ tests/spec/recipe/local_archive.php | 14 +++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/spec/LocalArchiveTest.php create mode 100644 tests/spec/recipe/local_archive.php diff --git a/tests/spec/LocalArchiveTest.php b/tests/spec/LocalArchiveTest.php new file mode 100644 index 000000000..31ca85404 --- /dev/null +++ b/tests/spec/LocalArchiveTest.php @@ -0,0 +1,46 @@ +init(self::RECIPE); + $this->tester->run([ + 'deploy', + 'selector' => 'all', + '-f' => self::RECIPE, + ], [ + 'verbosity' => Output::VERBOSITY_VERBOSE, + ]); + + $display = $this->tester->getDisplay(); + self::assertEquals(0, $this->tester->getStatusCode(), $display); + + foreach ($this->deployer->hosts as $host) { + $deployPath = $host->get('deploy_path'); + self::assertFileExists($deployPath . '/current/README.md', $display); + } + + $gitRoot = trim((string) shell_exec('git rev-parse --show-toplevel')); + self::assertFileDoesNotExist($gitRoot . '/archive.tar'); + } +} diff --git a/tests/spec/recipe/local_archive.php b/tests/spec/recipe/local_archive.php new file mode 100644 index 000000000..3ebab2516 --- /dev/null +++ b/tests/spec/recipe/local_archive.php @@ -0,0 +1,14 @@ +