diff --git a/src/Illuminate/Support/Sleep.php b/src/Illuminate/Support/Sleep.php index 35ef55685a38..0b19563915d4 100644 --- a/src/Illuminate/Support/Sleep.php +++ b/src/Illuminate/Support/Sleep.php @@ -342,7 +342,14 @@ protected function goodnight() while ($while()) { if ($seconds > 0) { - sleep($seconds); + $secondsToSleep = $seconds; + + // sleep() returns the number of seconds left to sleep if it was + // interrupted by a signal, such as those sent by a queue worker, + // so we keep sleeping until the requested duration has elapsed. + while ($secondsToSleep > 0) { + $secondsToSleep = sleep($secondsToSleep); + } $remaining = $remaining->subSeconds($seconds); } diff --git a/tests/Support/SleepTest.php b/tests/Support/SleepTest.php index 2f6388af9613..6d64ebd64b4b 100644 --- a/tests/Support/SleepTest.php +++ b/tests/Support/SleepTest.php @@ -32,6 +32,29 @@ public function testItSleepsForSeconds() $this->assertEqualsWithDelta(1, $end - $start, 0.03); } + public function testItKeepsSleepingWhenInterruptedBySignal() + { + if (! function_exists('pcntl_alarm')) { + $this->markTestSkipped('The pcntl extension is required.'); + } + + pcntl_async_signals(true); + pcntl_signal(SIGALRM, fn () => null); + + // Schedule a signal that will interrupt the sleep after one second. + pcntl_alarm(1); + + $start = microtime(true); + Sleep::for(2)->seconds(); + $elapsed = microtime(true) - $start; + + pcntl_alarm(0); + pcntl_signal(SIGALRM, SIG_DFL); + pcntl_async_signals(false); + + $this->assertGreaterThanOrEqual(2, round($elapsed, 1)); + } + public function testCallbacksMayBeExecutedUsingThen() { $this->assertEquals(123, Sleep::for(1)->milliseconds()->then(fn () => 123));