Files
FreshRSS/tests/lib/ActualizeMutexTest.php
Gerard Alvear Porras c15171efb5 Fix actualize mutex collision across instances (#9045)
## What changed

- derive the actualization mutex name from a SHA-256 hash of the canonical `DATA_PATH`
- keep `TMP_PATH` as the configurable location for the mutex file
- add coverage for deterministic per-instance names and independent locks

## Why

Multiple FreshRSS instances that use the default shared system temporary directory currently use the same actualization lock. The data path identifies the instance without exposing or depending on its configured salt.

## Validation

- `vendor/bin/phpunit --bootstrap ./tests/bootstrap.php ./tests/lib/ActualizeMutexTest.php --no-progress`
- `vendor/bin/phpcs app/actualize_script.php lib/lib_rss.php tests/lib/ActualizeMutexTest.php -s`
- `vendor/bin/phpstan analyse --memory-limit 512M --no-progress app/actualize_script.php lib/lib_rss.php tests/lib/ActualizeMutexTest.php`

Fixes #6370

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-18 22:32:38 +02:00

36 lines
1.2 KiB
PHP

<?php
declare(strict_types=1);
final class ActualizeMutexTest extends \PHPUnit\Framework\TestCase {
public function testMutexFilesAreUniquePerDataPathAndUseTheConfiguredTemporaryPath(): void {
$testPath = sys_get_temp_dir() . '/freshrss-actualize-mutex-' . bin2hex(random_bytes(8));
$tmpPath = $testPath . '/tmp';
$firstDataPath = $testPath . '/first/data';
$secondDataPath = $testPath . '/second/data';
mkdir($tmpPath, 0700, true);
mkdir($firstDataPath, 0700, true);
mkdir($secondDataPath, 0700, true);
try {
$firstMutex = actualize_mutex_file($tmpPath, $firstDataPath);
$secondMutex = actualize_mutex_file($tmpPath, $secondDataPath);
self::assertSame($firstMutex, actualize_mutex_file($tmpPath, $firstDataPath . '/.'));
self::assertNotSame($firstMutex, $secondMutex);
self::assertStringStartsWith($tmpPath . '/actualize.', $firstMutex);
self::assertStringEndsWith('.freshrss.lock', $firstMutex);
$firstHandle = fopen($firstMutex, 'x');
$secondHandle = fopen($secondMutex, 'x');
self::assertIsResource($firstHandle);
self::assertIsResource($secondHandle);
fclose($firstHandle);
fclose($secondHandle);
self::assertFalse(@fopen($firstMutex, 'x'));
} finally {
recursive_unlink($testPath);
}
}
}