rekiq 0.9.3 → 1.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 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