ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

Background Scheduler with Periodic Tasks

Crystal

Goal -- WPM

Ready
Exercise Algorithm Area
1require "time"
2
3class ScheduledTask
4getter name : String
5getter interval_seconds : Float64
6getter task : Proc(Nil)
7getter next_run_time : Time
8
9def initialize(@name : String, @interval_seconds : Float64, @task : Proc(Nil))
10@next_run_time = Time.monotonic + @interval_seconds
11end
12
13def run
14begin
15@task.call
16puts "Task '#{@name}' executed successfully at #{Time.monotonic}."
17rescue ex
18puts "Error executing task '#{@name}': #{ex.message}"
19end
20end
21
22def reschedule
23@next_run_time = Time.monotonic + @interval_seconds
24end
25end
26
27class BackgroundScheduler
28getter tasks : Array(ScheduledTask)
29getter running : Bool
30
31def initialize
32@tasks = Array(ScheduledTask).new
33@running = false
34@scheduler_thread = nil
35end
36
37def add_task(name : String, interval_seconds : Float64, task : Proc(Nil))
38@tasks << ScheduledTask.new(name, interval_seconds, task)
39end
40
41def start
42return if @running
43@running = true
44@scheduler_thread = spawn do
45scheduler_loop
46end
47puts "Scheduler started."
48end
49
50def stop
51return unless @running
52@running = false
53@scheduler_thread.join if @scheduler_thread
54puts "Scheduler stopped."
55end
56
57private def scheduler_loop
58while @running
59current_time = Time.monotonic
60tasks_to_run = [] of ScheduledTask
61
62@tasks.each do |task|
63if task.next_run_time <= current_time
64tasks_to_run << task
65end
66end
67
68tasks_to_run.each do |task|
69spawn { task.run }
70task.reschedule
71end
72
73# Determine sleep duration to avoid busy-waiting
74min_next_run = Float64::INFINITY
75@tasks.each do |task|
76min_next_run = [min_next_run, task.next_run_time.to_f].min
77end
78
79sleep_duration = if min_next_run == Float64::INFINITY
801.0 # Default sleep if no tasks
81else
82sleep_time = min_next_run - Time.monotonic
83sleep_time > 0 ? sleep_time : 0.001 # Ensure positive sleep
84end
85
86sleep(sleep_duration)
87end
88end
89end
90
91# Example Usage:
92# scheduler = BackgroundScheduler.new
93
94# scheduler.add_task("heartbeat", 5.0) do
95# puts "Heartbeat: #{Time.monotonic}"
96# end
97
98# scheduler.add_task("cleanup", 10.0) do
99# puts "Performing cleanup..."
100# # Simulate an error
101# # raise "Cleanup failed!"
102# end
103
104# scheduler.start
105
106# # Let the scheduler run for a while
107# sleep(20)
108
109# scheduler.stop
Algorithm description viewbox

Background Scheduler with Periodic Tasks

Algorithm description:

This Crystal code implements a `BackgroundScheduler` that executes tasks at specified intervals. It maintains a list of `ScheduledTask` objects, each with a name, interval, and a `Proc` to execute. The scheduler loop continuously checks for tasks whose next run time has passed, spawns new fibers to run them, and reschedules them for their next execution. This is ideal for background processes like health checks, periodic data synchronization, or cleanup jobs.

Algorithm explanation:

The `BackgroundScheduler` uses a main loop that runs in a separate fiber. In each iteration, it identifies tasks due for execution, spawns new fibers to run them concurrently, and updates their `next_run_time`. The loop then calculates the minimum time until the next task is due and sleeps for that duration, minimizing CPU usage. Error handling within individual tasks is done via `begin...rescue` in `ScheduledTask#run`, preventing a single task failure from crashing the scheduler. Time complexity of the scheduler loop is O(T) where T is the number of tasks, as it iterates through all tasks. Sleep duration calculation is O(T). Space complexity is O(T) to store the tasks.

Pseudocode:

Class ScheduledTask:
  Initialize(name, interval_seconds, task):
    Set name, interval_seconds, task
    Set next_run_time = current_time + interval_seconds

  Run():
    Try:
      Execute task
      Print success message
    Catch any exception:
      Print error message

  Reschedule():
    Set next_run_time = current_time + interval_seconds

Class BackgroundScheduler:
  Initialize():
    Set tasks = empty array
    Set running = false
    Set scheduler_thread = nil

  Add_task(name, interval_seconds, task):
    Create new ScheduledTask and add to tasks array

  Start():
    If not running:
      Set running = true
      Spawn a fiber running scheduler_loop
      Print "Scheduler started."

  Stop():
    If running:
      Set running = false
      Join scheduler_thread
      Print "Scheduler stopped."

  Scheduler_loop():
    While running:
      current_time = current_time
      tasks_to_run = empty array
      For each task in tasks:
        If task.next_run_time <= current_time:
          Add task to tasks_to_run

      For each task in tasks_to_run:
        Spawn a fiber to run task.run()
        task.reschedule()

      Calculate min_next_run time among all tasks
      Determine sleep_duration based on min_next_run or a default
      Sleep for sleep_duration