Event scheduler in Ruby

J

John Labovitz

Is there anything like the sched module in Python?
http://docs.python.org/lib/module-sched.html

I've written a Scheduler class for a project I'm working on. Perhaps
it might help.

I haven't packed it up yet, but I'll include the code below. If
there's interest, I could publish it somewhere.

#<-- CUT HERE
#-- this is schedule.rb
=begin

= Scheduler

Schedules tasks in a cron-like manner, but uses a nearly-English
syntax for scheduling tasks.
It uses threads to do the waiting, although the tasks themselves are
run sequentially in the main thread.

Note that it doesn't handle absolute times or dates (eg, 'at 3.pm',
'on 2005-05-18') yet.
It would also be nice to handle +Runt+ objects.

== Example

class MyScheduler < Scheduler

def schedule

at startup do
do_my_startup_task
end

at shutdown do
do_my_shutdown_task
end

every minute do
do_something_frequently
end

every 2.hours do
do_something_occasionally
end

end

end

sched = MyScheduler.new
sched.start

# or to run it only for one hour...

sched.start(1.hour)

== Author

John Labovitz <[email protected]>

=end

require 'timeunits' # see below
require 'time'
require 'thread'


#
# A +Scheduler+ object keeps track of tasks to be run. Usual usage
is to subclass Scheduler, and
# define your own +schedule+ method to set up the schedule. Once
you've created a new Scheduler
# object, you can call +start+ to start the schedule running.

class Scheduler

#
# A +Task+ represents something to do after an interval of time
has happened.

class Task

#
# The amount of time to wait until the task will be run. If
this is set to the symbol
# +:startup+ or +:shutdown+, the task will be run only once, at
schedule startup or
# shutdown, respectively.
#
# FIXME: It would be nice if +interval+ could be a +Time+ or
+Date+ object, or a crontab-like
# string, or a +Runt::Event+.

attr_accessor :interval

#
# A block or Proc object that will be run when the time interval
has passed.

attr_accessor :proc

#
# The internal thread that is responsible for actually waiting
the time interval.

attr_accessor :thread

#
# Create a new task. If supplied, +interval+ and +proc+
correspond to their
# respective methods.

def initialize(interval=nil, proc=nil)
self.interval = interval
self.proc = proc
self.thread = nil
end

#
# Run the block or Proc associated with a task.

def run
self.proc.call
end

#
# Schedule the task. This actually starts a thread that waits
until the specified
# time interval has passed, then adds the task to a run queue
that the main +Scheduler+
# object waits on.

def schedule(scheduler)
case self.interval
when :startup, :shutdown
# ignore it
else
self.thread = Thread.new do
loop do
sleep self.interval
scheduler.run_queue << self
end
end
self.thread.abort_on_exception = true
end
end

end


#
# The list of all tasks in the schedule, including startup and
shutdown tasks.

attr_accessor :tasks

#
# The run-queue, representing all the tasks whose interval has
passed, and should be run.

attr_accessor :run_queue

#
# Create a new schedule. The object's +#schedule+ method, if any,
will be called to create
# the schedule. Note that the schedule's +#run+ method must be
called to get actually start
# the schedule.

def initialize
self.tasks = Array.new
self.run_queue = Queue.new
self.schedule
end

#
# Add a task to the schedule. If +interval+ is a number (Fixnum
or Float), it represents the
# number of seconds to wait between successive runs of the task.
If +interval+ is a Time or Date
# object, the task will be run once at the specified time.

def add_task(interval, &block)
tasks << Task.new(interval, block)
end

#
# Return all tasks that should be run at startup.

def startup_tasks
self.tasks.select { |t| t.interval == :startup }
end

#
# Return all tasks that should be run at shutdown.

def shutdown_tasks
self.tasks.select { |t| t.interval == :shutdown }
end

#
# Return all tasks that should be scheduled.

def scheduled_tasks
self.tasks.select { |t| t.interval != :startup && t.interval !
= :shutdown }
end

#
# Various helpful aliases that encourage readable schedules.

def second; 1.second; end
def minute; 1.minute; end
def hour; 1.hour; end
def day; 1.day; end

def startup; :startup; end
def shutdown; :shutdown; end

alias every add_task
alias at every
alias on every

#
# Set up schedules. This method should be implemented by a
subclass of Scheduler.

def schedule
# implemented by subclass
end

#
# Start a schedule. If +period+ is supplied, the schedule will
only run for that
# many seconds. Otherwise, it will run forever, or until there
are no tasks to run.

def start(period=nil)

#
# Run any startup tasks.

self.startup_tasks.each { |t| t.run }

#
# If caller only wants to run for a while, start a thread that will
# sleep that long, then kill off all the threads. +nil+ is
posted to
# the queue as a signal that the scheduler should stop running.

if period
Thread.new do
Thread.abort_on_exception = true
sleep period
self.scheduled_tasks.each { |t| t.thread.exit if t.thread &&
t.thread.alive? }
self.run_queue << nil
end
end

#
# Schedule all the tasks.

self.scheduled_tasks.each { |t| t.schedule(self) }

#
# Run any tasks whose threads have placed the task onto the run
queue, until +nil+ is received.

while (task = self.run_queue.pop) do
task.run
end

#
# Run any shutdown tasks.

self.shutdown_tasks.each { |t| t.run }

end

end

#<-- CUT HERE
#--this is timeunits.rb
module TimeUnits
def second; self ; end
def minute; self * 60 ; end
def hour; self.minute * 60; end
def day; self.hour * 24 ; end

alias :seconds :second
alias :minutes :minute
alias :hours :hour
alias :days :day
end

class Fixnum
include TimeUnits
end

class Float
include TimeUnits
end

#<-- CUT HERE


--
John Labovitz
Macintosh support, research, and software development
John Labovitz Consulting, LLC
(e-mail address removed) | +1 503.949.3492 | www.johnlabovitz.com/
consulting
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top