1<?php 2/* 3 * Copyright 2008 Google Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18/** 19 * A persistent storage class based on the memcache, which is not 20 * really very persistent, as soon as you restart your memcache daemon 21 * the storage will be wiped, however for debugging and/or speed 22 * it can be useful, kinda, and cache is a lot cheaper then storage. 23 * 24 * @author Chris Chabot <chabotc@google.com> 25 */ 26class Google_MemcacheCache extends Google_Cache { 27 private $connection = false; 28 29 public function __construct() { 30 global $apiConfig; 31 if (! function_exists('memcache_connect')) { 32 throw new Google_CacheException("Memcache functions not available"); 33 } 34 $this->host = $apiConfig['ioMemCacheCache_host']; 35 $this->port = $apiConfig['ioMemCacheCache_port']; 36 if (empty($this->host) || empty($this->port)) { 37 throw new Google_CacheException("You need to supply a valid memcache host and port"); 38 } 39 } 40 41 private function isLocked($key) { 42 $this->check(); 43 if ((@memcache_get($this->connection, $key . '.lock')) === false) { 44 return false; 45 } 46 return true; 47 } 48 49 private function createLock($key) { 50 $this->check(); 51 // the interesting thing is that this could fail if the lock was created in the meantime.. 52 // but we'll ignore that out of convenience 53 @memcache_add($this->connection, $key . '.lock', '', 0, 5); 54 } 55 56 private function removeLock($key) { 57 $this->check(); 58 // suppress all warnings, if some other process removed it that's ok too 59 @memcache_delete($this->connection, $key . '.lock'); 60 } 61 62 private function waitForLock($key) { 63 $this->check(); 64 // 20 x 250 = 5 seconds 65 $tries = 20; 66 $cnt = 0; 67 do { 68 // 250 ms is a long time to sleep, but it does stop the server from burning all resources on polling locks.. 69 usleep(250); 70 $cnt ++; 71 } while ($cnt <= $tries && $this->isLocked($key)); 72 if ($this->isLocked($key)) { 73 // 5 seconds passed, assume the owning process died off and remove it 74 $this->removeLock($key); 75 } 76 } 77 78 // I prefer lazy initialization since the cache isn't used every request 79 // so this potentially saves a lot of overhead 80 private function connect() { 81 if (! $this->connection = @memcache_pconnect($this->host, $this->port)) { 82 throw new Google_CacheException("Couldn't connect to memcache server"); 83 } 84 } 85 86 private function check() { 87 if (! $this->connection) { 88 $this->connect(); 89 } 90 } 91 92 /** 93 * @inheritDoc 94 */ 95 public function get($key, $expiration = false) { 96 $this->check(); 97 if (($ret = @memcache_get($this->connection, $key)) === false) { 98 return false; 99 } 100 if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) { 101 $this->delete($key); 102 return false; 103 } 104 return $ret['data']; 105 } 106 107 /** 108 * @inheritDoc 109 * @param string $key 110 * @param string $value 111 * @throws Google_CacheException 112 */ 113 public function set($key, $value) { 114 $this->check(); 115 // we store it with the cache_time default expiration so objects will at least get cleaned eventually. 116 if (@memcache_set($this->connection, $key, array('time' => time(), 117 'data' => $value), false) == false) { 118 throw new Google_CacheException("Couldn't store data in cache"); 119 } 120 } 121 122 /** 123 * @inheritDoc 124 * @param String $key 125 */ 126 public function delete($key) { 127 $this->check(); 128 @memcache_delete($this->connection, $key); 129 } 130} 131