From 4d48c14894fb111e2b2559c5f48a0fbe3ac62316 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:39:50 +0000 Subject: [PATCH 1/8] Initial plan From 0cf8d21db70c38b7027fba7c85b9bf255da129b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:46:27 +0000 Subject: [PATCH 2/8] Fix race condition in FileStorage causing filemtime() warnings Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/Storage/FileStorage.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Storage/FileStorage.php b/src/Storage/FileStorage.php index 113850d07..7ff5ce406 100644 --- a/src/Storage/FileStorage.php +++ b/src/Storage/FileStorage.php @@ -6,9 +6,12 @@ use Yiisoft\Files\FileHelper; +use function array_filter; use function array_slice; +use function array_values; use function count; use function dirname; +use function file_exists; use function filemtime; use function glob; use function json_decode; @@ -113,9 +116,15 @@ private function findFilesOrderedByModifiedTime(string $pattern): array usort( $files, - static fn (string $a, string $b) => filemtime($b) <=> filemtime($a) + static function (string $a, string $b): int { + $mtimeA = file_exists($a) ? filemtime($a) : 0; + $mtimeB = file_exists($b) ? filemtime($b) : 0; + return $mtimeB <=> $mtimeA; + } ); - return $files; + + // Filter out files that no longer exist (due to concurrent deletion) + return array_values(array_filter($files, static fn(string $file): bool => file_exists($file))); } private function encode(mixed $value): string From aeb7fd15ca004123e74c02ae42bb92b6020c30c2 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 2 Jan 2026 12:46:35 +0000 Subject: [PATCH 3/8] Apply fixes from StyleCI --- src/Storage/FileStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storage/FileStorage.php b/src/Storage/FileStorage.php index 7ff5ce406..8724ea4b5 100644 --- a/src/Storage/FileStorage.php +++ b/src/Storage/FileStorage.php @@ -122,7 +122,7 @@ static function (string $a, string $b): int { return $mtimeB <=> $mtimeA; } ); - + // Filter out files that no longer exist (due to concurrent deletion) return array_values(array_filter($files, static fn(string $file): bool => file_exists($file))); } From 4ad2e928a92bfc838d8ffe366b251c13862dd108 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:48:59 +0000 Subject: [PATCH 4/8] Add test case for concurrent file deletion race condition Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- composer.json | 2 +- tests/Unit/Storage/FileStorageTest.php | 28 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3c63d6850..318fb03e3 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "jetbrains/phpstorm-attributes": "^1.2", "maglnet/composer-require-checker": "^4.2", "nyholm/psr7": "^1.3", - "phpunit/phpunit": "^10.5", + "phpunit/phpunit": "*", "rector/rector": "^1.2", "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", diff --git a/tests/Unit/Storage/FileStorageTest.php b/tests/Unit/Storage/FileStorageTest.php index a139667fd..05ca11bef 100644 --- a/tests/Unit/Storage/FileStorageTest.php +++ b/tests/Unit/Storage/FileStorageTest.php @@ -51,6 +51,34 @@ public function testClear(): void $this->assertDirectoryDoesNotExist($this->path); } + public function testConcurrentDeletion(): void + { + $storage = $this->getStorage(); + $storage->setHistorySize(10); + + // Write some data + for ($i = 1; $i <= 5; $i++) { + $storage->write('test' . $i, [['data' . $i]], [], ['id' => 'test' . $i]); + usleep(100000); // 100ms delay to ensure different modification times + } + + // Find all summary files + $pattern = $this->path . '/**/**/summary.json'; + $summaryFiles = glob($pattern, GLOB_NOSORT); + $this->assertNotEmpty($summaryFiles, 'Should have summary files'); + + // Delete one summary file to simulate concurrent deletion during gc + if (!empty($summaryFiles)) { + unlink($summaryFiles[0]); + } + + // This should not produce any warnings even though a file was deleted + $summary = $storage->read(StorageInterface::TYPE_SUMMARY); + + // We should get 4 results (5 written - 1 deleted) + $this->assertCount(4, $summary); + } + public function getStorage(): FileStorage { return new FileStorage($this->path); From 2715ffab18efc1f897deb1bd5f020c54ee821518 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 2 Jan 2026 12:49:06 +0000 Subject: [PATCH 5/8] Apply fixes from StyleCI --- tests/Unit/Storage/FileStorageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Storage/FileStorageTest.php b/tests/Unit/Storage/FileStorageTest.php index 05ca11bef..8b906f543 100644 --- a/tests/Unit/Storage/FileStorageTest.php +++ b/tests/Unit/Storage/FileStorageTest.php @@ -74,7 +74,7 @@ public function testConcurrentDeletion(): void // This should not produce any warnings even though a file was deleted $summary = $storage->read(StorageInterface::TYPE_SUMMARY); - + // We should get 4 results (5 written - 1 deleted) $this->assertCount(4, $summary); } From 2cccf5e5f5322caf29295ce0dc4d6634d8e82f10 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:50:01 +0000 Subject: [PATCH 6/8] Revert unintended composer.json changes --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 318fb03e3..3c63d6850 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "jetbrains/phpstorm-attributes": "^1.2", "maglnet/composer-require-checker": "^4.2", "nyholm/psr7": "^1.3", - "phpunit/phpunit": "*", + "phpunit/phpunit": "^10.5", "rector/rector": "^1.2", "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", From b7195a1c8ce03bad33d8325c331dc407285dc32e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:51:50 +0000 Subject: [PATCH 7/8] Address code review feedback: remove unnecessary array_values and reduce test delay Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/Storage/FileStorage.php | 3 +-- tests/Unit/Storage/FileStorageTest.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Storage/FileStorage.php b/src/Storage/FileStorage.php index 8724ea4b5..40b792c70 100644 --- a/src/Storage/FileStorage.php +++ b/src/Storage/FileStorage.php @@ -8,7 +8,6 @@ use function array_filter; use function array_slice; -use function array_values; use function count; use function dirname; use function file_exists; @@ -124,7 +123,7 @@ static function (string $a, string $b): int { ); // Filter out files that no longer exist (due to concurrent deletion) - return array_values(array_filter($files, static fn(string $file): bool => file_exists($file))); + return array_filter($files, static fn(string $file): bool => file_exists($file)); } private function encode(mixed $value): string diff --git a/tests/Unit/Storage/FileStorageTest.php b/tests/Unit/Storage/FileStorageTest.php index 8b906f543..38034712c 100644 --- a/tests/Unit/Storage/FileStorageTest.php +++ b/tests/Unit/Storage/FileStorageTest.php @@ -59,7 +59,7 @@ public function testConcurrentDeletion(): void // Write some data for ($i = 1; $i <= 5; $i++) { $storage->write('test' . $i, [['data' . $i]], [], ['id' => 'test' . $i]); - usleep(100000); // 100ms delay to ensure different modification times + usleep(1000); // 1ms delay to ensure different modification times } // Find all summary files From 7b4e6c7dd2fb324fc21af202f941a08cc690fbdc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:51:39 +0000 Subject: [PATCH 8/8] Use @ operator instead of file_exists checks to reduce I/O operations Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/Storage/FileStorage.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Storage/FileStorage.php b/src/Storage/FileStorage.php index 40b792c70..83ea0c519 100644 --- a/src/Storage/FileStorage.php +++ b/src/Storage/FileStorage.php @@ -116,14 +116,16 @@ private function findFilesOrderedByModifiedTime(string $pattern): array usort( $files, static function (string $a, string $b): int { - $mtimeA = file_exists($a) ? filemtime($a) : 0; - $mtimeB = file_exists($b) ? filemtime($b) : 0; - return $mtimeB <=> $mtimeA; + // Use @ to suppress warnings when files are deleted concurrently by another process + $mtimeA = @filemtime($a); + $mtimeB = @filemtime($b); + // filemtime() returns false if file doesn't exist, treat as 0 for sorting + return ($mtimeB ?: 0) <=> ($mtimeA ?: 0); } ); // Filter out files that no longer exist (due to concurrent deletion) - return array_filter($files, static fn(string $file): bool => file_exists($file)); + return array_filter($files, static fn(string $file): bool => @file_exists($file)); } private function encode(mixed $value): string