ConcurrentLimiterTest.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. <?php
  2. namespace Illuminate\Tests\Redis;
  3. use Error;
  4. use Illuminate\Contracts\Redis\LimiterTimeoutException;
  5. use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis;
  6. use Illuminate\Redis\Limiters\ConcurrencyLimiter;
  7. use PHPUnit\Framework\TestCase;
  8. use Throwable;
  9. class ConcurrentLimiterTest extends TestCase
  10. {
  11. use InteractsWithRedis;
  12. protected function setUp(): void
  13. {
  14. parent::setUp();
  15. $this->setUpRedis();
  16. }
  17. protected function tearDown(): void
  18. {
  19. parent::tearDown();
  20. $this->tearDownRedis();
  21. }
  22. public function testItLocksTasksWhenNoSlotAvailable()
  23. {
  24. $store = [];
  25. foreach (range(1, 2) as $i) {
  26. (new ConcurrencyLimiterMockThatDoesntRelease($this->redis(), 'key', 2, 5))->block(2, function () use (&$store, $i) {
  27. $store[] = $i;
  28. });
  29. }
  30. try {
  31. (new ConcurrencyLimiterMockThatDoesntRelease($this->redis(), 'key', 2, 5))->block(0, function () use (&$store) {
  32. $store[] = 3;
  33. });
  34. } catch (Throwable $e) {
  35. $this->assertInstanceOf(LimiterTimeoutException::class, $e);
  36. }
  37. (new ConcurrencyLimiterMockThatDoesntRelease($this->redis(), 'other_key', 2, 5))->block(2, function () use (&$store) {
  38. $store[] = 4;
  39. });
  40. $this->assertEquals([1, 2, 4], $store);
  41. }
  42. public function testItReleasesLockAfterTaskFinishes()
  43. {
  44. $store = [];
  45. foreach (range(1, 4) as $i) {
  46. (new ConcurrencyLimiter($this->redis(), 'key', 2, 5))->block(2, function () use (&$store, $i) {
  47. $store[] = $i;
  48. });
  49. }
  50. $this->assertEquals([1, 2, 3, 4], $store);
  51. }
  52. public function testItReleasesLockIfTaskTookTooLong()
  53. {
  54. $store = [];
  55. $lock = (new ConcurrencyLimiterMockThatDoesntRelease($this->redis(), 'key', 1, 1));
  56. $lock->block(2, function () use (&$store) {
  57. $store[] = 1;
  58. });
  59. try {
  60. $lock->block(0, function () use (&$store) {
  61. $store[] = 2;
  62. });
  63. } catch (Throwable $e) {
  64. $this->assertInstanceOf(LimiterTimeoutException::class, $e);
  65. }
  66. usleep(1.2 * 1000000);
  67. $lock->block(0, function () use (&$store) {
  68. $store[] = 3;
  69. });
  70. $this->assertEquals([1, 3], $store);
  71. }
  72. public function testItFailsImmediatelyOrRetriesForAWhileBasedOnAGivenTimeout()
  73. {
  74. $store = [];
  75. $lock = (new ConcurrencyLimiterMockThatDoesntRelease($this->redis(), 'key', 1, 2));
  76. $lock->block(2, function () use (&$store) {
  77. $store[] = 1;
  78. });
  79. try {
  80. $lock->block(0, function () use (&$store) {
  81. $store[] = 2;
  82. });
  83. } catch (Throwable $e) {
  84. $this->assertInstanceOf(LimiterTimeoutException::class, $e);
  85. }
  86. $lock->block(3, function () use (&$store) {
  87. $store[] = 3;
  88. });
  89. $this->assertEquals([1, 3], $store);
  90. }
  91. public function testItFailsAfterRetryTimeout()
  92. {
  93. $store = [];
  94. $lock = (new ConcurrencyLimiterMockThatDoesntRelease($this->redis(), 'key', 1, 10));
  95. $lock->block(2, function () use (&$store) {
  96. $store[] = 1;
  97. });
  98. try {
  99. $lock->block(2, function () use (&$store) {
  100. $store[] = 2;
  101. });
  102. } catch (Throwable $e) {
  103. $this->assertInstanceOf(LimiterTimeoutException::class, $e);
  104. }
  105. $this->assertEquals([1], $store);
  106. }
  107. public function testItReleasesIfErrorIsThrown()
  108. {
  109. $store = [];
  110. $lock = new ConcurrencyLimiter($this->redis(), 'key', 1, 5);
  111. try {
  112. $lock->block(1, function () {
  113. throw new Error;
  114. });
  115. } catch (Error $e) {
  116. }
  117. $lock = new ConcurrencyLimiter($this->redis(), 'key', 1, 5);
  118. $lock->block(1, function () use (&$store) {
  119. $store[] = 1;
  120. });
  121. $this->assertEquals([1], $store);
  122. }
  123. private function redis()
  124. {
  125. return $this->redis['phpredis']->connection();
  126. }
  127. }
  128. class ConcurrencyLimiterMockThatDoesntRelease extends ConcurrencyLimiter
  129. {
  130. protected function release($key, $id)
  131. {
  132. //
  133. }
  134. }