rekiq 0.9.3 → 1.0.0

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: dd9eb984f52070bc25eb6b801d09e6bfd4131042
4
- data.tar.gz: 0c8efe50023481085da80b2f0a420ddab4d9b4d2
3
+ metadata.gz: c35103e24fdf19a9103b5413045b7b9519f31b5c
4
+ data.tar.gz: d370ec60d8dfa653b1fc2baab31b4bc9e63ec459
5
5
  SHA512:
6
- metadata.gz: b7a0a66f5caa4b38e28e78f2b71653e8550b25a35c21dd8294c5de5d3e954c00f645cc0202557ac3faeba3cbe1dfeaa9906d1d72f5a9f5a7a364fc651060c090
7
- data.tar.gz: 18b1364a70c24c790e7b2971ac379958a7dd6a81661c8b496cd7eabce7fb75428ad10bd7e42e078c7e156c19a74a86f4a8b3df607afd77570473547ca550385c
6
+ metadata.gz: 8cce22c704902a039b2dfdf721109c292199310a1bcab75398e4d3bcaa2c4cee1109f922ab34f85c59d8d3fbdf61fac02701091dde78f3709f56bbfade159205
7
+ data.tar.gz: afd6108ae9af9a7285b8aa8afc7ec4af58892d0dcba5c71e1208150c36169230e6cb58207885e5aa889a7611e8b736c932157a97b8e3e23c38e13711992497bf
@@ -4,28 +4,34 @@ module Rekiq
4
4
  class Configuration
5
5
  include Validator
6
6
 
7
- attr_accessor :shift, :schedule_post_work, :schedule_expired,
8
- :expiration_margin
7
+ attr_accessor :schedule_post_work, :work_time_shift, :work_time_tolerance,
8
+ :schedule_expired
9
9
 
10
- validate :shift, :numeric
11
- validate :schedule_post_work, :bool
12
- validate :expiration_margin, :numeric, greater_than_or_equal_to: 0
13
- validate :schedule_expired, :bool
10
+ validate :schedule_post_work, :bool
11
+ validate :work_time_shift, :numeric
12
+ validate :work_time_tolerance, :numeric, greater_than_or_equal_to: 0
13
+ validate :schedule_expired, :bool
14
14
 
15
15
  def initialize
16
- # the value of the shift to apply relative to time returned by schedule
17
- self.shift = 0
18
-
19
- # if next work is scheduled after or before the worker completes
20
- self.schedule_post_work = false
21
-
22
- # indicates the margin after which a work is considered expired
23
- # default to 0
24
- self.expiration_margin = 0
25
-
26
- # if expired works are to be scheduled
27
- # an expired work is a work that has a time bellow current_time - margin
28
- self.schedule_expired = false
16
+ # indicates if next work is scheduled after or before the worker completes
17
+ # this is relevant when we want to guarantee that workers do not run in paralel
18
+ # default false
19
+ @schedule_post_work = false
20
+
21
+ # indicates a shift, in seconds, to apply to event time returned from schedule
22
+ # to calculate the work_time
23
+ # default 0
24
+ @work_time_shift = 0
25
+
26
+ # indicates the tolerance, in seconds, for work_time relative to current time
27
+ # default 0 and must be greater than or equal to 0
28
+ @work_time_tolerance = 0
29
+
30
+ # indicates if expired work_times are to be scheduled
31
+ # a work_time is considered expired when it's before current time minus
32
+ # work_time_tolerance
33
+ # default false
34
+ @schedule_expired = false
29
35
  end
30
36
  end
31
37
 
@@ -0,0 +1,115 @@
1
+ require 'rekiq/validator'
2
+ require 'rekiq/configuration'
3
+
4
+ module Rekiq
5
+ class Contract
6
+ include Validator
7
+
8
+ attr_accessor :schedule, :cancel_args, :addon, :schedule_post_work,
9
+ :work_time_shift, :work_time_tolerance, :schedule_expired
10
+
11
+ validate :schedule, :schedule
12
+ validate :schedule_post_work, :bool, allow_nil: true
13
+ validate :work_time_shift, :numeric, allow_nil: true
14
+ validate :work_time_tolerance, :numeric, allow_nil: true,
15
+ greater_than_or_equal_to: 0
16
+ validate :schedule_expired, :bool, allow_nil: true
17
+
18
+ class << self
19
+ def from_hash(hash)
20
+ new \
21
+ 'schedule' => Marshal.load(hash['s'].encode('ISO-8859-1')),
22
+ 'cancel_args' => hash['ca'],
23
+ 'addon' => hash['ao'],
24
+ 'schedule_post_work' => hash['pw'],
25
+ 'work_time_shift' => hash['ws'],
26
+ 'work_time_tolerance' => hash['wt'],
27
+ 'schedule_expired' => hash['se']
28
+ end
29
+ end
30
+
31
+ def initialize(attributes = {})
32
+ @schedule = attributes['schedule']
33
+ @cancel_args = attributes['cancel_args']
34
+ @addon = attributes['addon']
35
+ @schedule_post_work = attributes['schedule_post_work']
36
+ @work_time_shift = attributes['work_time_shift']
37
+ @work_time_tolerance = attributes['work_time_tolerance']
38
+ @schedule_expired = attributes['schedule_expired']
39
+ end
40
+
41
+ def to_hash
42
+ {
43
+ 's' => Marshal.dump(schedule).force_encoding('ISO-8859-1').encode('UTF-8'),
44
+ 'ca' => cancel_args,
45
+ 'ao' => addon,
46
+ 'pw' => schedule_post_work,
47
+ 'ws' => work_time_shift,
48
+ 'wt' => work_time_tolerance,
49
+ 'se' => schedule_expired
50
+ }.delete_if { |k, v| v.nil? }
51
+ end
52
+
53
+ def initial_work_time(from)
54
+ from = (shift > 0 ? from - shift : from) - tolerance
55
+ calculate_work_time(from)
56
+ end
57
+
58
+ def next_work_time(previous_work_time)
59
+ from = previous_work_time - shift
60
+ calculate_work_time(from)
61
+ end
62
+
63
+ def schedule_post_work?
64
+ unless schedule_post_work.nil?
65
+ schedule_post_work
66
+ else
67
+ Rekiq.configuration.schedule_post_work
68
+ end
69
+ end
70
+
71
+ protected
72
+
73
+ def calculate_work_time(from)
74
+ if schedule_expired?
75
+ from = schedule.next_occurrence(from)
76
+ work_time = from.nil? ? nil : from + shift
77
+ else
78
+ begin
79
+ from = schedule.next_occurrence(from)
80
+ work_time = from.nil? ? nil : from + shift
81
+ end until work_time.nil? || work_time > expiration_time
82
+ end
83
+
84
+ work_time
85
+ end
86
+
87
+ def expiration_time
88
+ Time.now - tolerance
89
+ end
90
+
91
+ def shift
92
+ unless work_time_shift.nil?
93
+ work_time_shift
94
+ else
95
+ Rekiq.configuration.work_time_shift
96
+ end
97
+ end
98
+
99
+ def tolerance
100
+ unless work_time_tolerance.nil?
101
+ work_time_tolerance
102
+ else
103
+ Rekiq.configuration.work_time_tolerance
104
+ end
105
+ end
106
+
107
+ def schedule_expired?
108
+ unless schedule_expired.nil?
109
+ schedule_expired
110
+ else
111
+ Rekiq.configuration.schedule_expired
112
+ end
113
+ end
114
+ end
115
+ end
@@ -2,8 +2,7 @@ module Rekiq
2
2
  module Middleware
3
3
  class Utils
4
4
  def call(worker, msg, queue)
5
- if worker.respond_to?(:scheduled_work_time) and
6
- msg.key?('rq:at')
5
+ if msg.key?('rq:at')
7
6
  worker.scheduled_work_time = Time.at(msg['rq:at'].to_f).utc
8
7
  end
9
8
 
@@ -1,6 +1,6 @@
1
1
  require 'sidekiq'
2
2
  require 'sidekiq/util'
3
- require 'rekiq/job'
3
+ require 'rekiq/contract'
4
4
  require 'rekiq/scheduler'
5
5
 
6
6
  module Rekiq
@@ -9,60 +9,49 @@ module Rekiq
9
9
  include ::Sidekiq::Util
10
10
 
11
11
  def call(worker, msg, queue)
12
- return yield unless msg.key?('rq:job')
12
+ return yield unless msg.key?('rq:ctr')
13
13
 
14
- setup_vars(worker, msg, queue)
14
+ @worker = worker
15
+ @worker_name = worker.class.name
16
+ @msg = msg
17
+ @queue = queue
18
+ @contract = Contract.from_hash(msg['rq:ctr'])
15
19
 
16
20
  if cancel_worker?
17
- return logger.info 'worker canceled by rekiq cancel method'
21
+ return logger.info "worker #{@worker_name} was canceled"
18
22
  end
19
23
 
20
- return yield unless msg.key?('rq:schdlr')
21
-
22
- msg.delete('rq:schdlr')
24
+ if msg.key?('rq:sdl')
25
+ msg.delete('rq:sdl')
26
+ else
27
+ return yield
28
+ end
23
29
 
24
30
  begin
25
- reschedule unless @job.schedule_post_work?
31
+ reschedule unless @contract.schedule_post_work?
26
32
  yield
27
33
  ensure
28
- reschedule if @job.schedule_post_work?
34
+ reschedule if @contract.schedule_post_work?
29
35
  end
30
36
  end
31
37
 
32
38
  protected
33
39
 
34
- def setup_vars(worker, msg, queue)
35
- @cancel_method = worker.rekiq_cancel_method
36
- @cancel_args = msg['rq:ca']
37
- @worker = worker
38
- @worker_name = worker.class.name
39
- @queue = queue
40
- @args = msg['args']
41
- @job = Job.from_array(msg['rq:job'])
42
- @addon = msg['rq:addon']
43
- @scheduled_work_time = Time.at(msg['rq:at'].to_f)
44
- end
45
-
46
40
  def cancel_worker?
47
- !@cancel_method.nil? and @worker.send(@cancel_method, *@cancel_args)
48
- rescue StandardError => s
49
- raise CancelMethodInvocationError,
50
- "error while invoking rekiq_cancel_method with message " \
51
- "#{s.message}",
52
- s.backtrace
41
+ @worker.cancel_rekiq_worker?(*@contract.cancel_args)
53
42
  end
54
43
 
55
44
  def reschedule
56
45
  jid, work_time =
57
46
  Rekiq::Scheduler
58
- .new(@worker_name, @queue, @args, @job, @addon, @cancel_args)
59
- .schedule_from_work_time(@scheduled_work_time)
47
+ .new(@worker_name, @queue, @msg['args'], @contract)
48
+ .schedule_next_work(Time.at(@msg['rq:at'].to_f))
60
49
 
61
50
  unless jid.nil?
62
- logger.info "recurring work for #{@worker_name} scheduled for " \
51
+ logger.info "worker #{@worker_name} scheduled for " \
63
52
  "#{work_time} with jid #{jid}"
64
53
  else
65
- logger.info 'recurrence terminated, job terminated'
54
+ logger.info 'recurrence terminated, worker terminated'
66
55
  end
67
56
  end
68
57
  end
@@ -1,41 +1,38 @@
1
1
  module Rekiq
2
2
  class Scheduler
3
- def initialize(worker_name, queue, args, job, addon, cancel_args)
3
+ def initialize(worker_name, queue, args, contract)
4
4
  @worker_name = worker_name
5
5
  @queue = queue
6
6
  @args = args
7
- @job = job
8
- @addon = addon
9
- @cancel_args = cancel_args
7
+ @contract = contract
10
8
  end
11
9
 
12
- def schedule(from = Time.now)
13
- @work_time = @job.next_work_time(from)
14
-
15
- @work_time.nil? ? nil : [schedule_work, @work_time]
10
+ def schedule_initial_work(from = Time.now)
11
+ @work_time = @contract.initial_work_time(from)
12
+ schedule_work
16
13
  end
17
14
 
18
- def schedule_from_work_time(from)
19
- @work_time = @job.next_work_time_from_work_time(from)
20
-
21
- @work_time.nil? ? nil : [schedule_work, @work_time]
15
+ def schedule_next_work(previous_work_time)
16
+ @work_time = @contract.next_work_time(previous_work_time)
17
+ schedule_work
22
18
  end
23
19
 
24
- private
20
+ protected
25
21
 
26
22
  def schedule_work
23
+ @work_time.nil? ? nil : [push_to_redis, @work_time]
24
+ end
25
+
26
+ def push_to_redis
27
27
  client_args = {
28
- 'at' => @work_time.to_f,
29
- 'queue' => @queue,
30
- 'class' => @worker_name,
31
- 'args' => @args,
32
- 'rq:job' => @job.to_array,
33
- 'rq:at' => @work_time.to_f,
34
- 'rq:schdlr' => nil
35
- }.tap do |hash|
36
- hash['rq:addon'] = @addon unless @addon.nil?
37
- hash['rq:ca'] = @cancel_args unless @cancel_args.nil?
38
- end
28
+ 'at' => @work_time.to_f,
29
+ 'queue' => @queue,
30
+ 'class' => @worker_name,
31
+ 'args' => @args,
32
+ 'rq:ctr' => @contract.to_hash,
33
+ 'rq:sdl' => nil,
34
+ 'rq:at' => @work_time.to_f # this needs to be here because the key 'at' is removed by sidekiq
35
+ }
39
36
 
40
37
  Sidekiq::Client.push(client_args)
41
38
  end
@@ -6,12 +6,12 @@ module Rekiq
6
6
  attr_accessor :for_validation
7
7
 
8
8
  def validate(attribute_name, type, options = {})
9
- options[:allow_nil] = true if options[:allow_nil].nil?
9
+ options[:allow_nil] = false if options[:allow_nil].nil?
10
10
 
11
11
  self.for_validation << {
12
12
  attribute_name: attribute_name,
13
- type: type,
14
- options: options
13
+ type: type,
14
+ options: options
15
15
  }
16
16
  end
17
17
  end
@@ -28,11 +28,11 @@ module Rekiq
28
28
  def validate!
29
29
  self.class.for_validation.each do |v|
30
30
  attribute_name = v[:attribute_name]
31
- type = v[:type]
32
- options = v[:options]
33
- value = send(attribute_name)
31
+ type = v[:type]
32
+ options = v[:options]
33
+ value = instance_variable_get("@#{attribute_name}")
34
34
 
35
- unless options[:allow_nil] and send(attribute_name).nil?
35
+ unless options[:allow_nil] and value.nil?
36
36
  send("validate_#{type}!", attribute_name, value, options)
37
37
  end
38
38
  end
@@ -62,7 +62,7 @@ module Rekiq
62
62
  def validate_schedule!(attribute_name, value, options)
63
63
  unless value.respond_to?(:next_occurrence) and
64
64
  value.method(:next_occurrence).arity.abs == 1
65
- raise InvalidConf, '#attribute_name must respond to next_occurrence ' \
65
+ raise InvalidConf, "#{attribute_name} must respond to next_occurrence " \
66
66
  'and receive one argument of type Time'
67
67
  end
68
68
  end
data/lib/rekiq/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rekiq
2
- VERSION = '0.9.3'
2
+ VERSION = '1.0.0'
3
3
  end
data/lib/rekiq/worker.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  require 'rekiq/exceptions'
2
- require 'rekiq/job'
2
+ require 'rekiq/contract'
3
3
  require 'rekiq/scheduler'
4
4
 
5
5
  module Rekiq
6
6
  module Worker
7
7
  class Configuration
8
- attr_accessor :shift, :schedule_post_work, :schedule_expired,
9
- :expiration_margin, :addon, :cancel_args
8
+ attr_accessor :cancel_args, :addon, :schedule_post_work, :work_time_shift,
9
+ :work_time_tolerance, :schedule_expired, :starting_at
10
10
 
11
11
  def rekiq_cancel_args(*args)
12
12
  @cancel_args = args
@@ -15,51 +15,49 @@ module Rekiq
15
15
 
16
16
  module ClassMethods
17
17
  def perform_recurringly(schedule, *args)
18
- @config = Configuration.new
19
- yield @config if block_given?
20
-
21
18
  validate!
22
19
 
23
- job =
24
- Rekiq::Job
25
- .new 'schedule' => schedule,
26
- 'shift' => @config.shift,
27
- 'schedule_post_work' => @config.schedule_post_work,
28
- 'schedule_expired' => @config.schedule_expired,
29
- 'expiration_margin' => @config.expiration_margin
20
+ config = Configuration.new
21
+ yield config if block_given?
22
+
23
+ contract =
24
+ Rekiq::Contract
25
+ .new 'schedule' => schedule,
26
+ 'cancel_args' => config.cancel_args,
27
+ 'addon' => config.addon,
28
+ 'schedule_post_work' => config.schedule_post_work,
29
+ 'work_time_shift' => config.work_time_shift,
30
+ 'work_time_tolerance' => config.work_time_tolerance,
31
+ 'schedule_expired' => config.schedule_expired
30
32
 
31
- job.validate!
33
+ contract.validate!
32
34
 
33
35
  queue = get_sidekiq_options['queue']
34
36
 
35
37
  jid, work_time =
36
38
  Rekiq::Scheduler
37
- .new(name, queue, args, job, @config.addon, @config.cancel_args)
38
- .schedule
39
+ .new(self.name, queue, args, contract)
40
+ .schedule_initial_work(config.starting_at || Time.now)
39
41
 
40
42
  unless jid.nil?
41
43
  ::Sidekiq.logger.info \
42
- "recurring work for #{name} scheduled for " \
43
- "#{work_time} with jid #{jid}"
44
+ "recurring work for #{self.name} scheduled for #{work_time} " \
45
+ "with jid #{jid}"
44
46
  end
45
47
 
46
48
  jid
47
49
  end
48
50
 
49
- def rekiq_cancel_method
50
- get_sidekiq_options['rekiq_cancel_method']
51
- end
52
-
53
51
  protected
54
52
 
55
53
  def validate!
56
- unless rekiq_cancel_method.nil? or
57
- self.method_defined?(rekiq_cancel_method)
58
- raise CancelMethodMissing,
59
- 'rekiq cancel method name defined as ' \
60
- "#{rekiq_cancel_method}, but worker does not have " \
61
- 'a method with that name, either remove definition or define ' \
62
- 'missing method'
54
+ method_name = get_sidekiq_options['rekiq_cancel_method']
55
+
56
+ unless method_name.nil? or method_defined?(method_name)
57
+ raise ::Rekiq::CancelMethodMissing,
58
+ "rekiq cancel method name defined as #{method_name}, but " \
59
+ 'worker has no method with that name, either remove ' \
60
+ 'definition or define missing method'
63
61
  end
64
62
  end
65
63
  end
@@ -77,8 +75,14 @@ module Sidekiq
77
75
  base.extend(Rekiq::Worker::ClassMethods)
78
76
  end
79
77
 
80
- def rekiq_cancel_method
81
- self.class.rekiq_cancel_method
78
+ def cancel_rekiq_worker?(*method_args)
79
+ method_name = self.class.get_sidekiq_options['rekiq_cancel_method']
80
+
81
+ !method_name.nil? and send(method_name, *method_args)
82
+ rescue StandardError => s
83
+ raise ::Rekiq::CancelMethodInvocationError,
84
+ "error while invoking rekiq_cancel_method: #{s.message}",
85
+ s.backtrace
82
86
  end
83
87
  end
84
88
  end
data/rekiq.gemspec CHANGED
@@ -26,7 +26,8 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'simplecov', '~> 0.9'
27
27
  spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4'
28
28
  spec.add_development_dependency 'factory_girl', '~> 4.4'
29
- spec.add_development_dependency 'jazz_hands', '~> 0.5'
30
29
  spec.add_development_dependency 'ice_cube', '~> 0.12'
31
30
  spec.add_development_dependency 'sidekiq', '~> 3.2'
31
+
32
+ # spec.add_development_dependency 'jazz_hands', '~> 0.5'
32
33
  end
@@ -1,8 +1,8 @@
1
1
  FactoryGirl.define do
2
2
  factory :configuration, class: Rekiq::Configuration do
3
- shift [*-100..100].sample
4
- schedule_post_work [true, false].sample
5
- expiration_margin [*0..100].sample
6
- schedule_expired [true, false].sample
3
+ schedule_post_work [true, false].sample
4
+ work_time_shift [*-100..100].sample
5
+ work_time_tolerance [*0..100].sample
6
+ schedule_expired [true, false].sample
7
7
  end
8
8
  end
@@ -0,0 +1,17 @@
1
+ require 'ice_cube'
2
+
3
+ FactoryGirl.define do
4
+ factory :contract, class: Rekiq::Contract do
5
+ work_time_shift 0
6
+ schedule IceCube::Schedule.new(Time.now + 3600)
7
+
8
+ trait :randomized_attributes do
9
+ cancel_args [nil, 'asd', ['d', 'dsa']].sample
10
+ addon [nil, '2121', 'dasdas'].sample
11
+ schedule_post_work [nil, false, true].sample
12
+ work_time_shift [nil, *0..100].sample
13
+ work_time_tolerance [nil, *0..100].sample
14
+ schedule_expired [nil, false, true].sample
15
+ end
16
+ end
17
+ end
@@ -5,16 +5,16 @@ describe Rekiq::Configuration do
5
5
  context 'for created Configuration instance' do
6
6
  before { @configuration = Rekiq::Configuration.new }
7
7
 
8
- it 'sets shift as 0 by default' do
9
- expect(@configuration.shift).to eq(0)
8
+ it 'sets work_time_shift as 0 by default' do
9
+ expect(@configuration.work_time_shift).to eq(0)
10
10
  end
11
11
 
12
12
  it 'sets schedule_post_work as false by default' do
13
13
  expect(@configuration.schedule_post_work).to eq(false)
14
14
  end
15
15
 
16
- it 'sets expiration_margin as 0 by default' do
17
- expect(@configuration.expiration_margin).to eq(0)
16
+ it 'sets work_time_tolerance as 0 by default' do
17
+ expect(@configuration.work_time_tolerance).to eq(0)
18
18
  end
19
19
 
20
20
  it 'sets schedule_expired as false by default' do
@@ -24,8 +24,8 @@ describe Rekiq::Configuration do
24
24
  end
25
25
 
26
26
  describe '#validate!' do
27
- context 'for instance with non numeric shift' do
28
- before { @configuration = build(:configuration, shift: [1]) }
27
+ context 'for instance with non numeric work_time_shift' do
28
+ before { @configuration = build(:configuration, work_time_shift: [1]) }
29
29
 
30
30
  it 'raises error' do
31
31
  expect do
@@ -34,7 +34,7 @@ describe Rekiq::Configuration do
34
34
  end
35
35
  end
36
36
 
37
- context 'for instance with numeric shift' do
37
+ context 'for instance with numeric work_time_shift' do
38
38
  before { @configuration = build(:configuration) }
39
39
 
40
40
  it 'does not raise error' do
@@ -66,8 +66,8 @@ describe Rekiq::Configuration do
66
66
  end
67
67
  end
68
68
 
69
- context 'for instance with non numeric expiration_margin' do
70
- before { @configuration = build(:configuration, expiration_margin: '1') }
69
+ context 'for instance with non numeric work_time_tolerance' do
70
+ before { @configuration = build(:configuration, work_time_tolerance: '1') }
71
71
 
72
72
  it 'raises error' do
73
73
  expect do
@@ -76,8 +76,8 @@ describe Rekiq::Configuration do
76
76
  end
77
77
  end
78
78
 
79
- context 'for instance with negative expiration_margin' do
80
- before { @configuration = build(:configuration, expiration_margin: -1) }
79
+ context 'for instance with negative work_time_tolerance' do
80
+ before { @configuration = build(:configuration, work_time_tolerance: -1) }
81
81
 
82
82
  it 'raises error' do
83
83
  expect do
@@ -86,7 +86,7 @@ describe Rekiq::Configuration do
86
86
  end
87
87
  end
88
88
 
89
- context 'for instance with 0 or positive expiration_margin' do
89
+ context 'for instance with 0 or positive work_time_tolerance' do
90
90
  before { @configuration = build(:configuration) }
91
91
 
92
92
  it 'does not raise error' do