sidekiq-dejavu 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec3f881a31547703f392561450f5377d121bd295
4
- data.tar.gz: 9b329b0b2bc47ee1265713e66dbaf84f3872661c
3
+ metadata.gz: 90086538bf6bc73e5b5a0b27034c72189c1f2221
4
+ data.tar.gz: 1a58258aef38bbf3f912cb74132c0620a337c1dd
5
5
  SHA512:
6
- metadata.gz: e1fcd1f0d38a22fab9a7f0375ba943760a9445e51d3eb7a91b67bc73c5e30d69faeaadcf29557d09b381dc160f00c73e1f824bca5ccfa5f72cc8ef435c5bff37
7
- data.tar.gz: 7b0f0e51af703b03fd560a51dad5ff9a76c6ab43913d0e6c4f5a0d8ac41ae5fb30824eb1507cc0a9ed61bf11b3005977e7035ed0835a01769596bf4a6e634de9
6
+ metadata.gz: 98f4470432dfa3579a25f9ce56eb19ae56a10155c31dfc02de1d0eca73ea33950f77502f07f68aa890e280e42e5e6a8b8ddc3dca7b7bfa60b7a1b0d900203018
7
+ data.tar.gz: 75357647e815519951176b27b8ef9d1bfa58e546ce7a358f7eb6097c508c2333ec5d567c2b62a377517f655f2fda02a8803513fe880f7e98fe1c5b9ac8ca1ba4
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ services:
5
+ - redis-server
6
+ rvm:
7
+ - 2.1
8
+ - 2.0.0
9
+ - 2.2
10
+ - jruby
11
+ - jruby-head
12
+ - rbx-2
13
+ env:
14
+ - SIDEKIQ='~> 3.5'
15
+ - SIDEKIQ='~> 4.0'
16
+ matrix:
17
+ allow_failures:
18
+ - rvm: rbx-2
19
+ - rvm: jruby-head
data/Gemfile CHANGED
@@ -2,3 +2,9 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in sidekiq-dejavu.gemspec
4
4
  gemspec
5
+
6
+ gem "sidekiq", ENV['SIDEKIQ'] if ENV['SIDEKIQ']
7
+ gem "minitest"
8
+ gem "minitest-utils"
9
+ gem "redis-namespace"
10
+ gem "pry" unless ENV['CI']
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Sidekiq::Dejavu
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/sidekiq-dejavu.svg)](https://rubygems.org/gems/sidekiq-dejavu)
4
+ [![Build Status](https://travis-ci.org/felixbuenemann/sidekiq-dejavu.svg)](https://travis-ci.org/felixbuenemann/sidekiq-dejavu)
5
+
3
6
  Dejavu is a clockless scheduler that uses Sidekiq's built-in scheduling.
4
7
 
5
8
  Most scheduling solutions for Sidekiq require either a separate cron like process
@@ -27,6 +30,8 @@ and all re-scheduling themselves. This is because jobs get re-scheduled if there
27
30
  schedule with the same name, but only the scheduled jobs queue is inspected.
28
31
  Depending on the intervals used it is possible that the job gets scheduled and run again before
29
32
  the other jobs can see the scheduled job, so they get re-scheduled as well.
33
+ - It's possible for jobs to be scheduled multiple times, if multiple processes load the same schedule
34
+ at the same time due to missing locks around loading the schedule.
30
35
 
31
36
  ## Installation
32
37
 
@@ -46,25 +51,27 @@ Or install it yourself as:
46
51
 
47
52
  You can configure schedule tasks in your config/sidekiq.yml:
48
53
 
49
- :schedule:
50
- hello_world:
51
- interval: 10
52
- class: HelloWorldWorker
53
- queue: hello
54
- retry: false
55
- backtrace: false
56
- args:
57
- - Hello
58
- - World
59
- cleanup:
60
- interval: '30 5 * * *' # every day at 5:30
61
- class: HelloWorldWorker
62
- queue: hello
63
- retry: false
64
- backtrace: false
65
- args:
66
- - Hello
67
- - World
54
+ ```yaml
55
+ :schedule:
56
+ hello_world:
57
+ interval: 10
58
+ class: HelloWorldWorker
59
+ queue: hello
60
+ retry: false
61
+ backtrace: false
62
+ args:
63
+ - Hello
64
+ - World
65
+ cleanup:
66
+ interval: '30 5 * * *' # every day at 5:30
67
+ class: HelloWorldWorker
68
+ queue: hello
69
+ retry: false
70
+ backtrace: false
71
+ args:
72
+ - Hello
73
+ - World
74
+ ```
68
75
 
69
76
  Interval can be specified in seconds or as a cron expression, including vixie cron syntax.
70
77
 
@@ -72,6 +79,8 @@ If a job fails or takes longer than an interval it will be retried at the next i
72
79
 
73
80
  You also need to set all options for the job, because currently a workers `sidekiq_options` are ignored. The minium required options for a schedule are `interval` and `class`. The same worker can be scheduled multiple times by using different schedule names (keys) in the config.
74
81
 
82
+ Note that by default sidekiq polls scheduled jobs roughly every 15 seconds, which means in order to run jobs at shorter intervals youd'd have to tune `config.average_scheduled_poll_interval` to a lower value.
83
+
75
84
  ## Contributing
76
85
 
77
86
  1. Fork it ( https://github.com/felixbuenemann/sidekiq-dejavu/fork )
data/Rakefile CHANGED
@@ -1,2 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/testtask"
2
3
 
4
+ Rake::TestTask.new(:test) do |test|
5
+ # test.warning = true
6
+ test.pattern = "test/**/test_*.rb"
7
+ end
8
+
9
+ task :default => :test
@@ -1,3 +1,4 @@
1
+ require "sidekiq"
1
2
  require "sidekiq/dejavu"
2
3
 
3
4
  Sidekiq.configure_server do |config|
@@ -3,6 +3,13 @@ require 'parse-cron'
3
3
  module Sidekiq
4
4
  module Dejavu
5
5
  module Helper
6
+ def valid_cron?(interval)
7
+ CronParser.new(interval)
8
+ true
9
+ rescue ArgumentError
10
+ false
11
+ end
12
+
6
13
  def next_timestamp(interval, time = Time.now)
7
14
  CronParser.new(interval).next(time).to_f
8
15
  rescue ArgumentError
@@ -10,7 +17,8 @@ module Sidekiq
10
17
  end
11
18
 
12
19
  def next_randomized_timestamp(interval, time = Time.now)
13
- CronParser.new(interval).next(time).to_f
20
+ diff = CronParser.new(interval).next(time).to_f - time.to_f
21
+ time.to_f + Random.rand(diff)
14
22
  rescue ArgumentError
15
23
  time.to_f + Random.rand(interval.to_f)
16
24
  end
@@ -3,10 +3,11 @@ module Sidekiq
3
3
  class Manager
4
4
  include Helper
5
5
 
6
- attr_reader :schedules
6
+ attr_accessor :schedules, :scheduled_set
7
7
 
8
- def initialize(schedules)
8
+ def initialize(schedules = {}, scheduled_set = Sidekiq::ScheduledSet.new)
9
9
  @schedules = schedules
10
+ @scheduled_set = scheduled_set
10
11
  end
11
12
 
12
13
  def reload_schedule!
@@ -14,12 +15,23 @@ module Sidekiq
14
15
  add_new_schedules
15
16
  end
16
17
 
18
+ def scheduled_jobs
19
+ scheduled_set.select { |job| job.item.has_key? 'schedule' }
20
+ end
21
+
17
22
  private
18
23
 
19
24
  def clear_changed_schedules
20
25
  scheduled_jobs.each do |job|
21
26
  item = job.item
22
27
  name = item['schedule']
28
+
29
+ unless schedules.has_key? name
30
+ Sidekiq.logger.info "Clearing schedule #{name} (not listed in config)."
31
+ job.delete
32
+ next
33
+ end
34
+
23
35
  schedule_options = schedules[name]
24
36
  schedule_options['args'] = Array(schedule_options['args'])
25
37
  item_options = item.select { |k,v| schedule_options.keys.include? k }
@@ -27,27 +39,25 @@ module Sidekiq
27
39
  if item_options != schedule_options
28
40
  Sidekiq.logger.info "Clearing schedule #{name} (config changed)."
29
41
  job.delete
30
- else
31
- schedules.delete(name)
32
42
  end
33
43
  end
34
44
  end
35
45
 
36
46
  def add_new_schedules
47
+ existing = scheduled_jobs.map { |job| job.item['schedule'] }
48
+
37
49
  schedules.each do |name, options|
50
+ next if existing.include? name
51
+
38
52
  args = Array(options['args'])
39
53
  interval = options['interval']
40
- first_run = next_randomized_timestamp(interval)
54
+ first_run = valid_cron?(interval) ? next_timestamp(interval) : next_randomized_timestamp(interval)
41
55
  job = options.merge('args' => args, 'schedule' => name, 'at' => first_run)
42
56
 
43
57
  Sidekiq.logger.info "Scheduling #{name} for first run at #{Time.at first_run}."
44
58
  Sidekiq::Client.push(job)
45
59
  end
46
60
  end
47
-
48
- def scheduled_jobs
49
- Sidekiq::ScheduledSet.new.select { |job| schedules.keys.include? job.item['schedule'] }
50
- end
51
61
  end
52
62
  end
53
63
  end
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Dejavu
3
- VERSION = "0.0.3"
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "sidekiq", ">= 3", "< 5"
21
+ spec.add_dependency "sidekiq", ">= 3", "< 6"
22
22
  spec.add_dependency "parse-cron", "~> 0.1.4"
23
23
  spec.add_development_dependency "bundler", "~> 1.6"
24
24
  spec.add_development_dependency "rake"
@@ -0,0 +1,6 @@
1
+ class DummyWorker
2
+ include Sidekiq::Worker
3
+ sidekiq_options :queue => :dejavu_test
4
+ def perform(*)
5
+ end
6
+ end
@@ -0,0 +1,52 @@
1
+ # disable minitest/parallel threads
2
+ ENV["N"] = "0"
3
+
4
+ require "bundler/setup"
5
+
6
+ require "sidekiq"
7
+ require "sidekiq/api"
8
+ require "sidekiq/testing"
9
+
10
+ require "minitest/autorun"
11
+ require "yaml"
12
+ require "pry" unless ENV['CI']
13
+
14
+ require "sidekiq/dejavu"
15
+
16
+ require_relative "fixtures/dummy_worker"
17
+
18
+ require 'sidekiq/redis_connection'
19
+ REDIS_URL = ENV['REDIS_URL'] || 'redis://localhost/15'
20
+ REDIS = Sidekiq::RedisConnection.create(:url => REDIS_URL, :namespace => 'dejavu_test')
21
+
22
+ Sidekiq.configure_client do |config|
23
+ config.redis = { :url => REDIS_URL, :namespace => 'dejavu_test' }
24
+ end
25
+
26
+ Sidekiq::Testing.disable!
27
+ Sidekiq::Testing.server_middleware do |chain|
28
+ chain.add Sidekiq::Dejavu::Middleware::Server::Scheduler
29
+ end
30
+
31
+ class Sidekiq::Dejavu::Test < MiniTest::Test
32
+ attr_reader :log, :scheduled_set
33
+
34
+ def setup
35
+ # capture logger
36
+ @old_logger = Sidekiq.logger
37
+ @log = StringIO.new
38
+ Sidekiq.logger = Logger.new(@log)
39
+ # clear scheduled set
40
+ @scheduled_set = Sidekiq::ScheduledSet.new
41
+ @scheduled_set.clear
42
+ end
43
+
44
+ def teardown
45
+ Sidekiq::Worker.drain_all
46
+ Sidekiq.logger = @old_logger
47
+ end
48
+
49
+ def assert_logged(regex)
50
+ assert_match log, regex
51
+ end
52
+ end
@@ -0,0 +1,61 @@
1
+ require_relative "helper"
2
+
3
+ class TestHelper < Sidekiq::Dejavu::Test
4
+ include Sidekiq::Dejavu::Helper
5
+
6
+ def time
7
+ @time ||= Time.now
8
+ end
9
+
10
+ def test_valid_cron_with_valid_cron
11
+ interval = '* * * * *'
12
+ assert_equal true, valid_cron?(interval)
13
+ end
14
+
15
+ def test_valid_cron_with_interval
16
+ [10, 10.0, '10', '10.0'].each do |interval|
17
+ assert_equal false, valid_cron?(interval)
18
+ end
19
+ end
20
+
21
+ def test_valid_cron_with_garbage
22
+ interval = 'garbage'
23
+ assert_equal false, valid_cron?(interval)
24
+ end
25
+
26
+ def test_next_timestamp_cron
27
+ interval = '* * * * *'
28
+ expected = time.to_f + (60 - time.to_f % 60)
29
+ assert_equal expected, next_timestamp(interval, time)
30
+ end
31
+
32
+ def test_next_timestamp_numeric
33
+ expected = time.to_f + 10.0
34
+ [10, 10.0, '10', '10.0'].each do |interval|
35
+ assert_equal expected, next_timestamp(interval, time)
36
+ end
37
+ end
38
+
39
+ def test_next_randomized_timestamp_cron
40
+ interval = '* * * * *'
41
+ expected = time.to_f + (60 - time.to_f % 60)
42
+ timestamp = next_randomized_timestamp(interval, time)
43
+ another_timestamp = next_randomized_timestamp(interval, time)
44
+
45
+ assert_in_delta expected, timestamp, 60.0
46
+ refute_equal expected, timestamp
47
+ refute_equal timestamp, another_timestamp
48
+ end
49
+
50
+ def test_next_randomized_timestamp_numeric
51
+ expected = time.to_f + 10.0
52
+ [10, 10.0, '10', '10.0'].each do |interval|
53
+ timestamp = next_randomized_timestamp(interval, time)
54
+ another_timestamp = next_randomized_timestamp(interval, time)
55
+
56
+ assert_in_delta expected, timestamp, 10.0
57
+ refute_equal expected, timestamp
58
+ refute_equal timestamp, another_timestamp
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,121 @@
1
+ require_relative "helper"
2
+
3
+ class TestManager < Sidekiq::Dejavu::Test
4
+ def schedules
5
+ @schedules ||= YAML.load(<<-YAML).freeze
6
+ foo:
7
+ interval: 10
8
+ class: DummyWorker
9
+ args:
10
+ - fooarg
11
+ bar:
12
+ interval: '30 5 * * *'
13
+ class: DummyWorker
14
+ args:
15
+ - bararg
16
+ YAML
17
+ end
18
+
19
+ def test_reload_schedule!
20
+ manager = Sidekiq::Dejavu::Manager.new schedules
21
+
22
+ assert_equal 0, scheduled_set.size
23
+ manager.reload_schedule!
24
+ assert_equal 2, scheduled_set.size
25
+
26
+ foo = scheduled_set.find { |job| job.item['schedule'] == 'foo' }
27
+ assert foo
28
+ assert_equal 10, foo.item['interval']
29
+ assert_equal 'DummyWorker', foo.item['class']
30
+ assert_equal %w(fooarg), foo.item['args']
31
+
32
+ bar = scheduled_set.find { |job| job.item['schedule'] == 'bar' }
33
+ assert bar
34
+ assert_equal '30 5 * * *', bar.item['interval']
35
+ assert_equal 'DummyWorker', bar.item['class']
36
+ assert_equal %w(bararg), bar.item['args']
37
+ end
38
+
39
+ def test_reload_schedule_clears_old_schedules
40
+ old_schedules = YAML.load <<-YAML
41
+ old:
42
+ interval: 15
43
+ class: DummyWorker
44
+ YAML
45
+
46
+ manager = Sidekiq::Dejavu::Manager.new old_schedules
47
+ assert_equal 0, scheduled_set.size
48
+ manager.reload_schedule!
49
+ assert_equal 1, scheduled_set.size
50
+
51
+ assert scheduled_set.find { |job| job.item['schedule'] == 'old' }
52
+
53
+ manager.schedules = {}
54
+ assert_equal 1, scheduled_set.size
55
+ manager.reload_schedule!
56
+ assert_equal 0, scheduled_set.size
57
+
58
+ refute scheduled_set.find { |job| job.item['schedule'] == 'old' }
59
+ end
60
+
61
+ def test_reload_schedule_updates_existing_schedules
62
+ old_schedules = YAML.load <<-YAML
63
+ foo:
64
+ interval: 15
65
+ class: DummyWorker
66
+ args:
67
+ - oldfooarg
68
+ bar:
69
+ interval: '15 4 * * *'
70
+ class: DummyWorker
71
+ args:
72
+ - oldbararg
73
+ YAML
74
+
75
+ manager = Sidekiq::Dejavu::Manager.new old_schedules
76
+
77
+ assert_equal 0, scheduled_set.size
78
+ manager.reload_schedule!
79
+ assert_equal 2, scheduled_set.size
80
+
81
+ foo = scheduled_set.find { |job| job.item['schedule'] == 'foo' }
82
+ assert foo
83
+ assert_equal 15, foo.item['interval']
84
+ assert_equal 'DummyWorker', foo.item['class']
85
+ assert_equal %w(oldfooarg), foo.item['args']
86
+
87
+ bar = scheduled_set.find { |job| job.item['schedule'] == 'bar' }
88
+ assert bar
89
+ assert_equal '15 4 * * *', bar.item['interval']
90
+ assert_equal 'DummyWorker', bar.item['class']
91
+ assert_equal %w(oldbararg), bar.item['args']
92
+
93
+ manager.schedules = schedules
94
+ assert_equal 2, scheduled_set.size
95
+ manager.reload_schedule!
96
+ assert_equal 2, scheduled_set.size
97
+
98
+ foo = scheduled_set.find { |job| job.item['schedule'] == 'foo' }
99
+ assert foo
100
+ assert_equal 10, foo.item['interval']
101
+ assert_equal 'DummyWorker', foo.item['class']
102
+ assert_equal %w(fooarg), foo.item['args']
103
+
104
+ bar = scheduled_set.find { |job| job.item['schedule'] == 'bar' }
105
+ assert bar
106
+ assert_equal '30 5 * * *', bar.item['interval']
107
+ assert_equal 'DummyWorker', bar.item['class']
108
+ assert_equal %w(bararg), bar.item['args']
109
+ end
110
+
111
+ def test_reload_schedule_keeps_unchanged_schedules
112
+ manager = Sidekiq::Dejavu::Manager.new schedules
113
+ assert_equal 0, scheduled_set.size
114
+ manager.reload_schedule!
115
+ assert_equal 2, scheduled_set.size
116
+
117
+ manager.schedules = schedules
118
+ manager.reload_schedule!
119
+ assert_equal 2, scheduled_set.size
120
+ end
121
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-dejavu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felix Buenemann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-15 00:00:00.000000000 Z
11
+ date: 2017-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '3'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '5'
22
+ version: '6'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '3'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '5'
32
+ version: '6'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: parse-cron
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +81,7 @@ extensions: []
81
81
  extra_rdoc_files: []
82
82
  files:
83
83
  - ".gitignore"
84
+ - ".travis.yml"
84
85
  - Gemfile
85
86
  - LICENSE.txt
86
87
  - README.md
@@ -92,6 +93,10 @@ files:
92
93
  - lib/sidekiq/dejavu/middleware/server/scheduler.rb
93
94
  - lib/sidekiq/dejavu/version.rb
94
95
  - sidekiq-dejavu.gemspec
96
+ - test/fixtures/dummy_worker.rb
97
+ - test/helper.rb
98
+ - test/test_helper.rb
99
+ - test/test_manager.rb
95
100
  homepage: https://github.com/felixbuenemann/sidekiq-dejavu
96
101
  licenses:
97
102
  - MIT
@@ -112,8 +117,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
117
  version: '0'
113
118
  requirements: []
114
119
  rubyforge_project:
115
- rubygems_version: 2.4.5.1
120
+ rubygems_version: 2.6.12
116
121
  signing_key:
117
122
  specification_version: 4
118
123
  summary: Dejavu is a clockless scheduler that uses Sidekiq's built-in scheduling
119
- test_files: []
124
+ test_files:
125
+ - test/fixtures/dummy_worker.rb
126
+ - test/helper.rb
127
+ - test/test_helper.rb
128
+ - test/test_manager.rb