Index: memcache-lock.inc
===================================================================
RCS file: memcache-lock.inc
diff -N memcache-lock.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ memcache-lock.inc	25 Jun 2010 05:32:11 -0000
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * A memcache based implementation of a locking mechanism.
+ * See includes/lock.inc for documenation
+ */
+
+
+/**
+ * Initialize the locking system.
+ */
+function lock_initialize() {
+  global $locks;
+
+  $locks = array();
+}
+
+/**
+ * Acquire (or renew) a lock, but do not block if it fails.
+ *
+ * @param $name
+ *   The name of the lock.
+ * @param $timeout
+ *   A number of seconds (int) before the lock expires (minimum of 1).
+ * @return
+ *   TRUE if the lock was acquired, FALSE if it failed.
+ */
+function lock_acquire($name, $timeout = 30) {
+  global $locks;
+
+  // Ensure that the timeout is at least 1 sec. This is a limitation
+  // imposed by memcached.
+  $timeout = max($timeout, 1);
+  $now = microtime(TRUE);
+  $expire = $now + $timeout;
+
+  // prepare lock object
+  $lock = new stdClass;
+  $lock->lock_expire = $expire;
+
+  $key = dmemcache_key($name, 'semaphore');
+  $result = dmemcache_get($key);
+  if ($result && $locks[$name] > $now) {
+    if (dmemcache_set($key, $lock, FALSE, $timeout)) {
+      $locks[$name] = $expire;
+    }
+  }
+  else if (dmemcache_add($key, $lock, FALSE, $timeout)) {
+    $locks[$name] = $expire;
+  }
+  else {
+    // could not acquire lock
+    unset($locks[$name]);
+  }
+
+  return isset($locks[$name]);
+}
+
+/**
+ * Check if lock acquired by a different process may be available.
+ *
+ * If an existing lock has expired, it is removed.
+ *
+ * @param $name
+ *   The name of the lock.
+ * @return
+ *   TRUE if there is no lock or it was removed, FALSE otherwise.
+ */
+function lock_may_be_available($name) {
+  return !dmemcache_get($name, 'semaphore');
+}
+
+/**
+ * Wait for a lock to be available.
+ *
+ * This function may be called in a request that fails to acquire a desired
+ * lock. This will block further execution until the lock is available or the
+ * specified delay in seconds is reached. This should not be used with locks
+ * that are acquired very frequently, since the lock is likely to be acquired
+ * again by a different request during the sleep().
+ *
+ * @param $name
+ *   The name of the lock.
+ * @param $delay
+ *   The maximum number of seconds to wait, as an integer.
+ * @return
+ *   TRUE if the lock holds, FALSE if it is available.
+ */
+function lock_wait($name, $delay = 30) {
+  $delay = (int) $delay;
+  while ($delay--) {
+    // This function should only be called by a request that failed to get a
+    // lock, so we sleep first to give the parallel request a chance to finish
+    // and release the lock.
+    sleep(1);
+    if (!dmemcache_get($name, 'semaphore')) {
+      // No longer need to wait.
+      return FALSE;
+    }
+  }
+  // The caller must still wait longer to get the lock.
+  return TRUE;
+}
+
+/**
+ * Release a lock previously acquired by lock_acquire().
+ *
+ * This will release the named lock if it is still held by the current request.
+ *
+ * @param $name
+ *   The name of the lock.
+ */
+function lock_release($name) {
+  global $locks;
+
+  dmemcache_delete($name, 'semaphore');
+  // we unset unconditionally since caller assumes lock is released anyway
+  unset($locks[$name]);
+}
+
