Repository navigation
1.6
Pre-releaseScheduler Overhaul
The scheduler was the first Coravel feature - which was just a prototype built one day while I had some time to kill while my kids were asleep ;) I just wanted to see if it was possible for me to get a really basic version coded.
As the dotnet community has indicated there is some real interest in this project, the scheduler has naturally been due for a re-write - if it is to be used in actual projects.
Internal Scheduler Changes (Briefly)
- Internally - to decide whether a task was "due" - the time of a previously executed task was stored and then compared every 30 seconds against "Now" in order to see if the task should be executed again (yes - a naive implementation. This is why it needed a re-write!)
There are issues with this such as:- Initially, every task has to run at startup to get a value for the "previous executed time."
- Won't work when building a distributed scheduler (maybe a future feature)
- How do we handle intervals like "every month"? Once you restart or re-deploy your app the counter resets! So it's really "every month only if you haven't re-deployed for a month".
Unit Tests
These changes have also increased the number of unit tests in Coravel from over 100 to almost 600! Something that should help the community feel like Coravel is a reliable library to use.
Cron
Now the scheduler works like cron. Every minute the scheduled task checks the time and uses a cron expression to determine if it should run.
A side benefit is that we now have a Cron method available for scheduling tasks.
I battled over whether or not to implement this myself. In the end, I decided that it was better not to add another dependency (especially since I couldn't find a library that was considered really trustworthy in this area).
I don't want to re-invent the wheel - I'm all for using third-party / NuGet packages others have made. But I want to ensure that whatever I do use has already been considered a trustworthy and reliable package (for example, I decided to include MailKit for the Mailer feature. MailKit is considered by everyone to be the .Net Core SMTP mailing go-to)
Threading Enhancements
The collection of scheduled tasks were simply held in a List. The scheduler allows scheduling tasks dynamically - while this isn't necessarily recommended (since these scheduled tasks will disappear after the application is closed) - it is possible. There may be some use-cases for this.
Anyways, using a List in multi-threaded use cases is bad. So now the scheduler is using a ConcurrentBag (among other internal enhancements such as using Task.WhenAll, etc.).
I'm still considering other parallel processing enhancements (to fire scheduled tasks in true parallel), but for now, the changes are needed and moving the right direction.
Schedule Configuration
Previously, all the scheduler config was done in ConfigureServices. This has changed to allow additional features and an implementation that uses .Net Core's DI container more properly.
In ConfigureServices:
services.AddScheduler()Then in Configure - all the existing configuration methods can be used:
app.UseScheduler(scheduler =>
{
scheduler.Schedule(
() => Console.WriteLine("Every minute during the week.")
)
.EveryMinute();
.Weekday();
});Queue Configuration Changes
Just like the point above, the queue had the same changes made:
In ConfigureServices:
services.AddQueue();In Configure:
app
.ConfigureQueue()
.OnError(e =>
{
//.... handle the error
});Other New Features
Invocables
Coravel now exposes a concept of "Invocables". Basically, it's a class that can be "Invoked" (i.e. executed) asynchronously.
This allows us to create specific classes to do a certain "Job" in our system. These "jobs" can then be either scheduled or queued using a super simple syntax.
this._queue.QueueInvocable<GrabDataFromApiAndPutInDBInvocable>();
// or
scheduler
.Schedule<GrabDataFromApiAndPutInDBInvocable>()
.EveryTenMinutes();Cli: Generate Invocable
New command: coravel invocable new [yourInvocable] will generate a clean invocable all ready to go.
Prevent Overlap
Sometimes you may have long running tasks (longer than 1 minute?). The normal behaviour of the scheduler is to simply fire off a task if it is due. What if you don't want a "due" task to execute if the previously executed one is still running?
scheduler
.Schedule<SomeInvocable>()
.EveryMinute()
.PreventOverlapping("SomeInvocable");