Skip to content

Commit c644aaf

Browse files
committed
fix: prevent race condition in CA ID generation during tests
IAppConfig uses per-process cache, causing race conditions when CLI commands and HTTP requests execute simultaneously during Behat tests. The CLI generates a ca_id, but HTTP requests see an empty cache and generate a different ca_id, overwriting the CLI value. Solution: - Detect debug mode (automatically set during Behat tests) - Apply file lock + cache clear workaround only in debug mode - Production code remains unchanged for optimal performance This fix resolves test failures in features/sign/request.feature:28 and features/sign/request.feature:61 where setup validation was failing due to mismatched certificate paths. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 45806e1 commit c644aaf

File tree

1 file changed

+39
-1
lines changed

1 file changed

+39
-1
lines changed

lib/Handler/CertificateEngine/AEngineHandler.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,49 @@ public function readCertificate(string $certificate, string $privateKey): array
143143
public function getCaId(): string {
144144
$caId = $this->caIdentifierService->getCaId();
145145
if (empty($caId)) {
146-
$caId = $this->caIdentifierService->generateCaId($this->getName());
146+
// In debug mode (tests), use file lock and cache clear to prevent race conditions
147+
// between CLI commands and HTTP requests executing simultaneously.
148+
// In production, this is not needed as setup commands run before HTTP requests.
149+
if ($this->config->getSystemValueBool('debug', false)) {
150+
$caId = $this->getCaIdWithLock();
151+
} else {
152+
$caId = $this->caIdentifierService->generateCaId($this->getName());
153+
}
147154
}
148155
return $caId;
149156
}
150157

158+
private function getCaIdWithLock(): string {
159+
$lockFilePath = $this->tempManager->getTempBaseDir() . '/libresign_ca_id.lock';
160+
$lockFile = fopen($lockFilePath, 'c+');
161+
if ($lockFile === false) {
162+
throw new \RuntimeException('Failed to open lock file');
163+
}
164+
165+
try {
166+
// Acquire exclusive lock (blocks until available)
167+
if (!flock($lockFile, LOCK_EX)) {
168+
throw new \RuntimeException('Failed to acquire lock');
169+
}
170+
171+
// Clear IAppConfig cache and reload from database
172+
// This is critical because IAppConfig cache is per-process
173+
$this->appConfig->clearCache(true);
174+
175+
// Check again after cache refresh - another process might have created it
176+
$caId = $this->caIdentifierService->getCaId();
177+
if (empty($caId)) {
178+
$caId = $this->caIdentifierService->generateCaId($this->getName());
179+
}
180+
181+
return $caId;
182+
} finally {
183+
// Release lock and close file
184+
flock($lockFile, LOCK_UN);
185+
fclose($lockFile);
186+
}
187+
}
188+
151189
#[\Override]
152190
public function parseCertificate(string $certificate): array {
153191
return $this->parseX509($certificate);

0 commit comments

Comments
 (0)