elected_scheduler 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -0
- data/lib/elected/scheduler/poller.rb +32 -32
- data/lib/elected/scheduler/schedule.rb +39 -93
- data/lib/elected/scheduler/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65e11fc03001777e3fc15d10cf8f617ccfc7c76e
|
4
|
+
data.tar.gz: b21d8aa9b54dd3858a097e45bacdc2c3333227d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0261d2074e5ac4e60f0f2bdc28f719775668eb6c403cb66c2663779c543da6bbc87db6ad646f785375b06052d5bc29f1e2cdc050c227096b1ddeda0a02299730
|
7
|
+
data.tar.gz: b946425a46a34668698a89555ffbe345d79e60a417a6dbb07f2462069d924d233a343ef3d54560a357e4ce5a89f3afdd68c6fb4d36ee469cf43bc63438494e1d
|
data/README.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/simple-finance/elected_scheduler.svg?branch=master)](https://travis-ci.org/simple-finance/elected_scheduler)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/simple-finance/elected_scheduler/badges/gpa.svg)](https://codeclimate.com/github/simple-finance/elected_scheduler)
|
3
|
+
[![security](https://hakiri.io/github/simple-finance/elected_scheduler/master.svg)](https://hakiri.io/github/simple-finance/elected_scheduler/master)
|
4
|
+
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/simple-finance/elected_scheduler?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
5
|
+
|
1
6
|
# ElectedScheduler - A ruby distributed cron-like scheduler that only runs jobs on leader processes.
|
2
7
|
|
3
8
|
> Schedule your jobs at cron-like times (seconds included) and run the jobs only if the process is the current leader.
|
@@ -5,7 +5,7 @@ module Elected
|
|
5
5
|
include Logging
|
6
6
|
extend Forwardable
|
7
7
|
|
8
|
-
attr_reader :key, :jobs
|
8
|
+
attr_reader :key, :jobs, :stats
|
9
9
|
|
10
10
|
def_delegators :@senado, :timeout
|
11
11
|
|
@@ -13,6 +13,7 @@ module Elected
|
|
13
13
|
@senado ||= Senado.new key, timeout
|
14
14
|
@key = key
|
15
15
|
@jobs = Concurrent::Hash.new
|
16
|
+
@stats = Stats.new :processed_job, :no_match, :sleep_slave
|
16
17
|
end
|
17
18
|
|
18
19
|
def add(job)
|
@@ -26,27 +27,17 @@ module Elected
|
|
26
27
|
@status ||= :stopped
|
27
28
|
end
|
28
29
|
|
29
|
-
def starting?
|
30
|
-
status == :starting
|
31
|
-
end
|
32
|
-
|
33
30
|
def running?
|
34
31
|
status == :running
|
35
32
|
end
|
36
33
|
|
37
|
-
def stopping?
|
38
|
-
status == :stopping
|
39
|
-
end
|
40
|
-
|
41
34
|
def stopped?
|
42
35
|
status == :stopped
|
43
36
|
end
|
44
37
|
|
45
38
|
def start
|
46
|
-
unless stopped?
|
47
|
-
|
48
|
-
return false
|
49
|
-
end
|
39
|
+
return false unless stopped?
|
40
|
+
raise 'No jobs to run!' if jobs.empty?
|
50
41
|
|
51
42
|
debug "#{label} starting ..."
|
52
43
|
@status = :starting
|
@@ -54,24 +45,24 @@ module Elected
|
|
54
45
|
@status = :running
|
55
46
|
debug "#{label} running poller!"
|
56
47
|
@status
|
57
|
-
rescue Exception => e
|
58
|
-
error "#{label} Exception thrown: #{e.class.name} : #{e.message}\n #{e.backtrace[0, 5].join("\n ")}"
|
59
48
|
end
|
60
49
|
|
61
50
|
def stop
|
62
|
-
unless running?
|
63
|
-
warn "cannot stop on #{status} ..."
|
64
|
-
return false
|
65
|
-
end
|
51
|
+
return false unless running?
|
66
52
|
|
67
53
|
debug "#{label} stopping poller ..."
|
68
54
|
@status = :stopping
|
69
55
|
stop_polling_loop
|
56
|
+
senado.release
|
70
57
|
@status = :stopped
|
71
58
|
debug "#{label} stopped poller!"
|
72
59
|
@status
|
73
60
|
end
|
74
61
|
|
62
|
+
def leader?
|
63
|
+
senado.leader?
|
64
|
+
end
|
65
|
+
|
75
66
|
def to_s
|
76
67
|
%{#<#{self.class.name} key="#{key}" timeout="#{timeout}" jobs=#{jobs.size}>}
|
77
68
|
end
|
@@ -85,11 +76,7 @@ module Elected
|
|
85
76
|
def start_polling_loop
|
86
77
|
debug 'starting process loop ...'
|
87
78
|
@polling_loop_thread = Thread.new do
|
88
|
-
|
89
|
-
poll_and_process_loop
|
90
|
-
rescue Exception => e
|
91
|
-
error "Exception: #{e.class.name} : #{e.message}\n #{e.backtrace[0, 10].join("\n ")}"
|
92
|
-
end
|
79
|
+
poll_and_process_loop
|
93
80
|
end
|
94
81
|
end
|
95
82
|
|
@@ -97,29 +84,32 @@ module Elected
|
|
97
84
|
cnt = 0
|
98
85
|
while running?
|
99
86
|
cnt += 1
|
100
|
-
|
101
|
-
|
102
|
-
poll_and_process_jobs
|
103
|
-
rescue Exception => e
|
104
|
-
error "#{label(cnt)} Exception: #{e.class.name} : #{e.message}\n #{e.backtrace[0, 5].join("\n ")}"
|
105
|
-
end
|
87
|
+
debug "#{label(cnt)} calling poll_and_process_jobs while running?:#{running?} ..."
|
88
|
+
poll_and_process_jobs
|
106
89
|
sleep_until_next_tick
|
107
90
|
end
|
108
91
|
end
|
109
92
|
|
110
93
|
def poll_and_process_jobs
|
111
94
|
if senado.leader?
|
95
|
+
debug "#{label} leader, processing jobs ..."
|
112
96
|
start_time = Time.now
|
113
97
|
jobs.values.each { |job| process_job job, start_time }
|
114
98
|
else
|
99
|
+
debug "#{label} not a leader, sleeping ..."
|
100
|
+
@stats.increment :sleep_slave
|
115
101
|
sleep_for_slave
|
116
102
|
end
|
117
103
|
end
|
118
104
|
|
119
105
|
def process_job(job, time)
|
120
|
-
|
106
|
+
unless job.matches? time
|
107
|
+
@stats.increment :no_match
|
108
|
+
return false
|
109
|
+
end
|
121
110
|
|
122
111
|
Concurrent::Future.execute { job.execute }
|
112
|
+
@stats.increment :processed_job
|
123
113
|
end
|
124
114
|
|
125
115
|
def stop_polling_loop
|
@@ -147,8 +137,18 @@ module Elected
|
|
147
137
|
|
148
138
|
extend self
|
149
139
|
|
140
|
+
attr_writer :key, :timeout
|
141
|
+
|
142
|
+
def key
|
143
|
+
@key || 'elected_scheduler_poller'
|
144
|
+
end
|
145
|
+
|
146
|
+
def timeout
|
147
|
+
@timeout || Elected.timeout
|
148
|
+
end
|
149
|
+
|
150
150
|
def poller
|
151
|
-
@poller ||= Poller.new
|
151
|
+
@poller ||= Poller.new key, timeout
|
152
152
|
end
|
153
153
|
end
|
154
154
|
end
|
@@ -6,11 +6,14 @@ module Elected
|
|
6
6
|
|
7
7
|
FIELDS = { seconds: :sec, minutes: :min, hours: :hour, days: :day, months: :month, dows: :wday }.freeze
|
8
8
|
|
9
|
-
ABBR_DAYNAMES = %w{ sun mon tue wed thu fri sat }
|
10
|
-
DAYNAMES = %w{ sunday monday tuesday wednesday thursday friday saturday }
|
9
|
+
ABBR_DAYNAMES = %w{ sun mon tue wed thu fri sat }.freeze
|
10
|
+
DAYNAMES = %w{ sunday monday tuesday wednesday thursday friday saturday }.freeze
|
11
11
|
|
12
|
-
MONTHNAMES = %w{ january february march april may june july august september october november december }
|
13
|
-
ABBR_MONTHNAMES = %w{ jan feb mar apr may jun jul aug sep oct nov dec }
|
12
|
+
MONTHNAMES = %w{ '' january february march april may june july august september october november december }.freeze
|
13
|
+
ABBR_MONTHNAMES = %w{ '' jan feb mar apr may jun jul aug sep oct nov dec }.freeze
|
14
|
+
|
15
|
+
MIN_NUMBERS = { seconds: 0, minutes: 0, hours: 0, days: 1, months: 1, dows: 0 }.freeze
|
16
|
+
MAX_NUMBERS = { seconds: 59, minutes: 59, hours: 23, days: 31, months: 12, dows: 6 }.freeze
|
14
17
|
|
15
18
|
def self.defaults
|
16
19
|
@defaults ||= { seconds: [0] }
|
@@ -18,7 +21,7 @@ module Elected
|
|
18
21
|
|
19
22
|
def initialize(options = {})
|
20
23
|
@options = options
|
21
|
-
setup *FIELDS.keys
|
24
|
+
setup *(FIELDS.keys)
|
22
25
|
end
|
23
26
|
|
24
27
|
def matches?(time)
|
@@ -26,15 +29,7 @@ module Elected
|
|
26
29
|
end
|
27
30
|
|
28
31
|
def to_cron_s
|
29
|
-
FIELDS.keys.map
|
30
|
-
value = get field
|
31
|
-
case value
|
32
|
-
when :all
|
33
|
-
'*'
|
34
|
-
when Range, Array
|
35
|
-
value.map { |x| x.to_s }.join(',')
|
36
|
-
end
|
37
|
-
end.join(' ')
|
32
|
+
FIELDS.keys.map { |field| get_cron_s(field) }.join(' ')
|
38
33
|
end
|
39
34
|
|
40
35
|
alias :to_s :to_cron_s
|
@@ -48,6 +43,11 @@ module Elected
|
|
48
43
|
|
49
44
|
alias :[] :get
|
50
45
|
|
46
|
+
def get_cron_s(field)
|
47
|
+
value = get(field)
|
48
|
+
value === :all ? '*' : value.map { |x| x.to_s }.join(',')
|
49
|
+
end
|
50
|
+
|
51
51
|
def set(field, value)
|
52
52
|
instance_variable_set "@#{field}", convert(field, value)
|
53
53
|
end
|
@@ -65,102 +65,48 @@ module Elected
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def convert(field, value)
|
68
|
+
return value if value === :all
|
69
|
+
|
70
|
+
ary = simplify_enum Array(value).flatten
|
68
71
|
case field
|
69
72
|
when :seconds, :minutes, :hours, :days
|
70
|
-
convert_numbers field,
|
73
|
+
convert_numbers field, ary
|
71
74
|
when :months
|
72
|
-
|
75
|
+
convert_numbers :months, ary, :convert_month_name
|
73
76
|
when :dows
|
74
|
-
|
77
|
+
convert_numbers :dows, ary, :convert_dow_name
|
75
78
|
end
|
76
79
|
end
|
77
80
|
|
78
|
-
def
|
79
|
-
|
80
|
-
return true if value === :all
|
81
|
-
value.include? exp_value
|
81
|
+
def simplify_enum(ary)
|
82
|
+
ary.map { |x| x.is_a?(Range) ? x.to_a : x }.flatten
|
82
83
|
end
|
83
84
|
|
84
|
-
def convert_numbers(field,
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
simplify_enum [value], 0, max
|
91
|
-
when Array, Range
|
92
|
-
simplify_enum value, 0, max
|
93
|
-
else
|
94
|
-
raise "Unknown value (#{value.class.name}) #{value.inspect}] for #{field}"
|
95
|
-
end
|
85
|
+
def convert_numbers(field, ary, converter_name = nil)
|
86
|
+
ary.map do |x|
|
87
|
+
converter_name ? send(converter_name, x) : x
|
88
|
+
end.select do |x|
|
89
|
+
x.is_a?(Integer) && x >= MIN_NUMBERS[field] && x <= MAX_NUMBERS[field]
|
90
|
+
end.uniq.sort
|
96
91
|
end
|
97
92
|
|
98
|
-
def
|
99
|
-
|
100
|
-
x.is_a?(Range) ? simplify_enum(x.to_a, min, max) : x.to_i
|
101
|
-
end.flatten.uniq.sort.
|
102
|
-
select { |x| x >= min && x <= max }
|
103
|
-
end
|
93
|
+
def convert_month_name(value)
|
94
|
+
return value unless value.is_a?(String) || value.is_a?(Symbol)
|
104
95
|
|
105
|
-
|
106
|
-
case value
|
107
|
-
when :all
|
108
|
-
value
|
109
|
-
when Integer, Symbol, String
|
110
|
-
convert_months [value]
|
111
|
-
when Array, Range
|
112
|
-
ary = value.map { |x| convert_month x }
|
113
|
-
simplify_enum ary, 1, 12
|
114
|
-
else
|
115
|
-
raise "Unknown value (#{value.class.name}) #{value.inspect}] for month"
|
116
|
-
end
|
96
|
+
[ABBR_MONTHNAMES, MONTHNAMES].map { |ary| ary.index value.to_s.downcase }.compact.first
|
117
97
|
end
|
118
98
|
|
119
|
-
def
|
120
|
-
|
121
|
-
when Integer
|
122
|
-
raise "Unknown value (#{value.class.name}) #{value.inspect}] for month" if value < 1 || value > 12
|
123
|
-
value
|
124
|
-
when Array, Range
|
125
|
-
ary = value.map { |x| convert_month x }
|
126
|
-
simplify_enum ary, 1, 12
|
127
|
-
when String, Symbol
|
128
|
-
idx = [ABBR_MONTHNAMES, MONTHNAMES].map { |ary| ary.index value.to_s.downcase }.compact.first
|
129
|
-
raise "[#{idx}] Unknown value (#{value.class.name}) #{value.inspect}] for month" if idx.nil? || idx < 0 || idx > 11
|
130
|
-
idx + 1
|
131
|
-
else
|
132
|
-
raise "Unknown value (#{value.class.name}) #{value.inspect}] for month"
|
133
|
-
end
|
134
|
-
end
|
99
|
+
def convert_dow_name(value)
|
100
|
+
return value unless value.is_a?(String) || value.is_a?(Symbol)
|
135
101
|
|
136
|
-
|
137
|
-
case value
|
138
|
-
when :all
|
139
|
-
value
|
140
|
-
when Integer, Symbol, String
|
141
|
-
convert_dows [value]
|
142
|
-
when Array, Range
|
143
|
-
ary = value.map { |x| convert_dow x }
|
144
|
-
simplify_enum ary, 0, 6
|
145
|
-
else
|
146
|
-
raise "Unknown value (#{value.class.name}) #{value.inspect}] for dow"
|
147
|
-
end
|
102
|
+
[ABBR_DAYNAMES, DAYNAMES].map { |ary| ary.index value.to_s.downcase }.compact.first
|
148
103
|
end
|
149
104
|
|
150
|
-
def
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
ary = value.map { |x| convert_dow x }
|
156
|
-
simplify_enum ary, 0, 6
|
157
|
-
when String, Symbol
|
158
|
-
idx = [ABBR_DAYNAMES, DAYNAMES].map { |ary| ary.index value.to_s.downcase }.compact.first
|
159
|
-
raise "[#{idx}] Unknown value (#{value.class.name}) #{value.inspect}] for dow" if idx.nil? || idx < 0 || idx > 6
|
160
|
-
idx
|
161
|
-
else
|
162
|
-
raise "Unknown value (#{value.class.name}) #{value.inspect}] for dow"
|
163
|
-
end
|
105
|
+
def match?(field, exp_value)
|
106
|
+
value = get field
|
107
|
+
return true if value === :all
|
108
|
+
|
109
|
+
value.include? exp_value
|
164
110
|
end
|
165
111
|
|
166
112
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elected_scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adrian Madrid
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elected
|