diff --git a/README.rdoc b/README.rdoc index 69d1b0a..9fe0547 100644 --- a/README.rdoc +++ b/README.rdoc @@ -50,7 +50,7 @@ Symphony connection information: == Adding Actions -There are two primary components to Metronome -- getting actions into +There are two primary components to Metronome -- getting schedules into its database, and performing some task with those actions when the time is appropriate. @@ -76,6 +76,15 @@ starts up or receives a HUP signal, it will re-read and schedule out upcoming work. +== Performing Work + +Calling 'run' on the Metronome class is a blocking call, waking up upon +a scheduled event. The run method expects a ruby block, and it receives +the payload and the database ID of the scheduled event. Metronome is +unopinioned, what you do within this block is entirely up to you. See +the Synopsis section above for some examples. + + == Options Metronome uses diff --git a/Rakefile b/Rakefile index 3e52922..722f275 100644 --- a/Rakefile +++ b/Rakefile @@ -140,4 +140,5 @@ lib/symphony/metronome/mixins.rb lib/symphony/metronome/intervalexpression.rb lib/symphony/metronome/scheduledevent.rb data/symphony-metronome/migrations/20140419_initial.rb +data/symphony-metronome/migrations/20141028_lastrun.rb README.rdoc diff --git a/data/symphony-metronome/migrations/20141028_lastrun.rb b/data/symphony-metronome/migrations/20141028_lastrun.rb new file mode 100644 index 0000000..d21a458 --- /dev/null +++ b/data/symphony-metronome/migrations/20141028_lastrun.rb @@ -0,0 +1,23 @@ +# vim: set nosta noet ts=4 sw=4: + +### Add a 'lastrun' time stamp for recurring events. +### +class Lastrun < Sequel::Migration + + def initialize( db ) + @db = db + end + + def up + if @db.adapter_scheme == :postgres + add_column :metronome, :lastrun, timestamptz + else + add_column :metronome, :lastrun, DateTime + end + end + + def down + drop_column :metronome, :lastrun + end +end + diff --git a/lib/symphony/metronome.rb b/lib/symphony/metronome.rb index da4ceaa..1759dbd 100644 --- a/lib/symphony/metronome.rb +++ b/lib/symphony/metronome.rb @@ -9,7 +9,7 @@ module Symphony::Metronome Configurability # Library version constant - VERSION = '0.1.0' + VERSION = '0.2.0' # Version-control revision constant REVISION = %q$Revision$ @@ -74,6 +74,5 @@ module Symphony::Metronome return Symphony::Metronome::Scheduler.run( &block ) end - end # Symphony::Metronome diff --git a/lib/symphony/metronome/scheduledevent.rb b/lib/symphony/metronome/scheduledevent.rb index a961c5f..13fcb10 100644 --- a/lib/symphony/metronome/scheduledevent.rb +++ b/lib/symphony/metronome/scheduledevent.rb @@ -24,8 +24,8 @@ class Symphony::Metronome::ScheduledEvent # Configure defaults. # CONFIG_DEFAULTS = { - db: 'sqlite:///tmp/metronome.db', - splay: 0 + :db => 'sqlite:///tmp/metronome.db', + :splay => 0 } class << self @@ -52,6 +52,7 @@ class Symphony::Metronome::ScheduledEvent # migrations_dir = Symphony::Metronome::DATADIR + 'migrations' unless Sequel::Migrator.is_current?( self.db, migrations_dir.to_s ) + Loggability[ Symphony ].info "Installing database schema..." Sequel::Migrator.apply( self.db, migrations_dir.to_s ) end end @@ -97,6 +98,8 @@ class Symphony::Metronome::ScheduledEvent @event = Symphony::Metronome::IntervalExpression.parse( row[:expression], row[:created] ) @options = row.delete( :options ) @id = row.delete( :id ) + @ds = self.class.db[ :metronome ].filter( :id => self.id ) + self.reset_runtime unless self.class.splay.zero? @@ -105,6 +108,9 @@ class Symphony::Metronome::ScheduledEvent end end + # The sequel dataset representing this event. + attr_reader :ds + # The parsed interval expression. attr_reader :event @@ -133,7 +139,21 @@ class Symphony::Metronome::ScheduledEvent # Otherwise, the event should already be running (start time has already # elapsed), so schedule it forward on it's next interval iteration. # - @runtime = now + self.event.interval + # If it's a recurring event that has run before, consider the elapsed time + # as part of the next calculation. + # + row = self.ds.first + if self.event.recurring && row + last = row[ :lastrun ] + if last && now > last + @runtime = now + self.event.interval - ( now - last ) + else + @runtime = now + self.event.interval + end + + else + @runtime = now + self.event.interval + end end @@ -141,13 +161,32 @@ class Symphony::Metronome::ScheduledEvent ### deserialized options, the action ID to the supplied block if ### this event is okay to execute. ### + ### If the event is recurring, perform additional checks against the + ### last run time. + ### ### Automatically remove the event if it has expired. ### def fire rv = self.event.fire? + # Just based on the expression parser, is this event ready to fire? + # if rv opts = Yajl.load( self.options ) + + # Don't fire recurring events unless their interval has elapsed. + # This prevents events from triggering when the daemon receives + # a HUP. + # + if self.event.recurring + now = Time.now + last = self.ds.first[ :lastrun ] + return false if last && now - last < self.event.interval + + # Mark the time this recurring event was fired. + self.ds.update( :lastrun => Time.now ) + end + yield opts, self.id end @@ -160,7 +199,7 @@ class Symphony::Metronome::ScheduledEvent ### def delete self.log.debug "Removing action %p" % [ self.id ] - self.class.db[ :metronome ].filter( :id => self.id ).delete + self.ds.delete end diff --git a/lib/symphony/metronome/scheduler.rb b/lib/symphony/metronome/scheduler.rb index 89e8b92..4be3aa0 100644 --- a/lib/symphony/metronome/scheduler.rb +++ b/lib/symphony/metronome/scheduler.rb @@ -18,7 +18,7 @@ class Symphony::Metronome::Scheduler SIGNALS = [ :HUP, :INT, :TERM ] CONFIG_DEFAULTS = { - :listen => true + :listen => false } class << self