say_when 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Guardfile +50 -0
- data/README.md +135 -2
- data/Rakefile +1 -0
- data/lib/say_when.rb +33 -18
- data/lib/say_when/configuration.rb +16 -0
- data/lib/say_when/cron_expression.rb +19 -21
- data/lib/say_when/poller/base_poller.rb +108 -0
- data/lib/say_when/poller/celluloid_poller.rb +30 -0
- data/lib/say_when/poller/concurrent_poller.rb +31 -0
- data/lib/say_when/poller/simple_poller.rb +37 -0
- data/lib/say_when/processor/active_job_strategy.rb +35 -0
- data/lib/say_when/processor/simple_strategy.rb +13 -0
- data/lib/say_when/processor/test_strategy.rb +21 -0
- data/lib/say_when/scheduler.rb +67 -101
- data/lib/say_when/storage/active_record_strategy.rb +204 -0
- data/lib/say_when/storage/base_job.rb +96 -0
- data/lib/say_when/storage/memory_strategy.rb +140 -0
- data/lib/say_when/tasks.rb +15 -3
- data/lib/say_when/triggers/base.rb +3 -3
- data/lib/say_when/triggers/cron_strategy.rb +2 -3
- data/lib/say_when/triggers/instance_strategy.rb +3 -4
- data/lib/say_when/triggers/once_strategy.rb +3 -4
- data/lib/say_when/utils.rb +16 -0
- data/lib/say_when/version.rb +1 -1
- data/say_when.gemspec +10 -5
- data/test/minitest_helper.rb +45 -15
- data/test/say_when/configuration_test.rb +14 -0
- data/test/say_when/cron_expression_test.rb +140 -0
- data/test/say_when/poller/base_poller_test.rb +42 -0
- data/test/say_when/poller/celluloid_poller_test.rb +17 -0
- data/test/say_when/poller/concurrent_poller_test.rb +19 -0
- data/test/say_when/poller/simple_poller_test.rb +27 -0
- data/test/say_when/processor/active_job_strategy_test.rb +31 -0
- data/test/say_when/processor/simple_strategy_test.rb +15 -0
- data/test/say_when/scheduler_test.rb +41 -57
- data/test/say_when/storage/active_record_strategy_test.rb +134 -0
- data/test/say_when/storage/memory_strategy_test.rb +96 -0
- data/test/say_when/triggers/cron_strategy_test.rb +11 -0
- data/test/say_when/triggers/instance_strategy_test.rb +13 -0
- data/test/say_when/triggers/once_strategy_test.rb +2 -2
- data/test/say_when_test.rb +20 -0
- metadata +110 -36
- data/lib/say_when/base_job.rb +0 -96
- data/lib/say_when/processor/active_messaging.rb +0 -21
- data/lib/say_when/processor/base.rb +0 -19
- data/lib/say_when/processor/shoryuken.rb +0 -14
- data/lib/say_when/processor/simple.rb +0 -17
- data/lib/say_when/storage/active_record/acts.rb +0 -92
- data/lib/say_when/storage/active_record/job.rb +0 -100
- data/lib/say_when/storage/active_record/job_execution.rb +0 -14
- data/lib/say_when/storage/memory/base.rb +0 -36
- data/lib/say_when/storage/memory/job.rb +0 -53
- data/test/say_when/cron_expression_spec.rb +0 -74
- data/test/say_when/processor/active_messaging_test.rb +0 -41
- data/test/say_when/storage/active_record/job_test.rb +0 -90
- data/test/say_when/storage/memory/job_test.rb +0 -32
- data/test/say_when/storage/memory/trigger_test.rb +0 -54
- data/test/support/models.rb +0 -33
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module SayWhen
|
4
|
+
module Storage
|
5
|
+
module BaseJob
|
6
|
+
# ready to be run, just waiting for its turn
|
7
|
+
STATE_WAITING = 'waiting'
|
8
|
+
|
9
|
+
# has been acquired b/c it is time to be triggered
|
10
|
+
STATE_ACQUIRED = 'acquired'
|
11
|
+
|
12
|
+
# related job for the trigger is executing
|
13
|
+
STATE_EXECUTING = 'executing'
|
14
|
+
|
15
|
+
# "Complete" means the trigger has no remaining fire times
|
16
|
+
STATE_COMPLETE = 'complete'
|
17
|
+
|
18
|
+
# A Trigger arrives at the error state when the scheduler
|
19
|
+
# attempts to fire it, but cannot due to an error creating and executing
|
20
|
+
# its related job.
|
21
|
+
STATE_ERROR = 'error'
|
22
|
+
|
23
|
+
def lock
|
24
|
+
@lock ||= Mutex.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def trigger
|
28
|
+
@trigger ||= load_trigger
|
29
|
+
end
|
30
|
+
|
31
|
+
def fired(fired_at = Time.now)
|
32
|
+
lock.synchronize do
|
33
|
+
self.last_fire_at = fired_at
|
34
|
+
self.next_fire_at = trigger.next_fire_at(last_fire_at + 1.second) rescue nil
|
35
|
+
|
36
|
+
if next_fire_at.nil?
|
37
|
+
self.status = STATE_COMPLETE
|
38
|
+
else
|
39
|
+
self.status = STATE_WAITING
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def release
|
45
|
+
lock.synchronize do
|
46
|
+
if self.status == STATE_ACQUIRED
|
47
|
+
self.status = STATE_WAITING
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def execute
|
53
|
+
execute_job(data)
|
54
|
+
end
|
55
|
+
|
56
|
+
def load_trigger
|
57
|
+
strategy = trigger_strategy || :once
|
58
|
+
require "say_when/triggers/#{strategy}_strategy"
|
59
|
+
trigger_class_name = "SayWhen::Triggers::#{strategy.to_s.camelize}Strategy"
|
60
|
+
trigger_class = trigger_class_name.constantize
|
61
|
+
trigger_class.new((trigger_options || {}).merge(:job=>self))
|
62
|
+
end
|
63
|
+
|
64
|
+
def execute_job(options)
|
65
|
+
task_method = (job_method || 'execute').to_s
|
66
|
+
task = get_task(task_method)
|
67
|
+
task.send(task_method, options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_task(task_method)
|
71
|
+
task = nil
|
72
|
+
|
73
|
+
if job_class
|
74
|
+
tc = job_class.constantize
|
75
|
+
if tc.respond_to?(task_method)
|
76
|
+
task = tc
|
77
|
+
else
|
78
|
+
to = tc.new
|
79
|
+
if to.respond_to?(task_method)
|
80
|
+
task = to
|
81
|
+
else
|
82
|
+
raise "Neither '#{job_class}' class nor instance respond to '#{task_method}'"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
elsif scheduled
|
86
|
+
if scheduled.respond_to?(task_method)
|
87
|
+
task = scheduled
|
88
|
+
else
|
89
|
+
raise "Scheduled '#{scheduled.inspect}' does not respond to '#{task_method}'"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
task
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'say_when/storage/base_job'
|
3
|
+
|
4
|
+
module SayWhen
|
5
|
+
module Storage
|
6
|
+
class MemoryStrategy
|
7
|
+
class << self
|
8
|
+
def acquire_next(no_later_than = nil)
|
9
|
+
SayWhen::Storage::MemoryStrategy::Job.acquire_next(no_later_than)
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset_acquired(older_than_seconds)
|
13
|
+
SayWhen::Storage::MemoryStrategy::Job.reset_acquired(older_than_seconds)
|
14
|
+
end
|
15
|
+
|
16
|
+
def fired(job, fired_at = Time.now)
|
17
|
+
job.fired(fired_at)
|
18
|
+
end
|
19
|
+
|
20
|
+
def release(job)
|
21
|
+
job.release
|
22
|
+
end
|
23
|
+
|
24
|
+
def create(job)
|
25
|
+
SayWhen::Storage::MemoryStrategy::Job.create(job)
|
26
|
+
end
|
27
|
+
|
28
|
+
def serialize(job)
|
29
|
+
job.to_hash
|
30
|
+
end
|
31
|
+
|
32
|
+
def deserialize(job)
|
33
|
+
SayWhen::Storage::MemoryStrategy::Job.new(job)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Job
|
38
|
+
include SayWhen::Storage::BaseJob
|
39
|
+
|
40
|
+
class << self
|
41
|
+
def acquire_lock
|
42
|
+
@acquire_lock ||= Mutex.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def jobs
|
46
|
+
@jobs ||= SortedSet.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def props
|
50
|
+
@props ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
def reset_acquired(older_than_seconds)
|
54
|
+
return unless older_than_seconds.to_i > 0
|
55
|
+
older_than = (Time.now - older_than_seconds.to_i)
|
56
|
+
acquire_lock.synchronize do
|
57
|
+
jobs.select do |j|
|
58
|
+
j.status == SayWhen::Storage::BaseJob::STATE_ACQUIRED && j.updated_at < older_than
|
59
|
+
end.each{ |j| j.status = SayWhen::Storage::BaseJob::STATE_WAITING }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def acquire_next(no_later_than)
|
64
|
+
acquire_lock.synchronize do
|
65
|
+
next_job = jobs.detect(nil) do |j|
|
66
|
+
(j.status == SayWhen::Storage::BaseJob::STATE_WAITING) &&
|
67
|
+
(j.next_fire_at.to_i <= no_later_than.to_i)
|
68
|
+
end
|
69
|
+
if next_job
|
70
|
+
next_job.status = SayWhen::Storage::BaseJob::STATE_ACQUIRED
|
71
|
+
next_job.updated_at = Time.now
|
72
|
+
end
|
73
|
+
next_job
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def create(job)
|
78
|
+
if existing_job = find_named_job(job[:group], job[:name])
|
79
|
+
self.jobs.delete(existing_job)
|
80
|
+
end
|
81
|
+
|
82
|
+
new(job).save
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_named_job(group, name)
|
86
|
+
group && name && jobs.detect { |j| j.group == group && j.name == name }
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_properties(*args)
|
90
|
+
args.each do |a|
|
91
|
+
unless props.member?(a.to_s)
|
92
|
+
props << a.to_s
|
93
|
+
class_eval { attr_accessor(a.to_sym) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
has_properties :group, :name, :status, :start_at, :end_at
|
100
|
+
has_properties :trigger_strategy, :trigger_options, :last_fire_at, :next_fire_at
|
101
|
+
has_properties :job_class, :job_method, :data, :updated_at, :scheduled
|
102
|
+
|
103
|
+
def initialize(options = {})
|
104
|
+
options.each do |k,v|
|
105
|
+
if self.class.props.member?(k.to_s)
|
106
|
+
send("#{k}=", v)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
self.updated_at = Time.now
|
111
|
+
self.status = STATE_WAITING unless self.status
|
112
|
+
self.next_fire_at = trigger.next_fire_at
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_hash
|
116
|
+
[:job_class, :job_method, :data].inject({}) { |h,k| h[k] = send(k); h }
|
117
|
+
end
|
118
|
+
|
119
|
+
def save
|
120
|
+
self.class.jobs << self
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
def <=>(job)
|
125
|
+
self.next_fire_at.to_i <=> job.next_fire_at.to_i
|
126
|
+
end
|
127
|
+
|
128
|
+
def fired(fired_at=Time.now)
|
129
|
+
super
|
130
|
+
self.updated_at = Time.now
|
131
|
+
end
|
132
|
+
|
133
|
+
def release
|
134
|
+
super
|
135
|
+
self.updated_at = Time.now
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/say_when/tasks.rb
CHANGED
@@ -1,9 +1,21 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
namespace :say_when do
|
4
|
-
desc 'Start the SayWhen Scheduler'
|
5
|
-
task
|
4
|
+
desc 'Start the SayWhen Scheduler with Rails environment'
|
5
|
+
task start_rails: :environment do
|
6
6
|
require 'say_when'
|
7
|
-
|
7
|
+
require 'say_when/poller/simple_poller'
|
8
|
+
|
9
|
+
SayWhen::Poller::SimplePoller.new.start
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Start the SayWhen Scheduler standalone'
|
13
|
+
task :start do
|
14
|
+
require 'say_when'
|
15
|
+
require 'say_when/poller/simple_poller'
|
16
|
+
|
17
|
+
SayWhen.configure
|
18
|
+
puts "SayWhen starting with options: #{SayWhen.options.inspect}"
|
19
|
+
SayWhen::Poller::SimplePoller.new.start
|
8
20
|
end
|
9
21
|
end
|
@@ -3,14 +3,14 @@
|
|
3
3
|
module SayWhen
|
4
4
|
module Triggers
|
5
5
|
module Base
|
6
|
-
|
7
6
|
attr_accessor :job
|
8
7
|
|
9
|
-
def initialize(options={})
|
8
|
+
def initialize(options = {})
|
10
9
|
self.job = options.delete(:job)
|
10
|
+
raise ArgumentError.new("job must be provided to create a trigger") unless job
|
11
11
|
end
|
12
12
|
|
13
|
-
def next_fire_at(time=nil)
|
13
|
+
def next_fire_at(time = nil)
|
14
14
|
raise NotImplementedError.new('You need to implement next_fire_at in your strategy')
|
15
15
|
end
|
16
16
|
end
|
@@ -6,17 +6,16 @@ require 'say_when/cron_expression'
|
|
6
6
|
module SayWhen
|
7
7
|
module Triggers
|
8
8
|
class CronStrategy
|
9
|
-
|
10
9
|
include SayWhen::Triggers::Base
|
11
10
|
|
12
11
|
attr_accessor :cron_expression
|
13
12
|
|
14
|
-
def initialize(options={})
|
13
|
+
def initialize(options = {})
|
15
14
|
super
|
16
15
|
self.cron_expression = SayWhen::CronExpression.new(options)
|
17
16
|
end
|
18
17
|
|
19
|
-
def next_fire_at(time=nil)
|
18
|
+
def next_fire_at(time = nil)
|
20
19
|
cron_expression.next_fire_at(time || Time.now)
|
21
20
|
end
|
22
21
|
end
|
@@ -5,18 +5,17 @@ require 'say_when/triggers/base'
|
|
5
5
|
module SayWhen
|
6
6
|
module Triggers
|
7
7
|
class InstanceStrategy
|
8
|
-
|
9
8
|
include SayWhen::Triggers::Base
|
10
9
|
|
11
10
|
attr_accessor :instance, :next_at_method
|
12
11
|
|
13
|
-
def initialize(options={})
|
12
|
+
def initialize(options = {})
|
14
13
|
super
|
15
|
-
self.instance
|
14
|
+
self.instance = job.scheduled
|
16
15
|
self.next_at_method = options[:next_at_method] || 'next_fire_at'
|
17
16
|
end
|
18
17
|
|
19
|
-
def next_fire_at(time=Time.now)
|
18
|
+
def next_fire_at(time = Time.now)
|
20
19
|
instance.send(next_at_method, time)
|
21
20
|
end
|
22
21
|
end
|
@@ -5,19 +5,18 @@ require 'say_when/triggers/base'
|
|
5
5
|
module SayWhen
|
6
6
|
module Triggers
|
7
7
|
class OnceStrategy
|
8
|
-
|
9
8
|
include SayWhen::Triggers::Base
|
10
9
|
|
11
10
|
attr_accessor :once_at
|
12
11
|
|
13
|
-
def initialize(options=nil)
|
12
|
+
def initialize(options = nil)
|
14
13
|
super
|
15
14
|
self.once_at = options[:at] || Time.now
|
16
15
|
end
|
17
16
|
|
18
|
-
def next_fire_at(time=nil)
|
17
|
+
def next_fire_at(time = nil)
|
19
18
|
nfa = once_at if (!time || (time <= once_at))
|
20
|
-
|
19
|
+
nfa
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module SayWhen
|
4
|
+
module Utils
|
5
|
+
def load_strategy(strategy_type, strategy)
|
6
|
+
if strategy.is_a?(Symbol) || strategy.is_a?(String)
|
7
|
+
require "say_when/#{strategy_type}/#{strategy}_strategy"
|
8
|
+
class_name = "SayWhen::#{strategy_type.to_s.camelize}::#{strategy.to_s.camelize}Strategy"
|
9
|
+
strategy_class = class_name.constantize
|
10
|
+
strategy_class
|
11
|
+
else
|
12
|
+
strategy
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/say_when/version.rb
CHANGED
data/say_when.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require 'say_when/version'
|
@@ -20,12 +20,17 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency('activesupport')
|
22
22
|
|
23
|
-
spec.add_development_dependency('
|
24
|
-
spec.add_development_dependency('shoryuken')
|
25
|
-
spec.add_development_dependency('activemessaging')
|
26
|
-
spec.add_development_dependency('bundler', '~> 1.3')
|
23
|
+
spec.add_development_dependency('bundler')
|
27
24
|
spec.add_development_dependency('rake')
|
28
25
|
spec.add_development_dependency('minitest')
|
26
|
+
spec.add_development_dependency('guard')
|
27
|
+
spec.add_development_dependency('guard-minitest')
|
29
28
|
spec.add_development_dependency('simplecov')
|
29
|
+
spec.add_development_dependency('coveralls')
|
30
|
+
|
30
31
|
spec.add_development_dependency('sqlite3')
|
32
|
+
spec.add_development_dependency('activerecord')
|
33
|
+
spec.add_development_dependency('celluloid')
|
34
|
+
spec.add_development_dependency('concurrent-ruby')
|
35
|
+
spec.add_development_dependency('activejob')
|
31
36
|
end
|
data/test/minitest_helper.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
ENV['RAILS_ENV'] ||= 'test'
|
4
|
+
|
5
|
+
require 'simplecov'
|
6
|
+
SimpleCov.start #'rails'
|
7
|
+
|
8
|
+
if ENV['TRAVIS']
|
9
|
+
require 'coveralls'
|
10
|
+
Coveralls.wear!
|
11
|
+
end
|
12
|
+
|
3
13
|
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
4
14
|
require 'say_when'
|
5
15
|
|
@@ -9,35 +19,55 @@ require 'minitest/spec'
|
|
9
19
|
require 'minitest/mock'
|
10
20
|
require 'fileutils'
|
11
21
|
|
12
|
-
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
|
13
|
-
|
14
22
|
require 'active_support'
|
23
|
+
require 'active_job'
|
24
|
+
|
25
|
+
require 'celluloid/test'
|
26
|
+
|
27
|
+
ActiveJob::Base.queue_adapter = :inline
|
28
|
+
|
29
|
+
Celluloid.boot
|
30
|
+
|
31
|
+
SayWhen.configure do |options|
|
32
|
+
options[:storage_strategy] = :memory
|
33
|
+
options[:processor_strategy] = :test
|
34
|
+
end
|
35
|
+
|
36
|
+
SayWhen.logger = Logger.new('/dev/null')
|
15
37
|
|
16
38
|
module SayWhen
|
17
39
|
module Test
|
18
|
-
|
19
40
|
class TestTask
|
20
|
-
|
41
|
+
@@executed = false
|
42
|
+
|
43
|
+
def self.reset
|
44
|
+
@@executed = false
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.execute(data)
|
48
|
+
@@executed = true
|
21
49
|
data[:result] || 0
|
22
50
|
end
|
51
|
+
|
52
|
+
def self.executed?
|
53
|
+
@@executed
|
54
|
+
end
|
23
55
|
end
|
24
56
|
|
25
|
-
class
|
26
|
-
|
57
|
+
class TestActsAsScheduled
|
58
|
+
@@_has_many = false
|
27
59
|
|
28
|
-
def
|
29
|
-
|
30
|
-
reset
|
60
|
+
def self.has_many_called?
|
61
|
+
@@_has_many
|
31
62
|
end
|
32
63
|
|
33
|
-
def
|
34
|
-
|
64
|
+
def self.has_many(*args)
|
65
|
+
@@_has_many = true
|
35
66
|
end
|
36
67
|
|
37
|
-
|
38
|
-
|
39
|
-
|
68
|
+
require 'say_when/storage/active_record_strategy'
|
69
|
+
include SayWhen::Storage::ActiveRecordStrategy::Acts
|
70
|
+
acts_as_scheduled
|
40
71
|
end
|
41
|
-
|
42
72
|
end
|
43
73
|
end
|