Skip to content

Commit 8e318b6

Browse files
committed
Refine statCache handling in StreamWrapper to only cache positive url_stat results and clear cache on metadata changes
1 parent e22759a commit 8e318b6

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

src/Internal/StreamWrapper.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ final class StreamWrapper implements StreamWrapperInterface
4242
private static string $cacheDir = '';
4343

4444
/**
45-
* In-memory cache for url_stat to avoid 100,000+ unregister/register cycles on file_exists/is_file.
45+
* In-memory cache for positive url_stat results.
4646
*
47-
* @var array<string, array<int|string, int>|false>
47+
* @var array<string, array<int|string, int>>
4848
*/
4949
private static array $statCache = [];
5050

@@ -338,13 +338,14 @@ public function stream_close(): void
338338

339339
/**
340340
* High-speed stat resolution with $O(1)$ memoization cache.
341+
* Never caches false (negative lookups) so newly created directories and files are immediately discovered.
341342
*
342343
* @return array<int|string, int>|false
343344
*/
344345
public function url_stat(string $path, int $flags): array|false
345346
{
346347
$normalized = str_replace('\\', '/', $path);
347-
if (\array_key_exists($normalized, self::$statCache)) {
348+
if (isset(self::$statCache[$normalized])) {
348349
return self::$statCache[$normalized];
349350
}
350351

@@ -353,11 +354,19 @@ public function url_stat(string $path, int $flags): array|false
353354
$result = self::silent(fn () => stat($path));
354355
self::register();
355356

356-
return self::$statCache[$normalized] = $result;
357+
// Only cache positive results (existing files/dirs)
358+
if ($result !== false) {
359+
self::$statCache[$normalized] = $result;
360+
}
361+
362+
return $result;
357363
}
358364

359365
public function stream_metadata(string $path, int $option, mixed $value): bool
360366
{
367+
$normalized = str_replace('\\', '/', $path);
368+
unset(self::$statCache[$normalized]);
369+
361370
self::unregister();
362371
$result = false;
363372
if ($option === STREAM_META_TOUCH) {
@@ -419,6 +428,9 @@ public function dir_closedir(): bool
419428

420429
public function mkdir(string $path, int $mode, int $options): bool
421430
{
431+
$normalized = str_replace('\\', '/', $path);
432+
unset(self::$statCache[$normalized]);
433+
422434
self::unregister();
423435
$result = (bool) self::silent(fn () => mkdir($path, $mode, (bool) ($options & STREAM_MKDIR_RECURSIVE)));
424436
self::register();
@@ -428,6 +440,9 @@ public function mkdir(string $path, int $mode, int $options): bool
428440

429441
public function rmdir(string $path, int $options): bool
430442
{
443+
$normalized = str_replace('\\', '/', $path);
444+
unset(self::$statCache[$normalized]);
445+
431446
self::unregister();
432447
$result = (bool) self::silent(fn () => rmdir($path));
433448
self::register();

0 commit comments

Comments
 (0)