Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/cache/PhabricatorCaches.php
12242 views
1
<?php
2
3
/**
4
*
5
* @task request Request Cache
6
* @task immutable Immutable Cache
7
* @task setup Setup Cache
8
* @task compress Compression
9
*/
10
final class PhabricatorCaches extends Phobject {
11
12
private static $requestCache;
13
14
public static function getNamespace() {
15
return PhabricatorEnv::getEnvConfig('phabricator.cache-namespace');
16
}
17
18
private static function newStackFromCaches(array $caches) {
19
$caches = self::addNamespaceToCaches($caches);
20
$caches = self::addProfilerToCaches($caches);
21
return id(new PhutilKeyValueCacheStack())
22
->setCaches($caches);
23
}
24
25
/* -( Request Cache )------------------------------------------------------ */
26
27
28
/**
29
* Get a request cache stack.
30
*
31
* This cache stack is destroyed after each logical request. In particular,
32
* it is destroyed periodically by the daemons, while `static` caches are
33
* not.
34
*
35
* @return PhutilKeyValueCacheStack Request cache stack.
36
*/
37
public static function getRequestCache() {
38
if (!self::$requestCache) {
39
self::$requestCache = new PhutilInRequestKeyValueCache();
40
}
41
return self::$requestCache;
42
}
43
44
45
/**
46
* Destroy the request cache.
47
*
48
* This is called at the beginning of each logical request.
49
*
50
* @return void
51
*/
52
public static function destroyRequestCache() {
53
self::$requestCache = null;
54
55
// See T12997. Force the GC to run when the request cache is destroyed to
56
// clean up any cycles which may still be hanging around.
57
if (function_exists('gc_collect_cycles')) {
58
gc_collect_cycles();
59
}
60
}
61
62
63
/* -( Immutable Cache )---------------------------------------------------- */
64
65
66
/**
67
* Gets an immutable cache stack.
68
*
69
* This stack trades mutability away for improved performance. Normally, it is
70
* APC + DB.
71
*
72
* In the general case with multiple web frontends, this stack can not be
73
* cleared, so it is only appropriate for use if the value of a given key is
74
* permanent and immutable.
75
*
76
* @return PhutilKeyValueCacheStack Best immutable stack available.
77
* @task immutable
78
*/
79
public static function getImmutableCache() {
80
static $cache;
81
if (!$cache) {
82
$caches = self::buildImmutableCaches();
83
$cache = self::newStackFromCaches($caches);
84
}
85
return $cache;
86
}
87
88
89
/**
90
* Build the immutable cache stack.
91
*
92
* @return list<PhutilKeyValueCache> List of caches.
93
* @task immutable
94
*/
95
private static function buildImmutableCaches() {
96
$caches = array();
97
98
$apc = new PhutilAPCKeyValueCache();
99
if ($apc->isAvailable()) {
100
$caches[] = $apc;
101
}
102
103
$caches[] = new PhabricatorKeyValueDatabaseCache();
104
105
return $caches;
106
}
107
108
public static function getMutableCache() {
109
static $cache;
110
if (!$cache) {
111
$caches = self::buildMutableCaches();
112
$cache = self::newStackFromCaches($caches);
113
}
114
return $cache;
115
}
116
117
private static function buildMutableCaches() {
118
$caches = array();
119
120
$caches[] = new PhabricatorKeyValueDatabaseCache();
121
122
return $caches;
123
}
124
125
public static function getMutableStructureCache() {
126
static $cache;
127
if (!$cache) {
128
$caches = self::buildMutableStructureCaches();
129
$cache = self::newStackFromCaches($caches);
130
}
131
return $cache;
132
}
133
134
private static function buildMutableStructureCaches() {
135
$caches = array();
136
137
$cache = new PhabricatorKeyValueDatabaseCache();
138
$cache = new PhabricatorKeyValueSerializingCacheProxy($cache);
139
$caches[] = $cache;
140
141
return $caches;
142
}
143
144
/* -( Runtime Cache )------------------------------------------------------ */
145
146
147
/**
148
* Get a runtime cache stack.
149
*
150
* This stack is just APC. It's fast, it's effectively immutable, and it
151
* gets thrown away when the webserver restarts.
152
*
153
* This cache is suitable for deriving runtime caches, like a map of Conduit
154
* method names to provider classes.
155
*
156
* @return PhutilKeyValueCacheStack Best runtime stack available.
157
*/
158
public static function getRuntimeCache() {
159
static $cache;
160
if (!$cache) {
161
$caches = self::buildRuntimeCaches();
162
$cache = self::newStackFromCaches($caches);
163
}
164
return $cache;
165
}
166
167
168
private static function buildRuntimeCaches() {
169
$caches = array();
170
171
$apc = new PhutilAPCKeyValueCache();
172
if ($apc->isAvailable()) {
173
$caches[] = $apc;
174
}
175
176
return $caches;
177
}
178
179
180
/* -( Repository Graph Cache )--------------------------------------------- */
181
182
183
public static function getRepositoryGraphL1Cache() {
184
static $cache;
185
if (!$cache) {
186
$caches = self::buildRepositoryGraphL1Caches();
187
$cache = self::newStackFromCaches($caches);
188
}
189
return $cache;
190
}
191
192
private static function buildRepositoryGraphL1Caches() {
193
$caches = array();
194
195
$request = new PhutilInRequestKeyValueCache();
196
$request->setLimit(32);
197
$caches[] = $request;
198
199
$apc = new PhutilAPCKeyValueCache();
200
if ($apc->isAvailable()) {
201
$caches[] = $apc;
202
}
203
204
return $caches;
205
}
206
207
public static function getRepositoryGraphL2Cache() {
208
static $cache;
209
if (!$cache) {
210
$caches = self::buildRepositoryGraphL2Caches();
211
$cache = self::newStackFromCaches($caches);
212
}
213
return $cache;
214
}
215
216
private static function buildRepositoryGraphL2Caches() {
217
$caches = array();
218
$caches[] = new PhabricatorKeyValueDatabaseCache();
219
return $caches;
220
}
221
222
223
/* -( Server State Cache )------------------------------------------------- */
224
225
226
/**
227
* Highly specialized cache for storing server process state.
228
*
229
* We use this cache to track initial steps in the setup phase, before
230
* configuration is loaded.
231
*
232
* This cache does NOT use the cache namespace (it must be accessed before
233
* we build configuration), and is global across all instances on the host.
234
*
235
* @return PhutilKeyValueCacheStack Best available server state cache stack.
236
* @task setup
237
*/
238
public static function getServerStateCache() {
239
static $cache;
240
if (!$cache) {
241
$caches = self::buildSetupCaches('phabricator-server');
242
243
// NOTE: We are NOT adding a cache namespace here! This cache is shared
244
// across all instances on the host.
245
246
$caches = self::addProfilerToCaches($caches);
247
$cache = id(new PhutilKeyValueCacheStack())
248
->setCaches($caches);
249
250
}
251
return $cache;
252
}
253
254
255
256
/* -( Setup Cache )-------------------------------------------------------- */
257
258
259
/**
260
* Highly specialized cache for performing setup checks. We use this cache
261
* to determine if we need to run expensive setup checks when the page
262
* loads. Without it, we would need to run these checks every time.
263
*
264
* Normally, this cache is just APC. In the absence of APC, this cache
265
* degrades into a slow, quirky on-disk cache.
266
*
267
* NOTE: Do not use this cache for anything else! It is not a general-purpose
268
* cache!
269
*
270
* @return PhutilKeyValueCacheStack Most qualified available cache stack.
271
* @task setup
272
*/
273
public static function getSetupCache() {
274
static $cache;
275
if (!$cache) {
276
$caches = self::buildSetupCaches('phabricator-setup');
277
$cache = self::newStackFromCaches($caches);
278
}
279
return $cache;
280
}
281
282
283
/**
284
* @task setup
285
*/
286
private static function buildSetupCaches($cache_name) {
287
// If this is the CLI, just build a setup cache.
288
if (php_sapi_name() == 'cli') {
289
return array();
290
}
291
292
// In most cases, we should have APC. This is an ideal cache for our
293
// purposes -- it's fast and empties on server restart.
294
$apc = new PhutilAPCKeyValueCache();
295
if ($apc->isAvailable()) {
296
return array($apc);
297
}
298
299
// If we don't have APC, build a poor approximation on disk. This is still
300
// much better than nothing; some setup steps are quite slow.
301
$disk_path = self::getSetupCacheDiskCachePath($cache_name);
302
if ($disk_path) {
303
$disk = new PhutilOnDiskKeyValueCache();
304
$disk->setCacheFile($disk_path);
305
$disk->setWait(0.1);
306
if ($disk->isAvailable()) {
307
return array($disk);
308
}
309
}
310
311
return array();
312
}
313
314
315
/**
316
* @task setup
317
*/
318
private static function getSetupCacheDiskCachePath($name) {
319
// The difficulty here is in choosing a path which will change on server
320
// restart (we MUST have this property), but as rarely as possible
321
// otherwise (we desire this property to give the cache the best hit rate
322
// we can).
323
324
// Unfortunately, we don't have a very good strategy for minimizing the
325
// churn rate of the cache. We previously tried to use the parent process
326
// PID in some cases, but this was not reliable. See T9599 for one case of
327
// this.
328
329
$pid_basis = getmypid();
330
331
// If possible, we also want to know when the process launched, so we can
332
// drop the cache if a process restarts but gets the same PID an earlier
333
// process had. "/proc" is not available everywhere (e.g., not on OSX), but
334
// check if we have it.
335
$epoch_basis = null;
336
$stat = @stat("/proc/{$pid_basis}");
337
if ($stat !== false) {
338
$epoch_basis = $stat['ctime'];
339
}
340
341
$tmp_dir = sys_get_temp_dir();
342
343
$tmp_path = $tmp_dir.DIRECTORY_SEPARATOR.$name;
344
if (!file_exists($tmp_path)) {
345
@mkdir($tmp_path);
346
}
347
348
$is_ok = self::testTemporaryDirectory($tmp_path);
349
if (!$is_ok) {
350
$tmp_path = $tmp_dir;
351
$is_ok = self::testTemporaryDirectory($tmp_path);
352
if (!$is_ok) {
353
// We can't find anywhere to write the cache, so just bail.
354
return null;
355
}
356
}
357
358
$tmp_name = 'setup-'.$pid_basis;
359
if ($epoch_basis) {
360
$tmp_name .= '.'.$epoch_basis;
361
}
362
$tmp_name .= '.cache';
363
364
return $tmp_path.DIRECTORY_SEPARATOR.$tmp_name;
365
}
366
367
368
/**
369
* @task setup
370
*/
371
private static function testTemporaryDirectory($dir) {
372
if (!@file_exists($dir)) {
373
return false;
374
}
375
if (!@is_dir($dir)) {
376
return false;
377
}
378
if (!@is_writable($dir)) {
379
return false;
380
}
381
382
return true;
383
}
384
385
private static function addProfilerToCaches(array $caches) {
386
foreach ($caches as $key => $cache) {
387
$pcache = new PhutilKeyValueCacheProfiler($cache);
388
$pcache->setProfiler(PhutilServiceProfiler::getInstance());
389
$caches[$key] = $pcache;
390
}
391
return $caches;
392
}
393
394
private static function addNamespaceToCaches(array $caches) {
395
$namespace = self::getNamespace();
396
if (!$namespace) {
397
return $caches;
398
}
399
400
foreach ($caches as $key => $cache) {
401
$ncache = new PhutilKeyValueCacheNamespace($cache);
402
$ncache->setNamespace($namespace);
403
$caches[$key] = $ncache;
404
}
405
406
return $caches;
407
}
408
409
410
/**
411
* Deflate a value, if deflation is available and has an impact.
412
*
413
* If the value is larger than 1KB, we have `gzdeflate()`, we successfully
414
* can deflate it, and it benefits from deflation, we deflate it. Otherwise
415
* we leave it as-is.
416
*
417
* Data can later be inflated with @{method:inflateData}.
418
*
419
* @param string String to attempt to deflate.
420
* @return string|null Deflated string, or null if it was not deflated.
421
* @task compress
422
*/
423
public static function maybeDeflateData($value) {
424
$len = strlen($value);
425
if ($len <= 1024) {
426
return null;
427
}
428
429
if (!function_exists('gzdeflate')) {
430
return null;
431
}
432
433
$deflated = gzdeflate($value);
434
if ($deflated === false) {
435
return null;
436
}
437
438
$deflated_len = strlen($deflated);
439
if ($deflated_len >= ($len / 2)) {
440
return null;
441
}
442
443
return $deflated;
444
}
445
446
447
/**
448
* Inflate data previously deflated by @{method:maybeDeflateData}.
449
*
450
* @param string Deflated data, from @{method:maybeDeflateData}.
451
* @return string Original, uncompressed data.
452
* @task compress
453
*/
454
public static function inflateData($value) {
455
if (!function_exists('gzinflate')) {
456
throw new Exception(
457
pht(
458
'%s is not available; unable to read deflated data!',
459
'gzinflate()'));
460
}
461
462
$value = gzinflate($value);
463
if ($value === false) {
464
throw new Exception(pht('Failed to inflate data!'));
465
}
466
467
return $value;
468
}
469
470
471
}
472
473