Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/cache/PhutilOnDiskKeyValueCache.php
12241 views
1
<?php
2
3
/**
4
* Interface to a disk cache. Storage persists across requests.
5
*
6
* This cache is very slow compared to caches like APC. It is intended as a
7
* specialized alternative to APC when APC is not available.
8
*
9
* This is a highly specialized cache and not appropriate for use as a
10
* generalized key-value cache for arbitrary application data.
11
*
12
* Also note that reading and writing keys from the cache currently involves
13
* loading and saving the entire cache, no matter how little data you touch.
14
*
15
* @task kvimpl Key-Value Cache Implementation
16
* @task storage Cache Storage
17
*/
18
final class PhutilOnDiskKeyValueCache extends PhutilKeyValueCache {
19
20
private $cache = array();
21
private $cacheFile;
22
private $lock;
23
private $wait = 0;
24
25
26
/* -( Key-Value Cache Implementation )------------------------------------- */
27
28
29
public function isAvailable() {
30
return true;
31
}
32
33
34
/**
35
* Set duration (in seconds) to wait for the file lock.
36
*/
37
public function setWait($wait) {
38
$this->wait = $wait;
39
return $this;
40
}
41
42
public function getKeys(array $keys) {
43
$now = time();
44
45
$results = array();
46
$reloaded = false;
47
foreach ($keys as $key) {
48
49
// Try to read the value from cache. If we miss, load (or reload) the
50
// cache.
51
52
while (true) {
53
if (isset($this->cache[$key])) {
54
$val = $this->cache[$key];
55
if (empty($val['ttl']) || $val['ttl'] >= $now) {
56
$results[$key] = $val['val'];
57
break;
58
}
59
}
60
61
if ($reloaded) {
62
break;
63
}
64
65
$this->loadCache($hold_lock = false);
66
$reloaded = true;
67
}
68
}
69
70
return $results;
71
}
72
73
74
public function setKeys(array $keys, $ttl = null) {
75
if ($ttl) {
76
$ttl_epoch = time() + $ttl;
77
} else {
78
$ttl_epoch = null;
79
}
80
81
$dicts = array();
82
foreach ($keys as $key => $value) {
83
$dict = array(
84
'val' => $value,
85
);
86
if ($ttl_epoch) {
87
$dict['ttl'] = $ttl_epoch;
88
}
89
$dicts[$key] = $dict;
90
}
91
92
$this->loadCache($hold_lock = true);
93
foreach ($dicts as $key => $dict) {
94
$this->cache[$key] = $dict;
95
}
96
$this->saveCache();
97
98
return $this;
99
}
100
101
102
public function deleteKeys(array $keys) {
103
$this->loadCache($hold_lock = true);
104
foreach ($keys as $key) {
105
unset($this->cache[$key]);
106
}
107
$this->saveCache();
108
109
return $this;
110
}
111
112
113
public function destroyCache() {
114
Filesystem::remove($this->getCacheFile());
115
return $this;
116
}
117
118
119
/* -( Cache Storage )------------------------------------------------------ */
120
121
122
/**
123
* @task storage
124
*/
125
public function setCacheFile($file) {
126
$this->cacheFile = $file;
127
return $this;
128
}
129
130
131
/**
132
* @task storage
133
*/
134
private function loadCache($hold_lock) {
135
if ($this->lock) {
136
throw new Exception(
137
pht(
138
'Trying to %s with a lock!',
139
__FUNCTION__.'()'));
140
}
141
142
$lock = PhutilFileLock::newForPath($this->getCacheFile().'.lock');
143
try {
144
$lock->lock($this->wait);
145
} catch (PhutilLockException $ex) {
146
if ($hold_lock) {
147
throw $ex;
148
} else {
149
$this->cache = array();
150
return;
151
}
152
}
153
154
try {
155
$this->cache = array();
156
if (Filesystem::pathExists($this->getCacheFile())) {
157
$cache = unserialize(Filesystem::readFile($this->getCacheFile()));
158
if ($cache) {
159
$this->cache = $cache;
160
}
161
}
162
} catch (Exception $ex) {
163
$lock->unlock();
164
throw $ex;
165
}
166
167
if ($hold_lock) {
168
$this->lock = $lock;
169
} else {
170
$lock->unlock();
171
}
172
}
173
174
175
/**
176
* @task storage
177
*/
178
private function saveCache() {
179
if (!$this->lock) {
180
throw new PhutilInvalidStateException('loadCache');
181
}
182
183
// We're holding a lock so we're safe to do a write to a well-known file.
184
// Write to the same directory as the cache so the rename won't imply a
185
// copy across volumes.
186
$new = $this->getCacheFile().'.new';
187
Filesystem::writeFile($new, serialize($this->cache));
188
Filesystem::rename($new, $this->getCacheFile());
189
190
$this->lock->unlock();
191
$this->lock = null;
192
}
193
194
195
/**
196
* @task storage
197
*/
198
private function getCacheFile() {
199
if (!$this->cacheFile) {
200
throw new PhutilInvalidStateException('setCacheFile');
201
}
202
return $this->cacheFile;
203
}
204
205
}
206
207