Skip to content

[Scheduler] Allow modifying the schedule at runtime and recalculate heap #51553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 26, 2023

Conversation

Jeroeny
Copy link
Contributor

@Jeroeny Jeroeny commented Sep 4, 2023

Q A
Branch? 6.4
Bug fix? no
New feature? yes
Deprecations? no
Tickets Fix #51206
License MIT

Schedules are created statically by a ScheduleProviderInterface. This is nice if you have a small app with some predetermined scheduled actions. But if you want to schedule business logic dynamically, e.g. have admins Crud a schedule in the DB, then the schedule needs to be updateable at runtime. This also allows for the app to re-add schedules that have returned null as nextRunDate in the past (making them unscheduled) and to remove schedules.

Usage example: reading the schedule from a DB repository, adding messages based on an custom event:

class MySchedule implements ScheduleProviderInterface
{
    private ?Schedule $schedule = null;

    public function __construct(private RecurrenceRepository $repository)
    {
    }

    public function getSchedule(): Schedule
    {
        return $this->schedule ??= Schedule::with(...$this->repository->all());
    }

    #[AsEventListener(ScheduledMessageAdded::class)]
    public function onScheduleAdded(ScheduledMessageAdded $event): void
    {
        $this->schedule->add($event->getMessage());
    }
}

Or in the context of Messenger, if some periodic batch task has no more work to do, the recurring message can be removed:

class MySchedule implements ScheduleProviderInterface
{
    private ?Schedule $schedule = null;

    public function getSchedule(): Schedule
    {
        return $this->schedule ??= Schedule::with(RecurringMessage::every(60, new BatchGenerateFiles()));
    }
    
    public function clear()
    {
        $this->schedule->clear();
    }
}

class MyHandler 
{
    #[AsMessageHandler]
    public function handle(BatchGenerateFiles $message)
    {
        // <-- handle message code -->

        if ($noMoreFilesToGenerate) {
            $this->mySchedule->clear();
        }
    }
}

@kbond
Copy link
Member

kbond commented Sep 7, 2023

Interesting, is this a companion PR for #51542?

@Jeroeny
Copy link
Contributor Author

Jeroeny commented Sep 7, 2023

@kbond they don't depend on each other. But when both of them are merged, it enables a lot of capabilities for the Scheduler.

Copy link
Member

@fabpot fabpot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some usage example in the PR description?

@@ -10,6 +10,7 @@ CHANGELOG
* Add `AbstractTriggerDecorator`
* Make `ScheduledStamp` "send-able"
* Add `ScheduledStamp` to `RedispatchMessage`
* Allow modifying the Schedule at runtime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Allow modifying the Schedule at runtime
* Allow modifying Schedule instances at runtime

@fabpot
Copy link
Member

fabpot commented Sep 26, 2023

Thank you @Jeroeny.

@fabpot fabpot merged commit cb0974c into symfony:6.4 Sep 26, 2023
@Jeroeny Jeroeny deleted the recalc branch September 27, 2023 12:52
fabpot added a commit that referenced this pull request Oct 16, 2023
This PR was merged into the 6.4 branch.

Discussion
----------

[Scheduler] pre_run and post_run events

| Q             | A
| ------------- | ---
| Branch?       | 6.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | #49803 (comment)
| License       | MIT

Based on #49803 `@kbond`  and taking into account #51553

The aim of this PR is to be able to act on the accumulated messages 'if something happens' and to be able to recalculate the heap via events (currently pre_run and post_run).
The aim is to have access to
- the  the schedule and therefore add/cancel a certain type of message.
- MessageContexte (e.g. access the id)
- The message itself

This PR would complement `@Jeroeny` #51553 PR by enabling action via events.

Commits
-------

20fd21a [Scheduler] add PRE_RUN and POST_RUN events
This was referenced Oct 21, 2023
@Sophikitis
Copy link

Hi !

Do you have any examples of use?
I'm trying to implement this, to dynamically retrieving frequencies and modifying the behavior with a custom event.
But this->schedule is always null in the listener function.
When I retrieve the instance from $this->getSchedule() I get an instance but when I try to delete a message or add one, nothing happens.

And as far as I understand, there's no documentation for this feature.
I'll try my luck here.
Thanks in advance

@Hanmac
Copy link
Contributor

Hanmac commented Apr 16, 2024

@fabpot I'm also looking for an example, currently i uses this Doctrine Query to build the Schedule

    public function getSchedule(): Schedule
    {
        $result = new Schedule();

        $repo = $this->manager->getRepository(Terminal::class);
        $q = $repo->createQueryBuilder('t');

        $q->where('t.status = true AND t.automatic = true');

        foreach ($q->getQuery()->getResult() as $r) {
            /**
             * @var Terminal $r
             */
            $result->add(RecurringMessage::cron(
                $r->getCronExpr(),
                new KassenschlussTask($r->getId()),
                $this->timezoneDetector->getTimezone())
            );
        }
        return $result;
    }

because each object might have its own CronExpr, each of them needs its own RecurringMessage

and how do i handle if they get disabled?

@Hanmac
Copy link
Contributor

Hanmac commented Apr 17, 2024

@Sophikitis while maybe not the best way, i found StopWorkersCommand

$cacheItem = $this->restartSignalCachePool->getItem(StopWorkerOnRestartSignalListener::RESTART_REQUESTED_TIMESTAMP_KEY);
$cacheItem->set(microtime(true));
$this->restartSignalCachePool->save($cacheItem);

i used [AsEntityListener] on my DB Entities, and then on Events::postRemove, Events::postUpdate, Events::postPersist i stop the messenger worker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Scheduler] "dynamic" schedules
6 participants