Repository navigation
Expand file tree
/
Copy pathCache.php
More file actions
89 lines (75 loc) · 2.65 KB
/
Copy pathCache.php
File metadata and controls
89 lines (75 loc) · 2.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<?php declare(strict_types = 1);
namespace PHPStan\Cache;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;
#[AutowiredService]
final class Cache
{
private ?bool $arenaUsable = null;
public function __construct(
#[AutowiredParameter(ref: '@cacheStorage')]
private CacheStorage $storage,
)
{
}
/**
* @param non-empty-string $key
*/
public function load(string $key, string $variableKey): mixed
{
// Every entry is also shared across the run's parallel workers
// through the shared-memory arena (turbo extension only; the seam is
// a no-op otherwise): whichever process loads or saves an entry first
// publishes it, and the others skip reading and unserializing the
// cache file. The arena's codec covers scalars, arrays and plain
// value objects, and interns repeated strings on read like include()
// does; payloads it cannot represent just stay per-worker.
//
// What a load publishes is only a copy of the file and does not
// displace an entry already there. What save() publishes replaces it:
// a caller that finds an entry out of date by checking it after the
// load (FileTypeMapper checks the hashes of the files a name scope map
// was created from) creates the value again and saves it, and every
// process after that has to get the new value, not the one it rejected.
$arenaKey = null;
if ($this->isArenaUsable()) {
$arenaKey = 'fcs:' . $key . "\0" . $variableKey;
$cached = ArenaCache::lookup($arenaKey);
if ($cached !== null) {
return $cached;
}
}
$data = $this->storage->load($key, $variableKey);
if ($data !== null && $arenaKey !== null) {
ArenaCache::publish($arenaKey, $data);
}
return $data;
}
/**
* @param non-empty-string $key
*/
public function save(string $key, string $variableKey, mixed $data): void
{
$this->storage->save($key, $variableKey, $data);
if (!$this->isArenaUsable()) {
return;
}
ArenaCache::replace('fcs:' . $key . "\0" . $variableKey, $data);
}
/**
* Whether this process is attached to a run's arena — probed once with a
* canary record because the answer is not directly observable through
* the seam: without the extension or without an arena the publish is a
* no-op and the canary never appears. Workers attach at boot, before any
* cache traffic; a process that touches the cache before its arena
* exists just stays unshared for its lifetime.
*/
private function isArenaUsable(): bool
{
if ($this->arenaUsable === null) {
ArenaCache::publish('fcs-arena-canary', true);
$this->arenaUsable = ArenaCache::hasRecord('fcs-arena-canary');
}
return $this->arenaUsable;
}
}