elected_scheduler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 80e36753deee16c6d4ac311880ddf160076f5d60
4
+ data.tar.gz: 899d8490b59f93ebc242788a303f5084ebe3775a
5
+ SHA512:
6
+ metadata.gz: 938a658a6c1f60910b09e0f7ca214f8b96b139b15b251593e4bef4ae11919bb352eb29cf1b5d07ec53bcaed9337a163baed2cad4d761ac7b32668cafdee8388d
7
+ data.tar.gz: 9cf6aace4ec6d692d300f1af6b292a579697fa2fbb4cd22756e2fb4ae67dd17fcd357c8889afbfbdf9f2c2bdd6d8db6a6019e2adce6023d6ee8c29011181f3e0
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,27 @@
1
+ before_install: gem install bundler -v 1.10.6
2
+
3
+ language: ruby
4
+ jdk:
5
+ - oraclejdk8
6
+ rvm:
7
+ - ruby-2.0.0-p598
8
+ - ruby-2.2.0
9
+ - jruby-9.0.0.0
10
+ - jruby-head
11
+ services:
12
+ - redis-server
13
+ env:
14
+ global:
15
+ - REDIS_URL="redis://localhost:6379/0"
16
+ - JRUBY_OPTS="--server -J-Dfile.encoding=utf8 --2.0"
17
+ install:
18
+ - bundle install --jobs=3 --retry=3
19
+ script:
20
+ - bundle exec rspec
21
+ notifications:
22
+ email:
23
+ recipients:
24
+ - aemadrid@gmail.com
25
+ on_success: change
26
+ on_failure: change
27
+ sudo: false
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in elected_scheduler.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Adrian Madrid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # ElectedScheduler - A ruby distributed cron-like scheduler that only runs jobs on leader processes.
2
+
3
+ > Schedule your jobs at cron-like times (seconds included) and run the jobs only if the process is the current leader.
4
+ >
5
+ > This gem depends on the [elected](https://github.com/simple-finance/elected) ruby gem to select a leader and
6
+ > keep it for a set of time.
7
+
8
+ ElectedScheduler is a Spanglish-fluent gem so expect Spanish and English names all over. Pardon my French!
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'elected_scheduler'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install elected_scheduler
25
+
26
+ ## Documentation
27
+
28
+ [RubyDoc](http://www.rubydoc.info/gems/elected_scheduler/frames)
29
+
30
+ ## Usage example
31
+
32
+ ```ruby
33
+ # Let's open up a console and get the ball rolling.
34
+ $> bin/console
35
+
36
+ # Config your redis urls or use the default REDIS_URL environment variable by default.
37
+ Elected.redis_urls = ['redis://localhost:6379', 'redis://someotherhost:6379']
38
+
39
+ # Create a poller object that will keep your job definitions and distributed lock
40
+ # You will need to choose a key (common between similar processes)
41
+ # and a timeout (in ms) to select a new leader.
42
+ poller = Elected::Scheduler::Poller.new 'some_key', 30_000
43
+
44
+ # Let's create and add our first job
45
+ job1 = Elected::Scheduler::Job.new('j1')
46
+ job1.run { puts '>> job #1' }
47
+ job1.at seconds: [0,2]
48
+ poller << job1
49
+
50
+ # Let's create and add our second job
51
+ job2 = Elected::Scheduler::Job.new('j1'){ puts '>> job #2' }.add(seconds: [1,2])
52
+ poller << job2
53
+
54
+ # Now we'll tell the poller to start doing it's magic!
55
+ # after starting we'll get back control of the console
56
+ poller.start!
57
+
58
+ # Now in the background the poller will become the leader
59
+ # and run our jobs at the right times and we'll see some lines get printer over time
60
+ >> job #1
61
+ >> job #2
62
+ >> job #1
63
+ >> job #2
64
+ ...
65
+
66
+ # After some time we can stop the poller from running.
67
+ poller.stop
68
+ ```
69
+
70
+ To truly see the effect you might want to run these examples in two or more consoles so you can
71
+ see how it only executes the jobs on a process at a time.
72
+ After the timeout hits then the first process to request leader access will become the leader.
73
+ This way your jobs will get executed only once no matter how many servers you run them on.
74
+
75
+ ## Run tests
76
+
77
+ Make sure you have at least 1 redis instances up.
78
+
79
+ $ rake rspec
80
+
81
+ ## Disclaimer
82
+
83
+ The hard work of securing a distributed lock is all done through the great
84
+ [redlock](https://github.com/leandromoreira/redlock-rb) gem.
85
+ Thanks to [Leandro Moreira](https://github.com/leandromoreira) for his hard work.
86
+ This code, thanks to Redlock, implements an algorithm which is currently a proposal, it was not formally analyzed.
87
+ Make sure to understand how it works before using it in your production environments.
88
+ You can see discussion about this approach at
89
+ [reddit](http://www.reddit.com/r/programming/comments/2nt0nq/distributed_lock_using_redis_implemented_in_ruby/).
90
+
91
+ In general you need to be careful if the leader process dies and does not release on exit then until the timeout
92
+ hits no other process will run the scheduled jobs. Setting the right timeout and having enough processes running
93
+ will mitigate the possibilities of that scenario becoming a major problem.
94
+
95
+ ## Development
96
+
97
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
98
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
99
+
100
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update
101
+ the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for
102
+ the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
103
+
104
+ ## Contributing
105
+
106
+ Bug reports and pull requests are welcome on GitHub at https://github.com/simple-finance/elected_scheduler.
107
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere
108
+ to the [Contributor Covenant](contributor-covenant.org) code of conduct.
109
+
110
+
111
+ ## License
112
+
113
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
114
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'elected_scheduler'
5
+
6
+ require 'logger'
7
+
8
+ E = Elected
9
+ S = Elected::Scheduler
10
+ J = Elected::Scheduler::Job
11
+ P = Elected::Scheduler::Poller
12
+ SC = Elected::Scheduler::Schedule
13
+
14
+ require 'pry'
15
+ ARGV.clear
16
+ Pry.start
17
+
18
+
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'elected/scheduler/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'elected_scheduler'
8
+ spec.version = Elected::Scheduler::VERSION
9
+ spec.authors = ['Adrian Madrid']
10
+ spec.email = ['aemadrid@gmail.com']
11
+
12
+ spec.summary = %q{Run code blocks at selected times on leader processes.}
13
+ spec.description = %q{Run code blocks at scheduled times (seconds, minutes, hours, etc.) only on elected leader processes.}
14
+ spec.homepage = 'https://github.com/simple-finance/elected_scheduler'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'elected', '~> 0.2.0'
23
+ spec.add_dependency 'concurrent-ruby', '1.0.0.pre4'
24
+ spec.add_dependency 'concurrent-ruby-edge', '0.2.0.pre4'
25
+ spec.add_dependency 'pry'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.10'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec'
30
+ spec.add_development_dependency 'timecop'
31
+ end
@@ -0,0 +1,46 @@
1
+ module Elected
2
+ module Scheduler
3
+ class Job
4
+
5
+ include Logging
6
+
7
+ attr_reader :name, :callback, :schedules
8
+
9
+ def initialize(name, &blk)
10
+ @name = name.to_s
11
+ @callback = blk if block_given?
12
+ @schedules = Set.new
13
+ end
14
+
15
+ def run(&blk)
16
+ raise 'must pass a block' unless block_given?
17
+ @callback = blk
18
+ self
19
+ end
20
+
21
+ def at(options = {})
22
+ schedules.add Schedule.new(options)
23
+ self
24
+ end
25
+
26
+ def matches?(time)
27
+ schedules.any? { |s| s.matches? time }
28
+ end
29
+
30
+ def execute
31
+ callback.call
32
+ true
33
+ rescue Exception => e
34
+ error "Exception: #{e.class.name} : #{e.message}\n #{e.backtrace[0, 10].join("\n ")}"
35
+ return false
36
+ end
37
+
38
+ def to_s
39
+ %{#<#{self.class.name} name="#{name}" schedules=#{schedules.map { |x| x.to_s }.inspect}>}
40
+ end
41
+
42
+ alias :inspect :to_s
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,154 @@
1
+ module Elected
2
+ module Scheduler
3
+ class Poller
4
+
5
+ include Logging
6
+ extend Forwardable
7
+
8
+ attr_reader :key, :jobs
9
+
10
+ def_delegators :@senado, :timeout
11
+
12
+ def initialize(key, timeout = nil)
13
+ @senado ||= Senado.new key, timeout
14
+ @key = key
15
+ @jobs = Concurrent::Hash.new
16
+ end
17
+
18
+ def add(job)
19
+ @jobs[job.name] = job
20
+ self
21
+ end
22
+
23
+ alias :<< :add
24
+
25
+ def status
26
+ @status ||= :stopped
27
+ end
28
+
29
+ def starting?
30
+ status == :starting
31
+ end
32
+
33
+ def running?
34
+ status == :running
35
+ end
36
+
37
+ def stopping?
38
+ status == :stopping
39
+ end
40
+
41
+ def stopped?
42
+ status == :stopped
43
+ end
44
+
45
+ def start
46
+ unless stopped?
47
+ debug "cannot start on #{status} ..."
48
+ return false
49
+ end
50
+
51
+ debug "#{label} starting ..."
52
+ @status = :starting
53
+ start_polling_loop
54
+ @status = :running
55
+ debug "#{label} running poller!"
56
+ @status
57
+ rescue Exception => e
58
+ error "#{label} Exception thrown: #{e.class.name} : #{e.message}\n #{e.backtrace[0, 5].join("\n ")}"
59
+ end
60
+
61
+ def stop
62
+ unless running?
63
+ warn "cannot stop on #{status} ..."
64
+ return false
65
+ end
66
+
67
+ debug "#{label} stopping poller ..."
68
+ @status = :stopping
69
+ stop_polling_loop
70
+ @status = :stopped
71
+ debug "#{label} stopped poller!"
72
+ @status
73
+ end
74
+
75
+ def to_s
76
+ %{#<#{self.class.name} key="#{key}" timeout="#{timeout}" jobs=#{jobs.size}>}
77
+ end
78
+
79
+ alias :inspect :to_s
80
+
81
+ private
82
+
83
+ attr_reader :senado
84
+
85
+ def start_polling_loop
86
+ debug 'starting process loop ...'
87
+ @polling_loop_thread = Thread.new do
88
+ begin
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
93
+ end
94
+ end
95
+
96
+ def poll_and_process_loop
97
+ cnt = 0
98
+ while running?
99
+ cnt += 1
100
+ begin
101
+ debug "#{label(cnt)} calling poll_and_process_jobs while running?:#{running?} ..."
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
106
+ sleep_until_next_tick
107
+ end
108
+ end
109
+
110
+ def poll_and_process_jobs
111
+ if senado.leader?
112
+ start_time = Time.now
113
+ jobs.values.each { |job| process_job job, start_time }
114
+ else
115
+ sleep_for_slave
116
+ end
117
+ end
118
+
119
+ def process_job(job, time)
120
+ return false unless job.matches? time
121
+
122
+ Concurrent::Future.execute { job.execute }
123
+ end
124
+
125
+ def stop_polling_loop
126
+ if @polling_loop_thread
127
+ @polling_loop_thread.join
128
+ @polling_loop_thread.terminate
129
+ end
130
+ end
131
+
132
+ def label(cnt = nil)
133
+ "[#{key}:#{status}#{cnt ? ":#{cnt}" : ''}]"
134
+ end
135
+
136
+ def sleep_until_next_tick
137
+ start = Time.now
138
+ sleep 0.1 while Time.now.sec == start.sec
139
+ end
140
+
141
+ def sleep_for_slave(ratio = 0.25)
142
+ deadline = Time.now + (timeout / 1_000.0) * ratio
143
+ sleep 0.1 while Time.now < deadline
144
+ end
145
+
146
+ end
147
+
148
+ extend self
149
+
150
+ def poller
151
+ @poller ||= Poller.new
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,168 @@
1
+ module Elected
2
+ module Scheduler
3
+ class Schedule
4
+
5
+ include Logging
6
+
7
+ FIELDS = { seconds: :sec, minutes: :min, hours: :hour, days: :day, months: :month, dows: :wday }.freeze
8
+
9
+ ABBR_DAYNAMES = %w{ sun mon tue wed thu fri sat }
10
+ DAYNAMES = %w{ sunday monday tuesday wednesday thursday friday saturday }
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 }
14
+
15
+ def self.defaults
16
+ @defaults ||= { seconds: [0] }
17
+ end
18
+
19
+ def initialize(options = {})
20
+ @options = options
21
+ setup *FIELDS.keys
22
+ end
23
+
24
+ def matches?(time)
25
+ FIELDS.all? { |field, meth| match? field, time.send(meth) }
26
+ end
27
+
28
+ def to_cron_s
29
+ FIELDS.keys.map do |field|
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(' ')
38
+ end
39
+
40
+ alias :to_s :to_cron_s
41
+ alias :inspect :to_cron_s
42
+
43
+ private
44
+
45
+ def get(field)
46
+ instance_variable_get "@#{field}"
47
+ end
48
+
49
+ alias :[] :get
50
+
51
+ def set(field, value)
52
+ instance_variable_set "@#{field}", convert(field, value)
53
+ end
54
+
55
+ alias :[]= :set
56
+
57
+ def default(field)
58
+ self.class.defaults[field] || :all
59
+ end
60
+
61
+ def setup(*fields)
62
+ fields.each do |field|
63
+ set field, @options.fetch(field, default(field))
64
+ end
65
+ end
66
+
67
+ def convert(field, value)
68
+ case field
69
+ when :seconds, :minutes, :hours, :days
70
+ convert_numbers field, value
71
+ when :months
72
+ convert_months value
73
+ when :dows
74
+ convert_dows value
75
+ end
76
+ end
77
+
78
+ def match?(field, exp_value)
79
+ value = get field
80
+ return true if value === :all
81
+ value.include? exp_value
82
+ end
83
+
84
+ def convert_numbers(field, value)
85
+ max = { seconds: 60, minutes: 60, hours: 23, days: 31 }[field]
86
+ case value
87
+ when :all
88
+ value
89
+ when Integer
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
96
+ end
97
+
98
+ def simplify_enum(ary, min, max)
99
+ ary.to_a.flatten.map do |x|
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
104
+
105
+ def convert_months(value)
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
117
+ end
118
+
119
+ def convert_month(value)
120
+ case value
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
135
+
136
+ def convert_dows(value)
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
148
+ end
149
+
150
+ def convert_dow(value)
151
+ case value
152
+ when Integer
153
+ value
154
+ when Array, Range
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
164
+ end
165
+
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,5 @@
1
+ module Elected
2
+ module Scheduler
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ require 'forwardable'
2
+ require 'concurrent'
3
+
4
+ require 'elected'
5
+ require 'elected/scheduler/version'
6
+
7
+ module Elected
8
+ module Scheduler
9
+ end
10
+ end
11
+
12
+ require 'elected/scheduler/schedule'
13
+ require 'elected/scheduler/job'
14
+ require 'elected/scheduler/poller'
@@ -0,0 +1 @@
1
+ require 'elected/scheduler'
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elected_scheduler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adrian Madrid
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: elected
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0.pre4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0.pre4
41
+ - !ruby/object:Gem::Dependency
42
+ name: concurrent-ruby-edge
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.0.pre4
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.2.0.pre4
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Run code blocks at scheduled times (seconds, minutes, hours, etc.) only
126
+ on elected leader processes.
127
+ email:
128
+ - aemadrid@gmail.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - .rspec
135
+ - .travis.yml
136
+ - CODE_OF_CONDUCT.md
137
+ - Gemfile
138
+ - LICENSE.txt
139
+ - README.md
140
+ - Rakefile
141
+ - bin/console
142
+ - bin/setup
143
+ - elected_scheduler.gemspec
144
+ - lib/elected/scheduler.rb
145
+ - lib/elected/scheduler/job.rb
146
+ - lib/elected/scheduler/poller.rb
147
+ - lib/elected/scheduler/schedule.rb
148
+ - lib/elected/scheduler/version.rb
149
+ - lib/elected_scheduler.rb
150
+ homepage: https://github.com/simple-finance/elected_scheduler
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.0.14
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Run code blocks at selected times on leader processes.
174
+ test_files: []