Skip to content

Commit 2ca0242

Browse files
committed
Switch to Clock 5.0.0 leveraging mostly Symfony
1 parent 033a30f commit 2ca0242

File tree

5 files changed

+66
-91
lines changed

5 files changed

+66
-91
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"php": "^8.4",
1313
"ext-mongodb": "*",
1414
"mongodb/mongodb": "^2.1",
15-
"recruiterphp/clock": "^4.1"
15+
"recruiterphp/clock": "^5.0"
1616
},
1717
"require-dev": {
1818
"ergebnis/composer-normalize": "^2.47",

src/Recruiter/Concurrency/MongoLock.php

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,22 @@
77
use MongoDB\BSON\UTCDateTime;
88
use MongoDB\Collection;
99
use MongoDB\Driver\Exception\BulkWriteException;
10-
use Recruiter\Clock\SystemClock;
10+
use Symfony\Component\Clock\ClockInterface;
11+
use Symfony\Component\Clock\NativeClock;
1112

1213
class MongoLock implements Lock
1314
{
14-
public const DUPLICATE_KEY = 11000;
15-
16-
private $collection;
17-
private $processName;
18-
private $programName;
19-
private $clock;
20-
private $sleep;
21-
22-
public function __construct(Collection $collection, $programName, $processName, $clock = null, $sleep = 'sleep')
23-
{
24-
$this->collection = $collection;
15+
private const int DUPLICATE_KEY = 11000;
16+
private ClockInterface $clock;
17+
18+
public function __construct(
19+
private readonly Collection $collection,
20+
private readonly string $programName,
21+
private readonly string $processName,
22+
?ClockInterface $clock = null
23+
) {
2524
$this->collection->createIndex(['program' => 1], ['unique' => true]);
26-
$this->programName = $programName;
27-
$this->processName = $processName;
28-
if (null === $clock) {
29-
$clock = new SystemClock();
30-
}
31-
$this->clock = $clock;
32-
$this->sleep = $sleep;
25+
$this->clock = $clock ?? new NativeClock();
3326
}
3427

3528
public static function forProgram($programName, Collection $collection): self
@@ -39,12 +32,11 @@ public static function forProgram($programName, Collection $collection): self
3932

4033
public function acquire(int $duration = 3600): void
4134
{
42-
$now = $this->clock->current();
35+
$now = $this->clock->now();
4336

4437
$this->removeExpiredLocks($now);
4538

46-
$expiration = clone $now;
47-
$expiration->add(new \DateInterval("PT{$duration}S"));
39+
$expiration = $now->add(new \DateInterval("PT{$duration}S"));
4840

4941
try {
5042
$document = [
@@ -64,12 +56,11 @@ public function acquire(int $duration = 3600): void
6456

6557
public function refresh(int $duration = 3600): void
6658
{
67-
$now = $this->clock->current();
59+
$now = $this->clock->now();
6860

6961
$this->removeExpiredLocks($now);
7062

71-
$expiration = clone $now;
72-
$expiration->add(new \DateInterval("PT{$duration}S"));
63+
$expiration = $now->add(new \DateInterval("PT{$duration}S"));
7364

7465
$result = $this->collection->updateOne(
7566
['program' => $this->programName, 'process' => $this->processName],
@@ -115,9 +106,9 @@ public function release(bool $force = false): void
115106
*/
116107
public function wait(int $polling = 30, int $maximumWaitingTime = 3600): void
117108
{
118-
$timeLimit = $this->clock->current()->add(new \DateInterval("PT{$maximumWaitingTime}S"));
109+
$timeLimit = $this->clock->now()->add(new \DateInterval("PT{$maximumWaitingTime}S"));
119110
while (true) {
120-
$now = $this->clock->current();
111+
$now = $this->clock->now();
121112
$result = $this->collection->count($query = [
122113
'program' => $this->programName,
123114
'expires_at' => ['$gte' => new UTCDateTime($now)],
@@ -127,7 +118,7 @@ public function wait(int $polling = 30, int $maximumWaitingTime = 3600): void
127118
if ($now > $timeLimit) {
128119
throw new LockNotAvailableException("I have been waiting up until {$timeLimit->format(\DateTime::ATOM)} for the lock $this->programName ($maximumWaitingTime seconds polling every $polling seconds), but it is still not available (now is {$now->format(\DateTime::ATOM)}).");
129120
}
130-
call_user_func($this->sleep, $polling);
121+
$this->clock->sleep($polling);
131122
} else {
132123
break;
133124
}
@@ -139,7 +130,7 @@ public function __toString(): string
139130
return var_export($this->show(), true);
140131
}
141132

142-
private function removeExpiredLocks(\DateTime $now): void
133+
private function removeExpiredLocks(\DateTimeImmutable $now): void
143134
{
144135
$this->collection->deleteMany($query = [
145136
'program' => $this->programName,

src/Recruiter/Concurrency/PeriodicalCheck.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,20 @@
44

55
namespace Recruiter\Concurrency;
66

7-
use Recruiter\Clock;
8-
use Recruiter\Clock\SystemClock;
7+
use Symfony\Component\Clock\ClockInterface;
8+
use Symfony\Component\Clock\NativeClock;
99

1010
class PeriodicalCheck
1111
{
1212
private array|\Closure $check;
1313
private int $lastCheck;
1414

15-
public static function every(int $seconds, ?Clock $clock = null): self
15+
public static function every(int $seconds, ?ClockInterface $clock = null): self
1616
{
17-
if (null === $clock) {
18-
$clock = new SystemClock();
19-
}
20-
21-
return new self($seconds, $clock);
17+
return new self($seconds, $clock ?? new NativeClock());
2218
}
2319

24-
private function __construct(private readonly int $seconds, private readonly Clock $clock)
20+
private function __construct(private readonly int $seconds, private readonly ClockInterface $clock)
2521
{
2622
$this->lastCheck = 0;
2723
}
@@ -43,7 +39,7 @@ public function __invoke(): void
4339

4440
public function execute(): void
4541
{
46-
$now = $this->clock->current()->getTimestamp();
42+
$now = $this->clock->now()->getTimestamp();
4743
if ($now - $this->lastCheck >= $this->seconds) {
4844
call_user_func($this->check);
4945
$this->lastCheck = $now;

tests/Recruiter/Concurrency/MongoLockTest.php

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,23 @@
1010
use Phake;
1111
use PHPUnit\Framework\Attributes\Group;
1212
use PHPUnit\Framework\TestCase;
13-
use Recruiter\Clock;
13+
use Recruiter\Clock\ProgressiveClock;
14+
use Symfony\Component\Clock\ClockInterface;
1415
use Symfony\Component\Process\Process;
1516

1617
class MongoLockTest extends TestCase
1718
{
1819
use Eris\TestTrait;
1920

2021
private MongoDB\Collection $lockCollection;
21-
private Clock&Phake\IMock $clock;
22-
private array $slept;
23-
private \Closure $sleep;
22+
private (ClockInterface&Phake\IMock)|ProgressiveClock $clock;
2423
private int $iteration;
2524

2625
protected function setUp(): void
2726
{
2827
$uri = getenv('MONGODB_URI') ?: null;
2928
$this->lockCollection = new MongoDB\Client($uri)->selectCollection('concurrency-test', 'lock');
30-
$this->clock = \Phake::mock(Clock::class);
31-
32-
$this->slept = [];
33-
$this->sleep = function ($amount): void {
34-
$this->slept[] = $amount;
35-
};
29+
$this->clock = \Phake::mock(ClockInterface::class);
3630
}
3731

3832
protected function tearDown(): void
@@ -78,9 +72,9 @@ public function testAnAlreadyAcquiredLockCannotBeAcquiredAgainEvenWithRefreshMet
7872

7973
public function testAnAlreadyAcquiredLockCanExpireSoThatItCanBeAcquiredAgain()
8074
{
81-
\Phake::when($this->clock)->current()
82-
->thenReturn(new \DateTime('2014-01-01T10:00:00Z'))
83-
->thenReturn(new \DateTime('2014-01-01T11:00:01Z'))
75+
\Phake::when($this->clock)->now()
76+
->thenReturn(new \DateTimeImmutable('2014-01-01T10:00:00Z'))
77+
->thenReturn(new \DateTimeImmutable('2014-01-01T11:00:01Z'))
8478
;
8579
$first = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
8680
$first->acquire(3600);
@@ -139,8 +133,8 @@ public function testALockCanBeForcedToBeReleasedIfYouReallyKnowWhatYouReDoing()
139133

140134
public function testALockCanBeShownEvenByOtherProcessesWorkingOnTheSameProgram()
141135
{
142-
\Phake::when($this->clock)->current()
143-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
136+
\Phake::when($this->clock)->now()
137+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:00Z'))
144138
;
145139
$first = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
146140
$first->acquire(3600);
@@ -159,35 +153,29 @@ public function testALockCanBeShownEvenByOtherProcessesWorkingOnTheSameProgram()
159153

160154
public function testALockCanBeWaitedOnUntilItsDisappearance()
161155
{
162-
$allCalls = \Phake::when($this->clock)->current()
163-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
164-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
165-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
166-
->thenReturn(new \DateTime('2014-01-01T00:00:30Z'))
167-
->thenReturn(new \DateTime('2014-01-01T00:01:00Z'))
156+
$allCalls = \Phake::when($this->clock)->now()
157+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:00Z'))
158+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:00Z'))
159+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:00Z'))
160+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:30Z'))
161+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:01:00Z'))
168162
;
169163
$first = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
170164
$first->acquire(45);
171165

172-
$second = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock, $this->sleep);
166+
$second = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
173167
$second->wait($polling = 30);
174-
$this->assertEquals([30, 30], $this->slept);
168+
\Phake::verify($this->clock, \Phake::times(2))->sleep(30);
175169
}
176170

177171
public function testALockShouldNotBeWaitedUponForever()
178172
{
179-
$allCalls = \Phake::when($this->clock)->current()
180-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
181-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
182-
->thenReturn(new \DateTime('2014-01-01T00:00:30Z'))
183-
->thenReturn(new \DateTime('2014-01-01T00:00:50Z'))
184-
->thenReturn(new \DateTime('2014-01-01T00:01:01Z'))
185-
->thenThrow(new \LogicException('Should not call anymore'))
186-
;
173+
$this->clock = new ProgressiveClock(new \DateTimeImmutable('2014-01-01T00:00:00Z'), \DateInterval::createFromDateString('500 milliseconds'));
174+
187175
$first = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
188176
$first->acquire(3600);
189177

190-
$second = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock, $this->sleep);
178+
$second = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
191179
try {
192180
$second->wait($polling = 30, $maximumInterval = 60);
193181
$this->fail('Should fail after 60 seconds');
@@ -201,28 +189,28 @@ public function testALockShouldNotBeWaitedUponForever()
201189

202190
public function testALockWaitedUponCanBeImmediatelyReacquired()
203191
{
204-
$allCalls = \Phake::when($this->clock)->current()
205-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
206-
->thenReturn(new \DateTime('2014-01-01T00:00:30Z'))
207-
->thenReturn(new \DateTime('2014-01-01T00:00:30Z'))
208-
->thenReturn(new \DateTime('2014-01-01T00:00:30Z'))
209-
->thenReturn(new \DateTime('2014-01-01T00:00:31Z'))
210-
->thenReturn(new \DateTime('2014-01-01T00:00:31Z'))
192+
$allCalls = \Phake::when($this->clock)->now()
193+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:00Z'))
194+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:30Z'))
195+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:30Z'))
196+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:30Z'))
197+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:31Z'))
198+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:31Z'))
211199
;
212200
$first = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
213201
$first->acquire(30);
214202

215-
$second = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock, $this->sleep);
203+
$second = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
216204
$second->wait($polling = 1);
217205
$second->acquire();
218206
$this->expectNotToPerformAssertions();
219207
}
220208

221209
public function testAnAlreadyAcquiredLockCanBeRefreshed()
222210
{
223-
\Phake::when($this->clock)->current()
224-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
225-
->thenReturn(new \DateTime('2014-01-01T00:10:00Z'))
211+
\Phake::when($this->clock)->now()
212+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:00Z'))
213+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:10:00Z'))
226214
;
227215

228216
$first = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
@@ -244,9 +232,9 @@ public function testAnAlreadyAcquiredLockCanBeRefreshed()
244232

245233
public function testAnExpiredLockCannotBeRefreshed()
246234
{
247-
\Phake::when($this->clock)->current()
248-
->thenReturn(new \DateTime('2014-01-01T00:00:00Z'))
249-
->thenReturn(new \DateTime('2014-01-01T02:00:00Z'))
235+
\Phake::when($this->clock)->now()
236+
->thenReturn(new \DateTimeImmutable('2014-01-01T00:00:00Z'))
237+
->thenReturn(new \DateTimeImmutable('2014-01-01T02:00:00Z'))
250238
;
251239

252240
$first = new MongoLock($this->lockCollection, 'windows_defrag', 'ws-a-25:42', $this->clock);
@@ -261,7 +249,7 @@ public function testAnExpiredLockCannotBeRefreshed()
261249

262250
private function givenTimeIsFixed()
263251
{
264-
\Phake::when($this->clock)->current()->thenReturn(new \DateTime('2014-01-01'));
252+
\Phake::when($this->clock)->now()->thenReturn(new \DateTimeImmutable('2014-01-01'));
265253
}
266254

267255
#[Group('long')]

tests/Recruiter/Concurrency/PeriodicalCheckTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Eris\Generator;
99
use Eris\Listener;
1010
use PHPUnit\Framework\TestCase;
11-
use Recruiter\Clock\SettableClock;
11+
use Symfony\Component\Clock\MockClock;
1212

1313
class PeriodicalCheckTest extends TestCase
1414
{
@@ -27,16 +27,16 @@ public function testDoesNotPerformTheCheckTooManyTimes()
2727
),
2828
)
2929
// ->hook(Listener\collectFrequencies())
30-
->then(function ($startingDate, $period, $deltas): void {
31-
$clock = new SettableClock($startingDate);
30+
->then(function (\DateTime $startingDate, int $period, array $deltas): void {
31+
$clock = new MockClock(\DateTimeImmutable::createFromMutable($startingDate));
3232
$check = PeriodicalCheck::every($period, $clock);
3333
$this->counter = 0;
3434
$check->onFire(function (): void {
3535
++$this->counter;
3636
});
3737
$check->__invoke();
3838
foreach ($deltas as $delta) {
39-
$clock->advance($delta);
39+
$clock->sleep($delta);
4040
$check->__invoke();
4141
}
4242
$totalInterval = array_sum($deltas);

0 commit comments

Comments
 (0)