magic.lambda.scheduler
14.0.5
See the version list below for details.
dotnet add package magic.lambda.scheduler --version 14.0.5
NuGet\Install-Package magic.lambda.scheduler -Version 14.0.5
<PackageReference Include="magic.lambda.scheduler" Version="14.0.5" />
paket add magic.lambda.scheduler --version 14.0.5
#r "nuget: magic.lambda.scheduler, 14.0.5"
// Install magic.lambda.scheduler as a Cake Addin #addin nuget:?package=magic.lambda.scheduler&version=14.0.5 // Install magic.lambda.scheduler as a Cake Tool #tool nuget:?package=magic.lambda.scheduler&version=14.0.5
Scheduling and persisting tasks from Hyperlambda
This project gives you the ability to create persisted and scheduled Hyperlambda tasks for Magic. More specifically it provides the following slots.
- [tasks.create] - Creates a new task
- [tasks.get] - Returns an existing task
- [tasks.list] - Lists all tasks
- [tasks.update] - Updates an existing task
- [tasks.count] - Counts tasks in system
- [tasks.execute] - Executes an existing task
- [tasks.delete] - Deletes a task
- [tasks.schedule] - Schedules an existing task
- [tasks.schedule.delete] - Deletes an existing schedule
- [tasks.scheduler.start] - Starts the task scheduler
Creating a task
To create and persist a task you can use something such as the following.
tasks.create:foo-bar-task-1
.lambda
// Your task's lambda object goes here
log.info:Executing foo-bar-task-1
The name or ID of your task in the above example becomes "foo-bar-task-1", and the task can be referenced later using this name. The name must be unique, otherwise an exception will be thrown. A task can also optionally have a [description] argument, which is a humanly readable description, describing what your task does. Below is an example.
tasks.create:foo-bar-task-2
description:This task will do a little bit of foo and some bar afterwards.
.lambda
log.info:Executing foo-bar-task-2
Notice - Your task's [id] argument, can only contain alpha numeric characters,
a-z, 0-9 - In addition to the following special characters; .
, -
and _
.
Convenience methods
When you create a task, you can also optionally schedule it simultaneously, by providing any amount of [due] dates, and/or [repeats] patterns, which will create and schedule the task at the same time. Below is an example.
tasks.create:foo-bar-task-3
due:date:"2025-01-01T23:59:27"
repeats:5.seconds
repeats:3.hours
due:date:"2030-01-01T23:59:27"
.lambda
log.info:Executing foo-bar-task-2
The above schedules your task for being executed once in the year of 2025, another time in the year of 2030, in addition to once every 3 hours and once every 5 seconds. This document will describe in details how schedules works further down.
You can also update an existing task by using the [tasks.update] slot. This slot allows you to update a task's description and its Hyperlambda, but you cannot associate schedules with your task using this slot. If you've already created your task and you need to (re) schedule it, you'll need to combine the slots [tasks.schedule] and [tasks.schedule.delete] together. Below is an example of first creating a task for then to update it. Any existing schedules you've already associated with your task as you update it will not be changed.
tasks.create:foo-bar-task-4
.lambda
log.info:Executing foo-bar-task-1
tasks.update:foo-bar-task-4
description:This is the foo bar task
.lambda
log.info:Another log entry now!
Executing a task
You can explicitly execute a persisted task by invoking [tasks.execute] and pass in the ID of your task. Below is an example, that assumes you have created the above "foo-bar-task-4" task first.
tasks.execute:foo-bar-task-4
Notice, if you try to execute a non-existing task, an exception will be thrown.
Deleting a task
Use the [tasks.delete] signal to delete a task. This will also delete all future schedules for your task and automatically dispose any timers associated with the task. An example can be found below.
tasks.delete:task-id
Besides from the task ID, the delete task signal doesn't take any arguments.
Inspecting a task
To inspect a task you can use the following.
tasks.get:task-id
The above will return the Hyperlambda for your task, in addition to your task's description. If you add
a [schedules] argument and set its value to boolean true
, this slot will also return all schedules
associated with the task.
tasks.get:task-id
schedules:true
Listing tasks
To list tasks, you can use the [tasks.list] signal. This slot optionally handles an [offset] and a [limit] argument, allowing you to page, which might be useful if you have a lot of tasks in your system. If no [limit] is specified, this signal will only return the first 10 tasks, including the task's Hyperlambda, but not its repetition pattern(s), or due date(s). Below is an example.
tasks.list
offset:20
limit:10
Persisting tasks
All tasks are persisted into your magic
database, either in MySQL, PostgreSQL, or Microsoft SQL Server.
Which implies that even if the server is stopped, all scheduled tasks and persisted tasks will automatically
load up again, and be available and re-scheduled as the server is restarted. This might imply that
all tasks in the past are immediately executed, which is important for you to understand, since any tasks
with a due date in the past, are executed immediately as the server restarts again.
Tasks are by default persisted into your tasks
table, and schedules are persisted into your
task_due
table.
Workflows and Magic Tasks
The above allows you to persist a "function invocation" for later to execute it, once some specific condition occurs - Effectively giving you the most important features from Microsoft Workflow Foundation, without the ridiculous XML and WYSIWYG features - In addition to that this also is a .Net Core library, contrary to MWF that only works for the full .Net Framework. The Hyperlambda task scheduler is also probably at least somewhere between 200 and 400 times faster than MWF, due to not needing any reflection.
Scheduling tasks
If you want to create a scheduled task, you can choose to have the task executed once in the future, at a specified date and time, by invoking [tasks.schedule], and reference your task after it's been created, passing in a [due] argument being a date and time in the future for when you want to execute your task.
tasks.create:foo-bar-task-3
.lambda
log.info:Executing foo-bar-task-3
tasks.schedule:foo-bar-task-3
due:date:"2025-12-24T17:00"
The above [due] argument is a UTC date and time in the future for when you want your task to be scheduled for execution. After the task has been executed, it will never execute again, unless you manually execute it, or invoke [tasks.schedule] again.
Notice - You cannot create a task with a due date being in the past, and all dates are assumed to be in the UTC timezone. The unique ID of the schedule created is returned when you explicitly schedule a task using the [tasks.schedule] slot. If you add schedules during invocations to [tasks.create] though, no schedule IDs are returned, but you can still retrieve all schedules by invoking [tasks.get] and passing in [schedules] to have the slot return all schedules for a specific task.
Repeating tasks
There are 3 basic [repeats] patterns for the Magic Lambda Scheduler, in addition to that you can extend
it with your own parametrized repeating IRepetitionPattern
implementation. The built in repetition patterns,
are as follows.
x.units
- Units can be one of "seconds", "minutes", "hours", "days", "weeks" or "months" - Andx
can be any integer value.MM.dd.HH.mm.ss
- Where the entities are in sequence months, days in months, hours, minutes and seconds.ww.HH.mm.ss
- Where the entities are weekdays, hour, minute and second.
Notice, MM, dd, and ww can have double asterix (**) as their values, implying "whatever value". MM, dd and ww can also have multiple values, separated by the pipe character (|), to provide multiple values for these types. See examples of this further below in this documentation.
Intervals
Evaluating your task every second/minute/hour/etc can be done by using something such as the following.
tasks.create:task-id-qwqw
.lambda
log.info:Executing repeating task
tasks.schedule:task-id-qwqw
repeats:50.seconds
The above will evaluate your task every 50 second. The above "seconds" can be exchanged with "minutes", "hours", "days", "weeks" or "months". Notice, this allows you to have very large values, to have tasks that are repeating very rarely, such as the following illustrates.
tasks.create:task-id-qwerty123
.lambda
log.info:Executing seldomly repeating task once every 10 year
tasks.schedule:task-id-qwerty123
repeats:3650.days
The above task will only be evaluated every 3650 days, which becomes once every 10 years. Below is a list of all valid units types.
- seconds
- minutes
- hours
- days
- weeks
- months
Periodically scheduled tasks
To create a task that is executed on the first day of every month, at 5PM, you can use the following repetition pattern.
tasks.create:task-id-xyx
.lambda
log.info:It is the 1st of the month, any month, and the time is 5AM at night.
tasks.schedule:task-id-xyx
repeats:**.01.05.00.00
Hours must be supplied as "military hours", implying from 00:00 to 23:59, where for instance 22 equals 10PM UTC time. Also notice how we provided a double asterix (**) for the month parts, implying "all months". We could also have provided multiple days, and/or months, such as the following illustrates. The Hyperlambda below will create a task that is executed in January and February, but only on the 5th and 15th of these months.
tasks.create:task-id
.lambda
log.info:It is the 5th or the 15th of January or February, and the time is 5AM at night.
tasks.schedule:task-id
repeats:01|02.5|15.05.00.00
By using the double asterix for month and day of month, you can create a task that is executed every day, at some specific time of the day (UTC time). Below is an example.
tasks.create:task-id-555
.lambda
log.info:It is the 10PM now.
tasks.schedule:task-id-555
repeats:**.**.22.00.00
Weekdays pattern
If you use the weekdays pattern, you can create any combinations of weekdays, allowing you to supply multiple weekdays in a single repetition pattern. Below is an exhaustive list of all possible weekdays.
- Monday
- Tuesday
- Wednesday
- Thursday
- Friday
- Saturday
- Sunday
To evaluate a task every Saturday and Sunday for instance, you can use saturday|sunday
as your weekday.
Below is an example. Notice, weekdays are case insensitive.
tasks.create:task-id-567
.lambda
log.info:It is Saturday or Sunday, and the time is 22PM.
tasks.schedule:task-id-567
repeats:saturday|SUNDAY.22.00.00
You can also provide a double asterix (**) for the weekdays pattern, implying "all days of the week". Notice how weekdays are case-insensitive as illustrated above.
Creating your own repetition pattern
In addition to the above 3 types of repetition patterns, you can also create your own repetition pattern type,
by implementing the IRepetitionPattern
interface on one of your own types, and registering your type create function
by using the PatternFactory.AddExtensionPattern
method. If you do, you'll have to reference your repetition
pattern type using "ext:", combined with its resolver key. Implying if you register your IRepetitionPattern
type such
that it resolves using for instance "my-pattern" as its key, you'll have to use _"ext:my-pattern:args"
to reference it later, as you wish to create an instance of your custom pattern type. The "args" part
are any arguments supplied to your pattern during creation. Below is an example that creates a custom
repetition pattern.
private class ExtPattern : IRepetitionPattern
{
readonly string _args;
public string Value => "ext:custom-pattern:" + _args;
public ExtPattern(string args)
{
_args = args;
}
public DateTime Next()
{
return new DateTime(2030, 11, 11, 11, 11, 57);
}
}
The above IRepetitionPattern
will statically resolve to the 11th of November 2030, at 23:11:57. But
the idea is that the args
supplied during creation, can be used to parametrize your pattern, and
calculate the next due date for your schedule.
After you have declared your custom IRepetitionPattern
type, you'll need to inform the PatternFactory
class
that you want to use the above class, and resolve it using some specific key from your schedules.
This is accomplished using something resembling the following code.
PatternFactory.AddExtensionPattern(
"custom-type",
str =>
{
return new ExtPattern(str);
});
The above code will ensure that every time you use "custom-type" as a repetition pattern type,
the create function above will be invoked, allowing you to create and decorate an instance of your
custom IRepetitionPattern
type. The str
argument to your above create function, will be everything
after the ext:custom-type:
parts, when creating an instance of your pattern.
To use the above pattern in your own code, you can use something such as the following.
tasks.create:custom-repetition-pattern
.lambda
log.info:Executing custom-repetition-pattern
tasks.schedule:custom-repetition-pattern
repeats:"ext:custom-pattern:some-arguments-here"
In the above [repeat] argument, the ext
parts informs the scheduler that you want to use a
custom repetition pattern, the custom-pattern
parts resolves to your IRepetitionPattern
create function,
and the "some-arguments-here" parts will be passed into your above ExtPattern
constructor allowing
you to parametrize your pattern any ways you see fit.
[tasks.scheduler.start]
Notice, this slot is not intended for being directly invoked by your code, but internally used by Magic after the system has been setup. But if you intend to significantly change the internals of Magic, the way it works is that it requires an integer number between 1 and 100, that sets the maximum number of concurrently executed tasks on the scheduler parts, and also schedules all tasks persisted into your database.
Typically you would never directly invoke this slot yourself, but rather rely upon Magic's middleware
to automatically take care of starting the scheduler for you. The scheduler is automatically started
as Magic starts. If you want to change the number of concurrent threads in your particular Magic installation,
this can be achieved by changing the magic:scheduler:max-threads
configuration setting and restarting
your Magic backend. The default number of concurrently executed tasks are 8, unless explicitly changed
through your configuration settings. This implies that only 8 tasks will be scheduled to execute in
parallel at the same time, resulting in any tasks beyond that will be queued up and have to wait for
another task to finish before it's allowed to execute. This prevents the task scheduler from exhausting
your backend server due to too many threads executing at the same time.
If you have a lot of tasks that are scheduled to repeat often, and all your tasks are doing a lot of IO, you might want to increase the number of concurrently executed tasks to a higher value, since as your tasks are waiting for IO, the thread they're executing on will be released back to the operating system, implying the task will not block a thread as it waits for IO.
Internals
One System.Threading.Timer
object will be created for each due date/repetition pattern you have, and kept
in memory of your Magic server, which doesn't lend itself to thousands of schedules for obvious reasons - But
due to the mechanics of how these are implemented at the system level, this is still highly scalable for most
solutions.
When a repeating task has finished executed, the next due date for the task's execution will be calculated using its interval pattern - Implying that if you use a 5 second pattern, the schedule for its next execution, will be calculated 5 seconds from the time the task finished executing, which might not necessarily imply that your tasks are executed exactly every 5 seconds, depending upon how much time your task requires to execute. The interval pattern declares how many units to count to before executing the task again, from when the task finished executing.
Project website
The source code for this repository can be found at github.com/polterguy/magic.lambda.scheduler, and you can provide feedback, provide bug reports, etc at the same place.
Quality gates
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- magic.data.common (>= 14.0.5)
- magic.lambda.logging (>= 14.0.5)
- magic.node.extensions (>= 14.0.5)
- magic.signals.contracts (>= 14.0.5)
- Microsoft.Extensions.Configuration (>= 6.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on magic.lambda.scheduler:
Package | Downloads |
---|---|
magic.library
Helper project for Magic to wire up everything easily by simply adding one package, and invoking two simple methods. When using Magic, this is (probably) the only package you should actually add, since this package pulls in everything else you'll need automatically, and wires up everything sanely by default. To use package go to https://polterguy.github.io |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
17.2.0 | 440 | 1/22/2024 |
17.1.7 | 196 | 1/12/2024 |
17.1.6 | 158 | 1/11/2024 |
17.1.5 | 180 | 1/5/2024 |
17.0.1 | 220 | 1/1/2024 |
17.0.0 | 358 | 12/14/2023 |
16.11.5 | 340 | 11/12/2023 |
16.9.0 | 316 | 10/9/2023 |
16.7.0 | 580 | 7/11/2023 |
16.4.1 | 372 | 7/2/2023 |
16.4.0 | 401 | 6/22/2023 |
16.3.1 | 367 | 6/7/2023 |
16.3.0 | 342 | 5/28/2023 |
16.1.9 | 622 | 4/30/2023 |
16.1.1 | 351 | 4/20/2023 |
15.10.11 | 393 | 4/13/2023 |
15.9.1 | 612 | 3/27/2023 |
15.9.0 | 458 | 3/24/2023 |
15.8.2 | 490 | 3/20/2023 |
15.7.0 | 398 | 3/6/2023 |
15.5.0 | 1,595 | 1/28/2023 |
15.2.0 | 689 | 1/18/2023 |
15.1.0 | 1,143 | 12/28/2022 |
14.5.7 | 710 | 12/13/2022 |
14.5.5 | 821 | 12/6/2022 |
14.5.1 | 675 | 11/23/2022 |
14.5.0 | 614 | 11/18/2022 |
14.4.5 | 701 | 10/22/2022 |
14.4.1 | 782 | 10/22/2022 |
14.4.0 | 671 | 10/17/2022 |
14.3.1 | 1,261 | 9/12/2022 |
14.3.0 | 661 | 9/10/2022 |
14.1.3 | 942 | 8/7/2022 |
14.1.2 | 678 | 8/7/2022 |
14.1.1 | 641 | 8/7/2022 |
14.0.14 | 708 | 7/26/2022 |
14.0.12 | 685 | 7/24/2022 |
14.0.11 | 666 | 7/23/2022 |
14.0.10 | 651 | 7/23/2022 |
14.0.9 | 662 | 7/23/2022 |
14.0.8 | 721 | 7/17/2022 |
14.0.5 | 780 | 7/11/2022 |
14.0.4 | 797 | 7/6/2022 |
14.0.3 | 726 | 7/2/2022 |
14.0.2 | 671 | 7/2/2022 |
14.0.0 | 673 | 6/25/2022 |
13.4.19 | 1,538 | 6/8/2022 |
13.4.0 | 1,212 | 5/31/2022 |
13.3.4 | 1,449 | 5/9/2022 |
13.3.1 | 752 | 5/1/2022 |
13.3.0 | 689 | 5/1/2022 |
13.2.0 | 1,161 | 4/21/2022 |
13.1.0 | 1,023 | 4/7/2022 |
13.0.0 | 748 | 4/5/2022 |
11.0.5 | 1,423 | 3/2/2022 |
11.0.4 | 750 | 2/22/2022 |
11.0.3 | 784 | 2/9/2022 |
11.0.2 | 808 | 2/6/2022 |
11.0.1 | 769 | 2/5/2022 |
10.0.21 | 759 | 1/28/2022 |
10.0.20 | 774 | 1/27/2022 |
10.0.19 | 758 | 1/23/2022 |
10.0.18 | 754 | 1/17/2022 |
10.0.16 | 751 | 1/2/2022 |
10.0.15 | 500 | 12/31/2021 |
10.0.14 | 546 | 12/28/2021 |
10.0.7 | 1,434 | 12/22/2021 |
10.0.5 | 741 | 12/18/2021 |
9.9.9 | 1,654 | 11/29/2021 |
9.9.3 | 909 | 11/9/2021 |
9.9.2 | 611 | 11/4/2021 |
9.9.0 | 705 | 10/30/2021 |
9.8.9 | 702 | 10/29/2021 |
9.8.7 | 666 | 10/27/2021 |
9.8.6 | 632 | 10/27/2021 |
9.8.5 | 684 | 10/26/2021 |
9.8.0 | 1,353 | 10/20/2021 |
9.7.9 | 626 | 10/19/2021 |
9.7.5 | 1,479 | 10/14/2021 |
9.7.0 | 854 | 10/9/2021 |
9.6.6 | 1,224 | 8/14/2021 |
9.2.0 | 6,277 | 5/26/2021 |
9.1.4 | 1,265 | 4/21/2021 |
9.1.0 | 1,052 | 4/14/2021 |
9.0.0 | 841 | 4/5/2021 |
8.9.9 | 1,024 | 3/30/2021 |
8.9.3 | 1,530 | 3/19/2021 |
8.9.2 | 1,010 | 1/29/2021 |
8.9.1 | 1,026 | 1/24/2021 |
8.9.0 | 1,076 | 1/22/2021 |
8.6.9 | 2,973 | 11/8/2020 |
8.6.6 | 1,997 | 11/2/2020 |
8.6.0 | 4,029 | 10/28/2020 |
8.5.0 | 1,915 | 10/23/2020 |
8.4.0 | 5,614 | 10/13/2020 |
8.3.2 | 1,969 | 10/8/2020 |
8.3.1 | 1,254 | 10/5/2020 |
8.3.0 | 1,213 | 10/3/2020 |
8.2.2 | 2,033 | 9/26/2020 |
8.2.1 | 1,360 | 9/25/2020 |
8.2.0 | 1,383 | 9/25/2020 |
8.1.17 | 6,693 | 9/13/2020 |
8.1.16 | 605 | 9/13/2020 |
8.1.15 | 1,916 | 9/12/2020 |
8.1.11 | 2,510 | 9/11/2020 |
8.1.10 | 1,308 | 9/6/2020 |
8.1.9 | 1,323 | 9/3/2020 |
8.1.8 | 1,267 | 9/2/2020 |
8.1.7 | 1,192 | 8/28/2020 |
8.1.4 | 1,227 | 8/25/2020 |
8.1.3 | 1,289 | 8/18/2020 |
8.1.2 | 1,192 | 8/16/2020 |
8.1.1 | 1,278 | 8/15/2020 |
8.0.2 | 495 | 8/11/2020 |
8.0.1 | 2,700 | 8/7/2020 |
8.0.0 | 1,225 | 8/7/2020 |
7.0.1 | 1,390 | 6/28/2020 |
7.0.0 | 1,234 | 6/28/2020 |
5.0.0 | 7,472 | 2/25/2020 |
4.0.4 | 7,960 | 1/27/2020 |
4.0.3 | 1,287 | 1/27/2020 |
4.0.2 | 1,397 | 1/16/2020 |
4.0.1 | 1,378 | 1/11/2020 |
4.0.0 | 1,343 | 1/5/2020 |
3.1.3 | 4,209 | 12/4/2019 |
3.1.2 | 1,405 | 11/30/2019 |
3.1.1 | 1,486 | 11/26/2019 |
3.1.0 | 605 | 11/19/2019 |