Search code examples
laravelcron

Laravel Scheduler and Cron - how does Laravel know not to run the scheduled jobs based on daily(), hourly() etc


In Laravel, in my Kernel, I have:

protected $commands = [
    Commands\SendRenewEmails::class,
];

/**
 * Define the application's command schedule.
 *
 * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 * @return void
 */
protected function schedule(Schedule $schedule)
{
    // $schedule->command('inspire')
    //          ->hourly();
    $schedule->command('renew:emails')
        ->daily();
}

The said function renew:emails, works as intended if I run this manually trough Artisan.

And in my crontab I have:

* */8 * * * cd /path-to-my-project && php artisan schedule:run >> /dev/null 2>&1

I have this to run every 8th hour, instead of every minute at * * * * *, since this is live for testing, and just to ensure that the task wasnt run every minute.

So how does Laravel know, when to run the daily job on the kernal, and when does this happen?

From this setup (which seems to be the basic setup for cronjobs in Laravel, but to run every minute instead of every 8th hour), there is no logs (that I can see), and no table in DB to keep track of this.

So if I where to set my cron to * * * * *, how does Laravel know not to run the scheduled job every minute, just because I have put ->daily(); at the end of the job?

And when I have daily();, at what specific time is that? And at specific what time is hourly();?

TL;DR: How does Laravel know not to run the same jobs again if it is not supposed to, for example with daily(); rule? Where is this information stored? How can I be certain that a job with rule daily(); wont run every minute if my cronjobs std:out's php artisan schedule:run every minute?


Solution

  • Under the hood, the Laravel Scheduler uses https://github.com/dragonmantank/cron-expression to determine if a command or job is scheduled to run at the given minute the schedule:run is called.

    Each task you schedule translates to a cron expression, which is then passed into the package. A method called isDue is then run against that expression to determine whether or not it should run. So, if you set a task to run hourly, then isDue will yield true at the top of the hour, and Laravel will execute to the task within the cron cycle.

    As such, the information does not need to be stored anywhere, as determination is done on the fly.

    This might also lead you to wonder what might happen if you have a long-running task that might take longer than the interval. This is where withoutOverlapping comes into the picture. When called, it creates what is known as a mutex, which is similar to a 'lock' of sorts (see What is a mutex? for more information), when the task is initially run. If a mutex already exists for a particular task on subsquent cycles, it means that task is currently running in another cycle, and should not be triggered again in this one.

    Where are mutexes stored? Simple: Laravel stores them in a cache, and when a mutexed task is finished, the mutex is removed from the cache. And so the cycle continues.

    I could go into much further detail here, but I think this answers your question for the most part.