Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
locking_method_memcache.inc
1 <?php
17 require_once SQ_CORE_PACKAGE_PATH.'/system/locking/locking_method/locking_method.inc';
18 
19 
33 {
34 
35 
36  public static $memcache = NULL;
37 
38 
48  public function __construct($assetid=0)
49  {
50  parent::__construct($assetid);
51 
52  }//end __construct()
53 
54 
62  public static function _initMemcache()
63  {
64  assert_true(extension_loaded('memcache'), 'Cannot use Memcache Locking Method; it requires the memcache PECL extension installed within PHP, which is not installed');
65  assert_true(file_exists(SQ_DATA_PATH.'/private/conf/memcache.inc'), 'Cannot use Memcache Locking Method; the Memcache configuration file is not set');
66 
67  $memcache_conf = require(SQ_DATA_PATH.'/private/conf/memcache.inc');
68  $hosts =& $memcache_conf['hosts'];
69  $services =& $memcache_conf['services'];
70 
71  assert_true(count($hosts) > 0, 'Cannot use Memcache Locking Method; no hosts are defined in the Memcache configuration file');
72  assert_true(array_key_exists('locking', $services) === TRUE, 'Cannot use Memcache Locking Method; no Memcache hosts are assigned to locking');
73  assert_true(count($services['locking']) > 0, 'Cannot use Memcache Locking Method; no Memcache hosts are assigned to locking');
74 
75  // If PHP has the memcache module installed, instantiate it and try to load some config.
76  self::$memcache = new Memcache;
77 
78  foreach ($services['locking'] as $host_key => $weight) {
79  assert_true(array_key_exists($host_key, $hosts) === TRUE, 'Cannot use Memcache Locking Method; host key "'.$host_key.'" assigned for use for locking but not defined as a host');
80  $host = $hosts[$host_key];
81  self::$memcache->addServer($host['host'], $host['port'], $host['persistent'], $weight, $host['timeout'], $host['retry_interval'], $host['status'], Array('Locking_Method_Memcache', 'failureCallback'));
82  }
83 
84  self::$memcache->setCompressThreshold($memcache_conf['compression_threshold'], $memcache_conf['compression_min_saving']);
85 
86  }//end _initMemcacheObj()
87 
88 
115  public static function acquireLock($lockid, $source_lockid='', $expires=0)
116  {
117  if (self::$memcache === NULL) {
118  self::_initMemcache();
119  }
120 
121  $current_lock = $GLOBALS['SQ_SYSTEM']->getLockInfo($lockid);
122  $current_time = time();
123 
124  // is this asset already locked
125  if (!empty($current_lock)) {
126  // the user is asking to acquire a lock they already had
127  // so just update the lock expiry date
128  if ($current_lock['userid'] == $GLOBALS['SQ_SYSTEM']->currentUserid()) {
129  return self::updateLock($lockid, $expires);
130  } else {
131  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($current_lock['userid']);
132  throw new Exception('Lock already held by "'.$user->name.'"');
133  }
134  }
135 
136  // if we have no source, we are the asset aquiring the locks
137  if (empty($source_lockid)) $source_lockid = $lockid;
138 
139  if (!is_null($expires)) {
140  $expires = (empty($expires)) ? ($current_time + SQ_CONF_LOCK_LENGTH) : (int) $expires;
141  //$expires = ts_iso8601($expires);
142  }
143 
144  // Change the expire time into a length in seconds, to avoid clashes
145  // between clocks on different servers. If calculated time is less than
146  // one second, make it 1 second because a time of zero is "never expire"
147  if ($expires !== NULL) {
148  $expire_seconds = $expires - $current_time;
149  if ($expire_seconds < 1) $expire_seconds = 1;
150  } else {
151  $expire_seconds = 0;
152  }
153 
154  // Store lock data in the value...this will get automatically serialised
155  // by the Memcache module
156  $lock_data = Array(
157  'lockid' => $lockid,
158  'source_lockid' => $source_lockid,
159  'userid' => $GLOBALS['SQ_SYSTEM']->currentUserId(),
160  'expires' => ts_iso8601($expires),
161  );
162 
163  self::$memcache->set('lock:'.$lockid, $lock_data, 0, $expire_seconds);
164 
165  $chain_lockids = self::$memcache->get('lockchain:'.$source_lockid);
166  if ($chain_lockids !== FALSE) {
167  // check whether we have to add this lock to the chain
168  if (array_search($lockid, $chain_lockids) === FALSE) {
169  $chain_lockids[] = $lockid;
170  }
171  } else {
172  // We need to create a new lock chain
173  $chain_lockids = Array($lockid);
174  }
175  // Set up the lock chain (or reset its expiry time)
176  self::$memcache->set('lockchain:'.$source_lockid, $chain_lockids, 0, $expire_seconds);
177 
178  return TRUE;
179 
180  }//end acquireLock()
181 
182 
203  public static function updateLock($lockid, $expires)
204  {
205  if (self::$memcache === NULL) {
206  self::_initMemcache();
207  }
208 
209  $current_time = time();
210  $current_lock = self::getLockInfo($lockid);
211 
212  // If there is no current lock (it may have expired), or if the lock
213  // is indefinite, do nothing
214  if (empty($current_lock) || empty($current_lock['expires'])) {
215  return TRUE;
216  }
217 
218  if (!is_null($expires)) {
219  $expires = (!$expires) ? (time() + SQ_CONF_LOCK_LENGTH) : (int) $expires;
220  }
221 
222  // Change the expire time into a length in seconds, to avoid clashes
223  // between clocks on different servers. If calculated time is less than
224  // one second, make it 1 second because a time of zero is "never expire"
225  if ($expires !== NULL) {
226  $expire_seconds = $expires - $current_time;
227  if ($expire_seconds < 1) $expire_seconds = 1;
228  } else {
229  $expire_seconds = 0;
230  }
231 
232  // Get the links in the chain, and update them. It'll get automatically
233  // serialised by Memcache PECL.
234  $chain_lockids = self::$memcache->get('lockchain:'.$current_lock['source_lockid']);
235  if ($chain_lockids !== FALSE) {
236  $lock_data = Array(
237  'source_lockid' => $current_lock['source_lockid'],
238  'userid' => $current_lock['userid'],
239  'expires' => ts_iso8601($expires),
240  );
241  foreach ($chain_lockids as $chain_lockid) {
242  $lock_data['lockid'] = $chain_lockid;
243  self::$memcache->set('lock:'.$chain_lockid, $lock_data, 0, $expire_seconds);
244  }
245  // Reset the lock chain's expiry time
246  self::$memcache->set('lockchain:'.$current_lock['source_lockid'], $chain_lockids, 0, $expire_seconds);
247  }
248 
249  return TRUE;
250 
251  }//end updateLock()
252 
253 
266  public static function releaseLock($lockid)
267  {
268  if (self::$memcache === NULL) {
269  self::_initMemcache();
270  }
271 
272  $current_lock = self::getLockInfo($lockid, FALSE, FALSE);
273  if (empty($current_lock)) return TRUE;
274 
275  // Get the links in the chain, and nuke them.
276  $chain_lockids = self::$memcache->get('lockchain:'.$current_lock['source_lockid']);
277  if ($chain_lockids !== FALSE) {
278  foreach ($chain_lockids as $chain_lockid) {
279  self::$memcache->delete('lock:'.$chain_lockid);
280  }
281  self::$memcache->delete('lockchain:'.$current_lock['source_lockid']);
282  }
283 
284  return TRUE;
285 
286  }//end releaseLock()
287 
288 
308  public static function getLockInfo($lockid, $full_chain=FALSE, $check_expires=TRUE, $allow_only_one=TRUE)
309  {
310  if (self::$memcache === NULL) {
311  self::_initMemcache();
312  }
313 
314  $results = Array();
315  if (is_array($lockid) === FALSE) $lockid = Array($lockid);
316 
317  foreach ($lockid as $this_lockid) {
318  $main_lock_data = self::$memcache->get('lock:'.$this_lockid);
319  if ($main_lock_data !== FALSE) {
320  $main_lock_data['expires'] = empty($main_lock_data['expires']) ? NULL : iso8601_ts($main_lock_data['expires']);
321  $results[$this_lockid] = $main_lock_data;
322  }
323 
324  if ($full_chain === TRUE) {
325  $results[$this_lockid]['chained_assets'] = Array();
326  $chain_lockids = self::$memcache->get('lockchain:'.$main_lock_data['source_lockid']);
327  if ($chain_lockids !== FALSE) {
328 
329  foreach ($chain_lockids as $chain_lockid) {
330  if ($chain_lockid !== $this_lockid) {
331  $chain_lock_data = self::$memcache->get('lock:'.$chain_lockid);
332  if ($chain_lock_data !== FALSE) {
333  $chain_lock_data['expires'] = empty($chain_lock_data['expires']) ? NULL : iso8601_ts($chain_lock_data['expires']);
334  $results[$this_lockid]['chained_assets'][$chain_lockid] = $chain_lock_data;
335  }
336 
337  }
338 
339  }
340 
341  }
342 
343  }//end if full chain
344 
345  }//end foreach main lockid
346 
347  if (!empty($results) && $allow_only_one) {
348  $results = reset($results);
349  }
350 
351  return $results;
352 
353  }//end getLockInfo()
354 
355 
363  public static function supportsGetActiveLocks()
364  {
365  return FALSE;
366 
367  }//end supportsGetActiveLocks()
368 
369 
378  public static function supportsChangeLockOwner()
379  {
380  return FALSE;
381 
382  }//end supportsChangeLockOwner()
383 
384 
392  public static function supportsDeletingExpiredLocks()
393  {
394  return FALSE;
395 
396  }//end supportsDeletingExpiredLocks()
397 
398 
408  public static function failureCallback($hostname, $port)
409  {
410  log_error(get_class()." failure communicating with $hostname:$port", E_USER_WARNING);
411 
412  }//end failureCallback()
413 
414 
415 }//end class
416 
417 ?>