Package: temporal-scheduler
TemporalSchedulerLightweight temporal scheduler for thread synchronization and code executionThe package implements a lightweight and simple to use temporal scheduler for callable
tasks (functions) and events. It started as a part of my project, but over time I found it
useful enough to turn the code into a standalone package.Callable actions are executed in own threads, so an exception during execution will
not interfere with operation of the scheduler.Installationpip install temporal_schedulerUsagePlease seeexamplesdirectoryRequirementsPackage depends onSortedDictLicenseGPLv3Theory of operationTimingThe scheduler relies onthreading.Eventfor precise timing. When the next task for execution
arrives, the event enters wait with timeout set to the interval between the current time and
the execution time.
If no tasks are scheduled for execution, the event enters a wait with no timeout.If scheduling a new arriving task requires modification of the current head task
(to be executed next), e.g. when a task arrives to an empty queue, requested
execution time is earlier than the current head task, or the head list becomes empty,
the head task is adjusted, and the event is triggered.If the event is triggered by a timeout, the worker thread processes the head task list.
For each callable task on the list a new execution thread is executed and started.For event tasks the event is triggered.When the event is triggered, the head task list is already populated by the scheduling
method, and the working thread doesn't need to take any action. The new timeout is
computed, and the thread enters sleep.Task schedulingThe scheduler maintains two lists of tasks:Tasks ready for executionis a list of tasks which will be executed when the scheduler
thread wakes up.Task queueis a set of tasks with execution times following
the execution time of the take on the "ready" list.The task queue is organized as asorted dictionary. The data structure behaves as
a regular dictionary in the sense that it can be treated as a key-value store,
and a sorted list, where the keys have defined order.With timestamps used as key values, it allows to easily organize and manage the
scheduled tasks.Because more than task can be scheduled for execution with the same timestamp,
each entry in the queue is organized as a list.Most of the scheduling work is performed by theschedule_eventmethod of the internal classTemporalScheduler.TimerThreadclass. TheTimerThread.schedule_taskmethod
is just a wrapper, which performs sanity checks and input data normalization.Inschedule_eventthe incoming request is processed using the following decision tree:|
| time is in the past
+--------------------------------------------------------- (1).
|
| Nothing on the scheduler list
| self.current_time is None
+--------------------------------------------------------- (2).
|
| self.current_time > time
| (event earlier than currently scheduled)
+--------------------------------------------------------- (3).
|
| time not in self.events (new time point)
+--------------------------------------------------------- (4).
|
|                              callback on the list
| time == self.current_time  +---------------------------- (5).
| upcoming execution time    |
+----------------------------+
|                            | callback not on the list
|                            +---------------------------- (6).
|
|                              callback on the list
|                            +---------------------------- (7)
| time in self.events        |
+----------------------------+
|                            | callback not on the list
|                            +---------------------------- (8)
|Case 1: time is in the pastThis case is handled by the wrapper method.
The call fails withValueErrorexception.Case 2: Nothing on the scheduler list, self.current_time is NoneIn this case the task is immediately added to the list of tasks ready for execution,
and the scheduler thread is woken up to schedule execution.New task ID is returned.Case 3: The event is earlier than currently scheduledThe tasks from list of tasks ready for execution are moved back to the queue,
the arriving task is placed on the ready list, and the scheduler thread is woken up to
adjust its schedule.New task ID is returned.Case 4: time not inself.events(new time point)The arriving task is not ready for placement on the execution list and is added to the scheduler queue.
In the queue is no entry for its execution time, and new entry is created.New task ID is returned.Case 5: Arriving task is a duplicate of a task on the list of tasks ready for executionArriving task is a duplicate of an existing entry of a task on the execution list.The ID of the task in the system is returned.Case 6: Arriving task is not a duplicate and ready for executionArriving task is not a duplicate. The task is added to the execution list.
There is no need to wake up the scheduler thread.New task ID is returned.Case 7: Arriving task has timestamp already present in the scheduler queue, and is a duplicateArriving task is a duplicate of an existing entry of a task in the scheduler queue.The ID of the task in the system is returned.Case 8: Arriving task has timestamp already present in the scheduler queue, and is not a duplicateThe timestamp of the arriving task is already in the queue, but it's not a duplicate.
The task is added to the list of tasks associated with this timestamp.
New task ID is returned.Detecting duplicatesThe comparison of tasks takes into account the timestamp, callable, and the arguments.Comparison of tuples (for positional args) and dictionaries (for names args) is used to determine if the sets identical.
No deep comparison is performed. The task is determined a duplicate if all 4 comparisons return positive results.Periodic tasksPeriodic takes are implemented by re-scheduling them at the execution time.
When the scheduler thread processes a task withperiodicflag set, the task is resubmitted to the scheduler.
The execution time is calculated by using the adding the task period to the current execution time.The new task is scheduled with the same task ID as the currently executing task.
Otherwise, it would be impossible to cancel a periodic task after first execution.Processing ofthreading.EventtasksTheEventclass of tasks has been introduced as an optimized method of synchronizing threads.
A task can be a function, which triggers an event. However, this is a very simple situation, and creating a new worker
thread and calling a callback function would be extremely wasteful.
This becomes very important when the events are triggered with high frequency.AuthorTemporalSchedulerwas written byJacek Radzikowski <jacek.radzikowski@gmail.com>.