Skip to content

Commit 4fe9681

Browse files
committed
Fix pcntl_signal() restore when pcntl_alarm() is set
1 parent 2ab079f commit 4fe9681

File tree

3 files changed

+58
-23
lines changed

3 files changed

+58
-23
lines changed

src/Util/PcntlTimeout.php

+17-12
Original file line numberDiff line numberDiff line change
@@ -53,34 +53,39 @@ public function __construct(int $timeout)
5353
*
5454
* @return T
5555
*
56+
* @throws \Throwable
5657
* @throws DeadlineException Running the code hit the deadline
5758
* @throws LockAcquireException Installing the timeout failed
5859
*/
5960
public function timeBoxed(callable $code)
6061
{
61-
$existingHandler = pcntl_signal_get_handler(\SIGALRM);
62+
if (pcntl_alarm($this->timeout) !== 0) {
63+
throw new LockAcquireException('Existing process alarm is not supported');
64+
}
65+
66+
$origSignalHandler = pcntl_signal_get_handler(\SIGALRM);
6267

63-
$signal = pcntl_signal(\SIGALRM, function (): void {
68+
$timeout = $this->timeout;
69+
$signalHandlerFx = static function () use ($timeout): void {
6470
throw new DeadlineException(sprintf(
6571
'Timebox hit deadline of %d seconds',
66-
$this->timeout
72+
$timeout
6773
));
68-
});
69-
if (!$signal) {
70-
throw new LockAcquireException('Could not install signal');
71-
}
74+
};
7275

73-
$oldAlarm = pcntl_alarm($this->timeout);
74-
if ($oldAlarm !== 0) {
75-
throw new LockAcquireException('Existing alarm was not expected');
76+
if (!pcntl_signal(\SIGALRM, $signalHandlerFx)) {
77+
throw new LockAcquireException('Failed to install signal handler');
7678
}
7779

7880
try {
7981
return $code();
8082
} finally {
8183
pcntl_alarm(0);
82-
pcntl_signal_dispatch();
83-
pcntl_signal(\SIGALRM, $existingHandler);
84+
try {
85+
pcntl_signal_dispatch();
86+
} finally {
87+
pcntl_signal(\SIGALRM, $origSignalHandler);
88+
}
8489
}
8590
}
8691

tests/Util/LoopTest.php

+6-7
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public function testInvalidAcquireTimeout(float $acquireTimeout): void
4646

4747
$this->expectException(\InvalidArgumentException::class);
4848
$this->expectExceptionMessage('The lock acquire timeout must be greater than or equal to 0.0 (' . LockUtil::getInstance()->formatTimeout($acquireTimeout) . ' was given)');
49-
5049
$loop->execute(static function () {
5150
self::fail();
5251
}, $acquireTimeout);
@@ -78,10 +77,10 @@ public function testExecutionWithinAcquireTimeout(): void
7877

7978
public function testExecutionWithinAcquireTimeoutWithoutCallingEnd(): void
8079
{
80+
$loop = new Loop();
81+
8182
$this->expectException(LockAcquireTimeoutException::class);
8283
$this->expectExceptionMessage('Lock acquire timeout of 0.5 seconds has been exceeded');
83-
84-
$loop = new Loop();
8584
$loop->execute(static function () {
8685
usleep(10 * 1000);
8786
}, 0.5);
@@ -102,20 +101,20 @@ public function testExceedAcquireTimeoutIsAcceptableIfEndWasCalled(): void
102101

103102
public function testExceedAcquireTimeoutWithoutCallingEnd(): void
104103
{
104+
$loop = new Loop();
105+
105106
$this->expectException(LockAcquireTimeoutException::class);
106107
$this->expectExceptionMessage('Lock acquire timeout of 0.5 seconds has been exceeded');
107-
108-
$loop = new Loop();
109108
$loop->execute(static function () {
110109
usleep(501 * 1000);
111110
}, 0.5);
112111
}
113112

114113
public function testExceptionStopsIteration(): void
115114
{
116-
$this->expectException(\DomainException::class);
117-
118115
$loop = new Loop();
116+
117+
$this->expectException(\DomainException::class);
119118
$loop->execute(static function () {
120119
throw new \DomainException();
121120
}, 1);

tests/Util/PcntlTimeoutTest.php

+35-4
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ class PcntlTimeoutTest extends TestCase
2121
*/
2222
public function testShouldTimeout(): void
2323
{
24-
$this->expectException(DeadlineException::class);
25-
2624
$timeout = new PcntlTimeout(1);
2725

26+
$this->expectException(DeadlineException::class);
2827
$timeout->timeBoxed(static function () {
2928
sleep(2);
3029
});
@@ -44,22 +43,37 @@ public function testShouldNotTimeout(): void
4443
self::assertSame(42, $result);
4544
}
4645

46+
/**
47+
* Thrown exceptions from the subject code should be rethrown.
48+
*/
49+
public function testShouldThrowException(): void
50+
{
51+
$timeout = new PcntlTimeout(1);
52+
53+
$this->expectException(\DomainException::class);
54+
$timeout->timeBoxed(static function () {
55+
throw new \DomainException();
56+
});
57+
}
58+
4759
/**
4860
* When a previous scheduled alarm exists, it should fail.
4961
*/
5062
public function testShouldFailOnExistingAlarm(): void
5163
{
52-
$this->expectException(LockAcquireException::class);
53-
64+
$origSignalHandler = pcntl_signal_get_handler(\SIGALRM);
5465
try {
5566
pcntl_alarm(1);
5667
$timeout = new PcntlTimeout(1);
5768

69+
$this->expectException(LockAcquireException::class);
70+
$this->expectExceptionMessage('Existing process alarm is not supported');
5871
$timeout->timeBoxed(static function () {
5972
sleep(1);
6073
});
6174
} finally {
6275
pcntl_alarm(0);
76+
self::assertSame($origSignalHandler, pcntl_signal_get_handler(\SIGALRM));
6377
}
6478
}
6579

@@ -74,4 +88,21 @@ public function testShouldResetAlarmWhenNotTimeout(): void
7488

7589
self::assertSame(0, pcntl_alarm(0));
7690
}
91+
92+
/**
93+
* After not timing out and throwing an exception, there should be no alarm scheduled.
94+
*/
95+
public function testShouldResetAlarmWhenNotTimeoutAndException(): void
96+
{
97+
$timeout = new PcntlTimeout(3);
98+
99+
$this->expectException(\DomainException::class);
100+
try {
101+
$timeout->timeBoxed(static function () {
102+
throw new \DomainException();
103+
});
104+
} finally {
105+
self::assertSame(0, pcntl_alarm(0));
106+
}
107+
}
77108
}

0 commit comments

Comments
 (0)