Predis.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. <?php
  2. /**
  3. * Predis lock backend implementation.
  4. *
  5. * This implementation works with a single key per lock so is viable when
  6. * doing client side sharding and/or using consistent hashing algorithm.
  7. */
  8. class Redis_Lock_Predis extends Redis_Lock_DefaultBackend {
  9. public function lockAcquire($name, $timeout = 30.0) {
  10. $client = $this->getClient();
  11. $key = $this->getKey($name);
  12. $id = $this->getLockId();
  13. // Insure that the timeout is at least 1 second, we cannot do otherwise with
  14. // Redis, this is a minor change to the function signature, but in real life
  15. // nobody will notice with so short duration.
  16. $timeout = ceil(max($timeout, 1));
  17. // If we already have the lock, check for his owner and attempt a new EXPIRE
  18. // command on it.
  19. if (isset($this->_locks[$name])) {
  20. // Create a new transaction, for atomicity.
  21. $client->watch($key);
  22. // Global tells us we are the owner, but in real life it could have expired
  23. // and another process could have taken it, check that.
  24. if ($client->get($key) != $id) {
  25. $client->unwatch();
  26. unset($this->_locks[$name]);
  27. return FALSE;
  28. }
  29. $replies = $client->pipeline(function($pipe) use ($key, $timeout, $id) {
  30. $pipe->multi();
  31. $pipe->setex($key, $timeout, $id);
  32. $pipe->exec();
  33. });
  34. $execReply = array_pop($replies);
  35. if (FALSE === $execReply[0]) {
  36. unset($this->_locks[$name]);
  37. return FALSE;
  38. }
  39. return TRUE;
  40. }
  41. else {
  42. $client->watch($key);
  43. $owner = $client->get($key);
  44. if (!empty($owner) && $owner != $id) {
  45. $client->unwatch();
  46. unset($this->_locks[$name]);
  47. return FALSE;
  48. }
  49. $replies = $client->pipeline(function($pipe) use ($key, $timeout, $id) {
  50. $pipe->multi();
  51. $pipe->setex($key, $timeout, $id);
  52. $pipe->exec();
  53. });
  54. $execReply = array_pop($replies);
  55. // If another client modified the $key value, transaction will be discarded
  56. // $result will be set to FALSE. This means atomicity have been broken and
  57. // the other client took the lock instead of us.
  58. // EXPIRE and SETEX won't return something here, EXEC return is index 0
  59. // This was determined debugging, seems to be Predis specific.
  60. if (FALSE === $execReply[0]) {
  61. return FALSE;
  62. }
  63. // Register the lock and return.
  64. return ($this->_locks[$name] = TRUE);
  65. }
  66. return FALSE;
  67. }
  68. public function lockMayBeAvailable($name) {
  69. $client = $this->getClient();
  70. $key = $this->getKey($name);
  71. $id = $this->getLockId();
  72. $value = $client->get($key);
  73. return empty($value) || $id == $value;
  74. }
  75. public function lockRelease($name) {
  76. $client = $this->getClient();
  77. $key = $this->getKey($name);
  78. $id = $this->getLockId();
  79. unset($this->_locks[$name]);
  80. // Ensure the lock deletion is an atomic transaction. If another thread
  81. // manages to removes all lock, we can not alter it anymore else we will
  82. // release the lock for the other thread and cause race conditions.
  83. $client->watch($key);
  84. if ($client->get($key) == $id) {
  85. $client->multi();
  86. $client->del(array($key));
  87. $client->exec();
  88. }
  89. else {
  90. $client->unwatch();
  91. }
  92. }
  93. public function lockReleaseAll($lock_id = NULL) {
  94. if (!isset($lock_id) && empty($this->_locks)) {
  95. return;
  96. }
  97. $client = $this->getClient();
  98. $id = isset($lock_id) ? $lock_id : $this->getLockId();
  99. // We can afford to deal with a slow algorithm here, this should not happen
  100. // on normal run because we should have removed manually all our locks.
  101. foreach (array_keys($this->_locks) as $name) {
  102. $key = $this->getKey($name);
  103. $owner = $client->get($key);
  104. if (empty($owner) || $owner == $id) {
  105. $client->del(array($key));
  106. }
  107. }
  108. }
  109. }