ARedisMutex.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <?php
  2. /**
  3. * Represents a redis mutex.
  4. *
  5. * Simple usage:
  6. * <pre>
  7. * $mutex = new ARedisMutex("someOperation");
  8. * $mutex->block(); // blocks execution until the resource becomes available
  9. * // do something
  10. * $mutex->unlock(); // release the lock
  11. * </pre>
  12. *
  13. * With events:
  14. * <pre>
  15. * $mutex = new ARedisMutex("someOperation");
  16. * $mutex->afterLock = function(CEvent $event) {
  17. * echo "Locked!\n";
  18. * // do some processing here
  19. * $event->sender->unlock(); // finally, unlock the mutex
  20. * };
  21. * $mutex->afterUnlock = function(CEvent $event) {
  22. * echo "Unlocked!";
  23. * }
  24. * $mutex->block(); // triggers appropriate events when the resource becomes available
  25. * </pre>
  26. * @author Charles Pick
  27. * @package packages.redis
  28. */
  29. class ARedisMutex extends ARedisEntity {
  30. /**
  31. * The number of seconds before this mutex will automatically expire
  32. * @var integer
  33. */
  34. public $expiresAfter = 10;
  35. /**
  36. * The number of micro seconds to sleep for between poll requests.
  37. * Defaults to half a second.
  38. * @var integer
  39. */
  40. public $pollDelay = 500000;
  41. /**
  42. * The time the mutex expires at
  43. * @var integer
  44. */
  45. protected $_expiresAt;
  46. /**
  47. * Attempts to lock the mutex, returns true if successful or false if the mutex is locked by another process.
  48. * @return boolean whether the lock was successful or not
  49. */
  50. public function lock() {
  51. if (!$this->beforeLock()) {
  52. return false;
  53. }
  54. $redis = $this->getConnection()->getClient();
  55. if (!$redis->setnx($this->name, $this->getExpiresAt(true))) {
  56. // see if this mutex has expired
  57. $value = $redis->get($this->name);
  58. if ($value > microtime(true)) {
  59. return false;
  60. }
  61. }
  62. $this->afterLock();
  63. return true;
  64. }
  65. /**
  66. * Attempts to unlock the mutex, returns true if successful, or false if the mutex is in use by another process
  67. * @return boolean whether the unlock was successful or not
  68. */
  69. public function unlock() {
  70. if (!$this->beforeUnlock()) {
  71. return false;
  72. }
  73. $redis = $this->getConnection()->getClient();
  74. $value = $redis->get($this->name);
  75. $decimalPlaces = max(strlen(substr($value,strpos($value,"."))),strlen(substr($this->_expiresAt,strpos($this->_expiresAt,".")))) - 1;
  76. if (bccomp($value,$this->_expiresAt,$decimalPlaces) == -1 && bccomp($value,microtime(true),$decimalPlaces) == 1) {
  77. return false;
  78. }
  79. $redis->delete($this->name);
  80. $this->afterUnlock();
  81. return true;
  82. }
  83. /**
  84. * Blocks program execution until the lock becomes available
  85. * @return ARedisMutex $this after the lock is opened
  86. */
  87. public function block() {
  88. while($this->lock() === false) {
  89. usleep($this->pollDelay);
  90. }
  91. return $this;
  92. }
  93. /**
  94. * Invoked before the mutex is locked.
  95. * The default implementation raises the onBeforeLock event
  96. * @return boolean true if the lock should continue
  97. */
  98. public function beforeLock() {
  99. $event = new CModelEvent();
  100. $event->sender = $this;
  101. $this->onBeforeLock($event);
  102. return $event->isValid;
  103. }
  104. /**
  105. * Invoked after the mutex is locked.
  106. * The default implementation raises the onAfterLock event
  107. */
  108. public function afterLock() {
  109. $event = new CEvent;
  110. $event->sender = $this;
  111. $this->onAfterLock($event);
  112. }
  113. /**
  114. * Invoked before the mutex is unlocked.
  115. * The default implementation raises the onBeforeUnlock event
  116. * @return boolean true if the unlock should continue
  117. */
  118. public function beforeUnlock() {
  119. $event = new CModelEvent;
  120. $event->sender = $this;
  121. $this->onBeforeUnlock($event);
  122. return $event->isValid;
  123. }
  124. /**
  125. * Invoked after the mutex is unlocked.
  126. * The default implementation raises the onAfterUnlock event
  127. */
  128. public function afterUnlock() {
  129. $event = new CEvent;
  130. $event->sender = $this;
  131. $this->onAfterUnlock($event);
  132. }
  133. /**
  134. * Raises the onBeforeLock event
  135. * @param CEvent $event the event to raise
  136. */
  137. public function onBeforeLock($event) {
  138. $this->raiseEvent("onBeforeLock",$event);
  139. }
  140. /**
  141. * Raises the onAfterLock event
  142. * @param CEvent $event the event to raise
  143. */
  144. public function onAfterLock($event) {
  145. $this->raiseEvent("onAfterLock",$event);
  146. }
  147. /**
  148. * Raises the onBeforeUnlock event
  149. * @param CEvent $event the event to raise
  150. */
  151. public function onBeforeUnlock($event) {
  152. $this->raiseEvent("onBeforeUnlock",$event);
  153. }
  154. /**
  155. * Raises the onAfterUnlock event
  156. * @param CEvent $event the event to raise
  157. */
  158. public function onAfterUnlock($event) {
  159. $this->raiseEvent("onAfterUnlock",$event);
  160. }
  161. /**
  162. * Gets the time the mutex expires
  163. * @param boolean $forceRecalculate whether to force recalculation or not
  164. * @return float the time the mutex expires
  165. */
  166. public function getExpiresAt($forceRecalculate = false)
  167. {
  168. if ($forceRecalculate || $this->_expiresAt === null) {
  169. $this->_expiresAt = $this->expiresAfter + microtime(true);
  170. }
  171. return $this->_expiresAt;
  172. }
  173. }