PhpRedis.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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_PhpRedis 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. // Explicit UNWATCH we are not going to run the MULTI/EXEC block.
  26. $client->unwatch();
  27. unset($this->_locks[$name]);
  28. return FALSE;
  29. }
  30. // See https://github.com/phpredis/phpredis#watch-unwatch
  31. // MULTI and other commands can fail, so we can't chain calls.
  32. if (FALSE !== ($result = $client->multi())) {
  33. $client->setex($key, $timeout, $id);
  34. $result = $client->exec();
  35. }
  36. // Did it broke?
  37. if (FALSE === $result) {
  38. unset($this->_locks[$name]);
  39. // Explicit transaction release which also frees the WATCH'ed key.
  40. $client->discard();
  41. return FALSE;
  42. }
  43. return ($this->_locks[$name] = TRUE);
  44. }
  45. else {
  46. $client->watch($key);
  47. $owner = $client->get($key);
  48. // If the $key is set they lock is not available
  49. if (!empty($owner) && $id != $owner) {
  50. $client->unwatch();
  51. return FALSE;
  52. }
  53. // See https://github.com/phpredis/phpredis#watch-unwatch
  54. // MULTI and other commands can fail, so we can't chain calls.
  55. if (FALSE !== ($result = $client->multi())) {
  56. $client->setex($key, $timeout, $id);
  57. $result->exec();
  58. }
  59. // If another client modified the $key value, transaction will be discarded
  60. // $result will be set to FALSE. This means atomicity have been broken and
  61. // the other client took the lock instead of us.
  62. if (FALSE === $result) {
  63. // Explicit transaction release which also frees the WATCH'ed key.
  64. $client->discard();
  65. return FALSE;
  66. }
  67. // Register the lock.
  68. return ($this->_locks[$name] = TRUE);
  69. }
  70. return FALSE;
  71. }
  72. public function lockMayBeAvailable($name) {
  73. $client = $this->getClient();
  74. $key = $this->getKey($name);
  75. $id = $this->getLockId();
  76. $value = $client->get($key);
  77. return FALSE === $value || $id == $value;
  78. }
  79. public function lockRelease($name) {
  80. $client = $this->getClient();
  81. $key = $this->getKey($name);
  82. $id = $this->getLockId();
  83. unset($this->_locks[$name]);
  84. // Ensure the lock deletion is an atomic transaction. If another thread
  85. // manages to removes all lock, we can not alter it anymore else we will
  86. // release the lock for the other thread and cause race conditions.
  87. $client->watch($key);
  88. if ($client->get($key) == $id) {
  89. $client->multi();
  90. $client->del($key);
  91. $client->exec();
  92. }
  93. else {
  94. $client->unwatch();
  95. }
  96. }
  97. public function lockReleaseAll($lock_id = NULL) {
  98. if (!isset($lock_id) && empty($this->_locks)) {
  99. return;
  100. }
  101. $client = $this->getClient();
  102. $id = isset($lock_id) ? $lock_id : $this->getLockId();
  103. // We can afford to deal with a slow algorithm here, this should not happen
  104. // on normal run because we should have removed manually all our locks.
  105. foreach (array_keys($this->_locks) as $name) {
  106. $key = $this->getKey($name);
  107. $owner = $client->get($key);
  108. if (empty($owner) || $owner == $id) {
  109. $client->del($key);
  110. }
  111. }
  112. }
  113. }