6.0任务调度回调onSuccess方法在加入runInBackground后台执行后可能存在BUG

1.我现在把所有的调度任务写入到了数据库,代码执行正常,直到我加入了个runInBackground后,一切变得异常起来,我发现回调只是一个回调,没有其他的卵用,不能传递任务执行过程中产生的参数,可能我的表述有问题,请大家看代码和输出

        $objSchedule  = new \App\Models\Schedule();
        $objSchedules = $objSchedule->where('status',1)->get(); //里面的就一条  ls  执行服务器的ls操作
        $objSchedules->each(function ($item) use ($schedule) {

            //1.生成日志名
            $file_name = bin2hex(random_bytes(6)).'.log';
            Log::info($file_name);

            //2.执行命令
            $event = $schedule->exec($item->command)->name($item->name);

            //3.记录开始执行
            $event->before(function ()use ($file_name){
                Log::info("开始执行".$file_name);
            });

            //4.回调函数
            $event->onSuccess(function ()use ($file_name){
                Log::info("成功");
                Log::info($file_name);
            });

        });

以上代码的输出
[2023-09-15 15:26:03] local.INFO: 20db2771b605.log
[2023-09-15 15:26:03] local.INFO: 开始执行20db2771b605.log
[2023-09-15 15:26:03] local.INFO: 成功
[2023-09-15 15:26:03] local.INFO: 20db2771b605.log
十分正常。

但是我加入后台运行后,诡异的事情就发生了,

//2.执行命令
$event = $schedule->exec($item->command)->name($item->name)->runInBackground();

看下输出
[2023-09-15 15:33:17] local.INFO: 1571f11fd425.log
[2023-09-15 15:33:17] local.INFO: 开始执行1571f11fd425.log
[2023-09-15 15:33:18] local.INFO: c743ebaeb21d.log
[2023-09-15 15:33:18] local.INFO: 成功
[2023-09-15 15:33:18] local.INFO: c743ebaeb21d.log

很明显前面2个是一块的 后面3个是一块的 。 应该是回调的时候
有进来执行了一遍
Log::info($file_name);
紧接着又调用
Log::info(“成功”);
Log::info($file_name);
当然记录的文件也是第一次输出的1571f11fd425,应该是前面写道属性中了。

那么 这么做的问题是 比如 我们想保存这个运行的情况到数据库中,最后通过回调来更改运行成功还是失败,就导致没办法传递数据给回调。如:

            //1.生成日志名
            $file_name = bin2hex(random_bytes(6)).'.log';
            Log::info($file_name);

            //2.执行命令
            $event = $schedule->exec($item->command)->name($item->name)->runInBackground();

            //3.记录开始执行
            $event->before(function ()use ($file_name,$item){
                $objScheduleLog = new ScheduleLog();
                $objScheduleLog->log_path = $file_name;
                $objScheduleLog->schedule_id = $item->id;
                $objScheduleLog->save();
                $log_id = $objScheduleLog->id;  //现在问题来了  如何把这个id传递到这个onSuccess呢
                //$this->log_id = $log_id;?这样肯定不行,因为加了后台运行后,相当于重新进入这个循环体一遍,导致没有这个属性
            });

            //4.回调函数
            $event->onSuccess(function ()use ($file_name){
                // 哎  我如何获取这个log_id呢   我想改自己成功还是失败啊
                Log::info("成功");
                Log::info($file_name);
            });

现在想通过回调函数来更改 这个log日志的执行状态,但是通过上面的测试,这是重新进来执行的所以没办法获取到这个ID,是不是一个问题? 这应该是BUG把 我个人觉得是哈。

《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案

按照前面的想法,实践了一下,确实是可以,但是需要注意:Event::createMutexNameUsingeveryTenSeconds 只有在最新的 Laravel 10 才有。

工作原理很简单 ,就是利用了 schedule:finish,在后台任务完成时,Laravel 内部会调用 schedule:finish,并且调用当前任务的 mutexName 来生成任务的 id, 传递给这个命令,在这个命令中确定要去调取哪些 finish 的方法。

需要注意的点就是当调用的命令是 schedule:finish 就不应该给生成的 mutexName 添加任务 ID,这样才能正确匹配。

此方法相当于间接的改变了 Laravel 内部的实现,需要自行评估是否合理。

  • app/Console/Commands/ScheduleFinishCommand.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Console\Events\ScheduledBackgroundTaskFinished;
use Illuminate\Console\Scheduling\ScheduleFinishCommand as LaravelScheduleFinishCommand;
use Illuminate\Contracts\Events\Dispatcher;

class ScheduleFinishCommand extends LaravelScheduleFinishCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    /**
     * Execute the console command.
     * @param Schedule $schedule
     */
    public function handle(\Illuminate\Console\Scheduling\Schedule $schedule)
    {
        function writeLog($msg)
        {
            file_put_contents(base_path('run.log'), $msg . PHP_EOL, FILE_APPEND);
        }

        collect($schedule->events())->filter(function ($value) {
            $argument = $this->argument('id');
            // 使用 - 分割,
            $parts = explode('-', $argument);
            // 排除最后一个(因为最后一个是 ID)
            $argument = implode('-', array_slice($parts, 0, -1));
            return $value->mutexName() == $argument;
        })->each(function ($event) {
            $event->finish($this->laravel, $this->argument('code'));

            $this->laravel->make(Dispatcher::class)->dispatch(new ScheduledBackgroundTaskFinished($event));
        });
    }
}
  • app/Console/Kernel.php
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Event;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * Define the application's command schedule.
     */
    protected function schedule(Schedule $schedule): void
    {
        function writeLog($msg)
        {
            file_put_contents(base_path('run.log'), $msg . PHP_EOL, FILE_APPEND);
        }

        /**
         * @return string|null
         *
         * 当调用命令为 schedule:finish 时,获取参数中的 task_id
         */
        function parserFinishId()
        {
            $artisanCommand = $_SERVER['argv'][1] ?? '';
            if ($artisanCommand !== 'schedule:finish' && !empty($_SERVER['argv'][2])) {
                return '';
            }
            $parts = explode('-', $_SERVER['argv'][2]);
            return array_pop($parts);
        }

        // $schedule->command('inspire')->hourly();
        $taskId = uniqid();
        $schedule->exec('pwd')
            ->everyTenSeconds()
            ->runInBackground()
            ->onFailure(function () {
                writeLog('FAR:' . parserFinishId());
            })
            ->onSuccess(function () {
                writeLog('SUC:' . parserFinishId());
            })
            ->after(function () {
                writeLog('AFT:' . parserFinishId());
            })
            ->before(function () use ($taskId) {
                writeLog('PUS:' . $taskId);
            })
            ->createMutexNameUsing(function (Event $event) use ($taskId) {
                $artisanCommand = $_SERVER['argv'][1] ?? '';
                $mutexName = 'framework' . DIRECTORY_SEPARATOR . 'schedule-' . sha1($event->expression . $event->command);
                // 如果当前命令不是 schedule:finish ,就拼接任务 ID
                if ($artisanCommand !== 'schedule:finish') {
                    $mutexName .= '-' . $taskId;
                }
                // 否则不拼接
                return $mutexName;
            });
    }

    /**
     * Register the commands for the application.
     */
    protected function commands(): void
    {
        $this->load(__DIR__ . '/Commands');

        require base_path('routes/console.php');
    }
}
7个月前 评论
cccdz 7个月前
Rache1 (作者) 7个月前
liuhaiqiang999 (楼主) 7个月前
liuhaiqiang999 (楼主) 7个月前
Rache1 (作者) 7个月前
cccdz 7个月前
讨论数量: 31
翟宇鑫

log_id 好像就是 model id?
each 中可以获取到 model id 的吧?

7个月前 评论
liuhaiqiang999 (楼主) 7个月前
liuhaiqiang999 (楼主) 7个月前
liuhaiqiang999 (楼主) 7个月前

因为runInBackground 方法是设置后台运行 就是会运行一个命令后台运行 command命令执行完后 就会执行schedule:finish命令 去做 后续操作 比如onSuccess onFailure 这里是一个新的进程去处理了 所以会不一样

博客: Laravel Schedule(任务调度)源码分析 可以看一下这个

7个月前 评论
liuhaiqiang999 (楼主) 7个月前
liuhaiqiang999 (楼主) 7个月前
cccdz (作者) 7个月前
Rache1 7个月前
liuhaiqiang999 (楼主) 7个月前
cccdz (作者) 7个月前
liuhaiqiang999 (楼主) 7个月前
liuhaiqiang999 (楼主) 7个月前
liuhaiqiang999 (楼主) 7个月前
cccdz (作者) 7个月前
Rache1 7个月前
liuhaiqiang999 (楼主) 7个月前

衍生一楼和楼主的想法,我想到了一种方式,你可以将 $objScheduleLog->save()保存后的id,赋值到 $objSchedules->each(function ($item) use ($schedule) 中的$item中,然后在onSuccess中通过$item对象去获取。

具体代码:

$file_name = bin2hex(random_bytes(6)).'.log';
 Log::info($file_name);

 $event = $schedule->exec($item->command)->name($item->name)->runInBackground();

$event->before(function ()use ($file_name,$item){
    $objScheduleLog = new ScheduleLog();
    $objScheduleLog->log_path = $file_name;
    $objScheduleLog->schedule_id = $item->id;
    $objScheduleLog->save();

    //通过赋值给$item对象
    $item->log_id = $objScheduleLog->id;
});


$event->onSuccess(function ()use ($file_name,$item){
   //这样应该可以获取到
    var_dump($item->log_id);
    log::info("success");
    Log::info($file_name);
});
7个月前 评论
fengder (作者) 7个月前

按照前面的想法,实践了一下,确实是可以,但是需要注意:Event::createMutexNameUsingeveryTenSeconds 只有在最新的 Laravel 10 才有。

工作原理很简单 ,就是利用了 schedule:finish,在后台任务完成时,Laravel 内部会调用 schedule:finish,并且调用当前任务的 mutexName 来生成任务的 id, 传递给这个命令,在这个命令中确定要去调取哪些 finish 的方法。

需要注意的点就是当调用的命令是 schedule:finish 就不应该给生成的 mutexName 添加任务 ID,这样才能正确匹配。

此方法相当于间接的改变了 Laravel 内部的实现,需要自行评估是否合理。

  • app/Console/Commands/ScheduleFinishCommand.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Console\Events\ScheduledBackgroundTaskFinished;
use Illuminate\Console\Scheduling\ScheduleFinishCommand as LaravelScheduleFinishCommand;
use Illuminate\Contracts\Events\Dispatcher;

class ScheduleFinishCommand extends LaravelScheduleFinishCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    /**
     * Execute the console command.
     * @param Schedule $schedule
     */
    public function handle(\Illuminate\Console\Scheduling\Schedule $schedule)
    {
        function writeLog($msg)
        {
            file_put_contents(base_path('run.log'), $msg . PHP_EOL, FILE_APPEND);
        }

        collect($schedule->events())->filter(function ($value) {
            $argument = $this->argument('id');
            // 使用 - 分割,
            $parts = explode('-', $argument);
            // 排除最后一个(因为最后一个是 ID)
            $argument = implode('-', array_slice($parts, 0, -1));
            return $value->mutexName() == $argument;
        })->each(function ($event) {
            $event->finish($this->laravel, $this->argument('code'));

            $this->laravel->make(Dispatcher::class)->dispatch(new ScheduledBackgroundTaskFinished($event));
        });
    }
}
  • app/Console/Kernel.php
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Event;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * Define the application's command schedule.
     */
    protected function schedule(Schedule $schedule): void
    {
        function writeLog($msg)
        {
            file_put_contents(base_path('run.log'), $msg . PHP_EOL, FILE_APPEND);
        }

        /**
         * @return string|null
         *
         * 当调用命令为 schedule:finish 时,获取参数中的 task_id
         */
        function parserFinishId()
        {
            $artisanCommand = $_SERVER['argv'][1] ?? '';
            if ($artisanCommand !== 'schedule:finish' && !empty($_SERVER['argv'][2])) {
                return '';
            }
            $parts = explode('-', $_SERVER['argv'][2]);
            return array_pop($parts);
        }

        // $schedule->command('inspire')->hourly();
        $taskId = uniqid();
        $schedule->exec('pwd')
            ->everyTenSeconds()
            ->runInBackground()
            ->onFailure(function () {
                writeLog('FAR:' . parserFinishId());
            })
            ->onSuccess(function () {
                writeLog('SUC:' . parserFinishId());
            })
            ->after(function () {
                writeLog('AFT:' . parserFinishId());
            })
            ->before(function () use ($taskId) {
                writeLog('PUS:' . $taskId);
            })
            ->createMutexNameUsing(function (Event $event) use ($taskId) {
                $artisanCommand = $_SERVER['argv'][1] ?? '';
                $mutexName = 'framework' . DIRECTORY_SEPARATOR . 'schedule-' . sha1($event->expression . $event->command);
                // 如果当前命令不是 schedule:finish ,就拼接任务 ID
                if ($artisanCommand !== 'schedule:finish') {
                    $mutexName .= '-' . $taskId;
                }
                // 否则不拼接
                return $mutexName;
            });
    }

    /**
     * Register the commands for the application.
     */
    protected function commands(): void
    {
        $this->load(__DIR__ . '/Commands');

        require base_path('routes/console.php');
    }
}
7个月前 评论
cccdz 7个月前
Rache1 (作者) 7个月前
liuhaiqiang999 (楼主) 7个月前
liuhaiqiang999 (楼主) 7个月前
Rache1 (作者) 7个月前
cccdz 7个月前

这样可以吗 @Rache1 @liuhaiqiang999

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

abstract class BaseScheduleCommand extends Command
{
    public function __construct()
    {
        $this->signature .= ' {--is_schedule_run : 是否定時任務執行} {--schedule_id=0 : 定時任務ID}';

        parent::__construct();
    }

    /**
     * 執行
     *
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return int
     * @throws Throwable
     */
    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->info('start');

        if ($isScheduleRun = $this->option('is_schedule_run')) {
            //創建子記錄 標記開始執行
        }

        try {
            $exitCode = parent::execute($input, $output);
        } catch (Throwable $e) {
            $isThrow = true;

            throw $e;
        } finally {
            $this->info('after');

            if ($isScheduleRun) {
                //標記結束
                if ($isThrow ?? false) {
                    //是否異常
                }
            }
        }

        return $exitCode;
    }
}
<?php

namespace App\Console\Commands;

class Test extends BaseScheduleCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'test';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '測試';

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        $this->info('ccc');
    }
}
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * Define the application's command schedule.
     */
    protected function schedule(Schedule $schedule): void
    {
        // $schedule->command('inspire')->hourly();
        $schedule->command('test --is_schedule_run --schedule_id=1')->everyMinute();
    }

    /**
     * Register the commands for the application.
     */
    protected function commands(): void
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }
}
7个月前 评论
liuhaiqiang999 (楼主) 7个月前
Rache1 7个月前
cccdz (作者) 7个月前
cccdz (作者) 7个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!