Skip to content

Sleep facade behaves weirdly in queued jobs #56647

@xHeaven

Description

@xHeaven

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions