Add 'last run' timestamps for recurring events, and take these into

account when firing events on daemon rescheduling (via HUP.)

FossilOrigin-Name: 1620e80e8bddd27741569dea0ff4bd84ba29429861ed49d17f5777c238ae4876
This commit is contained in:
mahlon@laika.com 2014-10-28 18:53:17 +00:00
parent b18647f6a5
commit 7c2954b0ad
6 changed files with 79 additions and 8 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,21 +139,54 @@ 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.
#
# 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
### Perform the action attached to the event. Yields the
### 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

View file

@ -18,7 +18,7 @@ class Symphony::Metronome::Scheduler
SIGNALS = [ :HUP, :INT, :TERM ]
CONFIG_DEFAULTS = {
:listen => true
:listen => false
}
class << self