swoole-bundle icon indicating copy to clipboard operation
swoole-bundle copied to clipboard

Symfony Scheduler integration proposal

Open mleczakm opened this issue 6 months ago • 0 comments

Having ability to run cron tasks using symfony scheduler would be really useful, I don't know currently how should it be handled: using tick, another sub-process or on task handling. I will try with rewriting Symfony\Component\Scheduler class to make it non-blocking, so I will remove while loop and sleep and run it using tick inside custom configurator, WithScheduler implementing SwooleBundle\SwooleBundle\Server\Configurator.

It is possible, only custom scheduler must be written, something like:

class Scheduler
{
    /**
     * @var array<MessageGenerator>
     */
    private array $generators = [];
    private int $index = 0;
    /**
     * @param iterable<ScheduleProviderInterface> $scheduleProviders
     */
    public function __construct(private MessageBusInterface $bus, array $scheduleProviders, private readonly ClockInterface $clock = new Clock(), private readonly ?EventDispatcherInterface $dispatcher = null)
    {

        foreach ($scheduleProviders as $scheduleProvider) {
            $this->addSchedule($scheduleProvider->getSchedule());
        }
    }

    public function addSchedule(Schedule $schedule): void
    {
        $this->addMessageGenerator(new MessageGenerator($schedule, 'schedule_' . $this->index++, $this->clock));
    }

    public function addMessageGenerator(MessageGenerator $generator): void
    {
        $this->generators[] = $generator;
    }
    public function run(): void
    {

        foreach ($this->generators as $generator) {
            foreach ($generator->getMessages() as $context => $message) {
                if (!$this->dispatcher) {
                    $this->bus->dispatch($message);

                    continue;
                }

                $preRunEvent = new PreRunEvent($generator->getSchedule(), $context, $message);
                $this->dispatcher->dispatch($preRunEvent);

                if ($preRunEvent->shouldCancel()) {
                    continue;
                }

                try {
                    $this->bus->dispatch($message);

                    $this->dispatcher->dispatch(new PostRunEvent($generator->getSchedule(), $context, $message));
                } catch (Throwable $error) {
                    $failureEvent = new FailureEvent($generator->getSchedule(), $context, $message, $error);
                    $this->dispatcher->dispatch($failureEvent);

                    if (!$failureEvent->shouldIgnore()) {
                        throw $error;
                    }
                }
            }
        }
    }
}

then configured with

        \Swoole\Timer::tick(1000, function () {
            $this->scheduler->run();
        });

and as a result, you are able to provide schedules with any frequency:

        return (new Schedule())
            ->stateful($this->cache)
            ->with(
                RecurringMessage::every(
                    3,
                    new DummyCommand()
                )
            )
            ;

Image

mleczakm avatar May 22 '25 14:05 mleczakm