From: John Labovitz Date: 2005-05-19T02:46:00+09:00 Subject: Re: Event scheduler in Ruby On May 18, 2005, at 12:50 AM, Lawrence Oluyede wrote: > 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 =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 johnl@johnlabovitz.com | +1 503.949.3492 | www.johnlabovitz.com/ consulting