Since the cache implementations aren't really meant to be 100% production ready,...
[shindig.git] / php / src / common / sample / CacheFile.php
blob15e7ff2570832c364c9fe764e0b77cb5c0525178
1 <?php
2 /**
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
23 * This class impliments a basic on disk caching. That will work fine on a single host
24 * but on a multi server setup this could lead to some problems due to inconsistencies
25 * between the various cached versions on the different servers. Other methods like
26 * memcached should be used instead really.
28 * When using this file based backend, its adviced to make a cron job that scans thru the
29 * cache dir, and removes all files that are older then 24 hours (or whatever your
30 * config's CACHE_TIME is set too).
32 class CacheFile extends Cache {
34 function isLocked($cacheFile) {
35 // our lock file convention is simple: /the/file/path.lock
36 return file_exists($cacheFile . '.lock');
39 private function createLock($cacheFile) {
40 $cacheDir = dirname($cacheFile);
41 if (! is_dir($cacheDir)) {
42 if (! @mkdir($cacheDir, 0755, true)) {
43 // make sure the failure isn't because of a concurency issue
44 if (! is_dir($cacheDir)) {
45 throw new CacheException("Could not create cache directory");
49 @touch($cacheFile . '.lock');
52 private function removeLock($cacheFile) {
53 // suppress all warnings, if some other process removed it that's ok too
54 @unlink($cacheFile . '.lock');
57 private function waitForLock($cacheFile) {
58 // 20 x 250 = 5 seconds
59 $tries = 20;
60 $cnt = 0;
61 do {
62 // make sure PHP picks up on file changes. This is an expensive action but really can't be avoided
63 clearstatcache();
64 // 250 ms is a long time to sleep, but it does stop the server from burning all resources on polling locks..
65 usleep(250);
66 $cnt ++;
67 } while ($cnt <= $tries && $this->isLocked($cacheFile));
68 if ($this->isLocked($cacheFile)) {
69 // 5 seconds passed, assume the owning process died off and remove it
70 $this->removeLock($cacheFile);
74 private function getCacheDir($hash) {
75 // use the first 2 characters of the hash as a directory prefix
76 // this should prevent slowdowns due to huge directory listings
77 // and thus give some basic amount of scalability
78 return Config::get('cache_root') . '/' . substr($hash, 0, 2);
81 private function getCacheFile($hash) {
82 return $this->getCacheDir($hash) . '/' . $hash;
85 public function get($key, $expiration = false) {
86 if (! $expiration) {
87 // if no expiration time was given, fall back on the global config
88 $expiration = Config::get('cache_time');
90 $cacheFile = $this->getCacheFile($key);
91 // See if this cache file is locked, if so we wait upto 5 seconds for the lock owning process to
92 // complete it's work. If the lock is not released within that time frame, it's cleaned up.
93 // This should give us a fair amount of 'Cache Stampeding' protection
94 if ($this->isLocked($cacheFile)) {
95 $this->waitForLock($cacheFile);
97 if (File::exists($cacheFile) && File::readable($cacheFile)) {
98 $now = time();
99 if (($mtime = filemtime($cacheFile)) !== false && ($now - $mtime) < $expiration) {
100 if (($data = @file_get_contents($cacheFile)) !== false) {
101 $data = unserialize($data);
102 return $data;
106 return false;
109 public function set($key, $value) {
110 $cacheDir = $this->getCacheDir($key);
111 $cacheFile = $this->getCacheFile($key);
112 if ($this->isLocked($cacheFile)) {
113 // some other process is writing to this file too, wait until it's done to prevent hickups
114 $this->waitForLock($cacheFile);
116 if (! is_dir($cacheDir)) {
117 if (! @mkdir($cacheDir, 0755, true)) {
118 throw new CacheException("Could not create cache directory");
121 // we serialize the whole request object, since we don't only want the
122 // responseContent but also the postBody used, headers, size, etc
123 $data = serialize($value);
124 $this->createLock($cacheFile);
125 if (! @file_put_contents($cacheFile, $data)) {
126 $this->removeLock($cacheFile);
127 throw new CacheException("Could not store data in cache file");
129 $this->removeLock($cacheFile);
132 public function delete($key) {
133 $file = $this->getCacheFile($key);
134 if (! @unlink($file)) {
135 throw new CacheException("Cache file could not be deleted");