framework icon indicating copy to clipboard operation
framework copied to clipboard

Sleep facade behaves weirdly in queued jobs

Open xHeaven opened this issue 4 months ago • 2 comments

Laravel Version

12.24.0

PHP Version

8.4.4

Database Driver & Version

No response

Description

The Sleep facade behaves weirdly in queued jobs via Horizon.

Let's take this job:

class SleepTestJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle(): void
    {
        Log::debug("Should sleep for 60 seconds");

        Sleep::for(60)->seconds();

        Log::debug("Should be awake now");
    }
}

In this case, the following happens (made up timestamps):

  1. 00:00:00 - "Should sleep for 60 seconds" gets logged
  2. 00:00:37 - "Should be awake now" gets logged - even though 60 seconds has not passed yet
  3. Sleep::goodnight() goes into action via the destructor and tries to sleep the remaining time

The expected behavior would be:

  1. 00:00:00 - "Should sleep for 60 seconds" gets logged
  2. 00:01:00 - "Should be awake now" gets logged

Is there any intended reason why Sleep is not sleeping?

Since Horizon sends pcntl signals that disrupts built-in sleep(), simply calling sleep() is not an option. If I understand correctly, the goodnight() method should do some kind of signal-resistant sleeping, but it's happening at the wrong time and place.

Am I wrong? Is this an intended behavior? If so, what is the usecase?

Currently this is my workaround:

if (!function_exists('true_sleep')) {
    function true_sleep(float|int $seconds): void
    {
        $end = microtime(true) + $seconds;
        while (($remaining = $end - microtime(true)) > 0) {
            usleep((int)($remaining * 1_000_000));
        }
    }
}

Steps To Reproduce

class SleepTestJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle(): void
    {
        Log::debug("Should sleep for 60 seconds");

        Sleep::for(60)->seconds();

        Log::debug("Should be awake now");
    }
}
// console.php
Schedule::job(SleepTestJob::class)
    ->name('Sleep test job')
    ->yearly();
php artisan schedule:test // select Sleep test job

Then watch the logs.

xHeaven avatar Aug 13 '25 21:08 xHeaven