sidekiq-status 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -91,6 +91,14 @@ Sidekiq::Status::total job_id #=> 100
91
91
  Sidekiq::Status::message job_id #=> "Almost done"
92
92
  Sidekiq::Status::pct_complete job_id #=> 5
93
93
  ```
94
+ ### Unscheduling
95
+
96
+ ```ruby
97
+ scheduled_job_id = MyJob.perform_in 3600
98
+ Sidekiq::Status.cancel scheduled_job_id #=> true
99
+ #doesn't cancel running jobs, this is more like unscheduling, therefore an alias:
100
+ Sidekiq::Status.unschedule scheduled_job_id #=> true
101
+ ```
94
102
 
95
103
  ### Features coming
96
104
  * Stopping jobs by id
@@ -100,6 +108,7 @@ Sidekiq::Status::pct_complete job_id #=> 5
100
108
  Andrew Korzhuev
101
109
  Jon Moses
102
110
  Wayne Hoover
111
+ Dylan Robinson
103
112
 
104
113
  ## License
105
114
  MIT License , see LICENSE for more details.
@@ -29,6 +29,12 @@ module Sidekiq::Status
29
29
  status.to_sym unless status.nil?
30
30
  end
31
31
 
32
+ def cancel(job_id, job_unix_time = nil)
33
+ delete_and_unschedule(job_id, job_unix_time)
34
+ end
35
+
36
+ alias_method :unschedule, :cancel
37
+
32
38
  STATUS.each do |name|
33
39
  class_eval(<<-END, __FILE__, __LINE__)
34
40
  def #{name}?(job_id)
@@ -1,5 +1,6 @@
1
1
  module Sidekiq::Status::Storage
2
2
  RESERVED_FIELDS=%w(status stop update_time).freeze
3
+ BATCH_LIMIT = 500
3
4
 
4
5
  protected
5
6
 
@@ -27,6 +28,26 @@ module Sidekiq::Status::Storage
27
28
  store_for_id id, {status: status}, expiration
28
29
  end
29
30
 
31
+ # Unschedules the job and deletes the Status
32
+ # @param [String] id job id
33
+ # @param [Num] job_unix_time, unix timestamp for the scheduled job
34
+ def delete_and_unschedule(job_id, job_unix_time = nil)
35
+ Sidekiq.redis do |conn|
36
+ scan_options = {offset: 0, conn: conn, start: (job_unix_time || '-inf'), end: (job_unix_time || '+inf')}
37
+
38
+ while not (jobs = schedule_batch(scan_options)).empty?
39
+ match = scan_scheduled_jobs_for_jid jobs, job_id
40
+ unless match.nil?
41
+ conn.zrem "schedule", match
42
+ conn.del job_id
43
+ return true # Done
44
+ end
45
+ scan_options[:offset] += BATCH_LIMIT
46
+ end
47
+ end
48
+ false
49
+ end
50
+
30
51
  # Gets a single valued from job status hash
31
52
  # @param [String] id job id
32
53
  # @param [String] Symbol field fetched field name
@@ -45,4 +66,28 @@ module Sidekiq::Status::Storage
45
66
  conn.hgetall id
46
67
  end
47
68
  end
69
+
70
+ private
71
+
72
+ # Gets the batch of scheduled jobs based on input options
73
+ # Uses Redis zrangebyscore for log(n) search, if unix-time is provided
74
+ # @param [Hash] options, options hash containing (REQUIRED) keys:
75
+ # - conn: Redis connection
76
+ # - start: start score (i.e. -inf or a unix timestamp)
77
+ # - end: end score (i.e. +inf or a unix timestamp)
78
+ # - offset: current progress through (all) jobs (e.g.: 100 if you want jobs from 100 to BATCH_LIMIT)
79
+ def schedule_batch(options)
80
+ options[:conn].zrangebyscore "schedule", options[:start], options[:end], {limit: [options[:offset], BATCH_LIMIT]}
81
+ end
82
+
83
+ # Searches the jobs Array for the job_id
84
+ # @param [Array] scheduled_jobs, results of Redis schedule key
85
+ # @param [String] id job id
86
+ def scan_scheduled_jobs_for_jid(scheduled_jobs, job_id)
87
+ # A Little skecthy, I know, but the structure of these internal JSON
88
+ # is predefined in such a way where this will not catch unintentional elements,
89
+ # and this is notably faster than performing JSON.parse() for every listing:
90
+ scheduled_jobs.each { |job_listing| (return job_listing) if job_listing.include?("\"jid\":\"#{job_id}") }
91
+ nil
92
+ end
48
93
  end
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Status
3
- VERSION = "0.3.0"
3
+ VERSION = "0.3.1"
4
4
  end
5
5
  end
@@ -5,6 +5,9 @@ describe Sidekiq::Status do
5
5
  let!(:redis) { Sidekiq.redis { |conn| conn } }
6
6
  let!(:job_id) { SecureRandom.hex(12) }
7
7
  let!(:job_id_1) { SecureRandom.hex(12) }
8
+ let!(:unused_id) { SecureRandom.hex(12) }
9
+ let!(:plain_sidekiq_job_id) { SecureRandom.hex(12) }
10
+ let!(:retried_job_id) { SecureRandom.hex(12) }
8
11
 
9
12
  # Clean Redis before each test
10
13
  # Seems like flushall has no effect on recently published messages,
@@ -77,30 +80,69 @@ describe Sidekiq::Status do
77
80
  end
78
81
  end
79
82
 
83
+ describe ".cancel" do
84
+ it "cancels a job by id" do
85
+ SecureRandom.should_receive(:hex).twice.and_return(job_id, job_id_1)
86
+ start_server do
87
+ job = LongJob.perform_in(3600)
88
+ job.should == job_id
89
+ second_job = LongJob.perform_in(3600)
90
+ second_job.should == job_id_1
91
+
92
+ initial_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
93
+ initial_schedule.size.should be 2
94
+ initial_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size.should be 1
95
+
96
+ Sidekiq::Status.unschedule(job_id).should be_true
97
+ Sidekiq::Status.cancel(unused_id).should be_false # Unused, therefore unfound => false
98
+
99
+ remaining_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
100
+ remaining_schedule.size.should == (initial_schedule.size - 1)
101
+ remaining_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size.should be 0
102
+ end
103
+ end
104
+
105
+ it "does not cancel a job with correct id but wrong time" do
106
+ SecureRandom.should_receive(:hex).once.and_return(job_id)
107
+ start_server do
108
+ scheduled_time = Time.now.to_i + 3600
109
+ returned_job_id = LongJob.perform_at(scheduled_time)
110
+ returned_job_id.should == job_id
111
+
112
+ initial_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
113
+ initial_schedule.size.should == 1
114
+ Sidekiq::Status.cancel(returned_job_id, (scheduled_time + 1)).should be_false # wrong time, therefore unfound => false
115
+ (redis.zrange "schedule", 0, -1, {withscores: true}).size.should be 1
116
+ Sidekiq::Status.cancel(returned_job_id, (scheduled_time)).should be_true # same id, same time, deletes
117
+ (redis.zrange "schedule", 0, -1, {withscores: true}).size.should be_zero
118
+ end
119
+ end
120
+ end
121
+
80
122
  context "keeps normal Sidekiq functionality" do
81
123
  it "does jobs with and without included worker module" do
82
- SecureRandom.should_receive(:hex).exactly(4).times.and_return(job_id, job_id, job_id_1, job_id_1)
124
+ SecureRandom.should_receive(:hex).exactly(4).times.and_return(plain_sidekiq_job_id, plain_sidekiq_job_id, job_id_1, job_id_1)
83
125
  start_server do
84
126
  capture_status_updates(12) {
85
- StubJob.perform_async.should == job_id
127
+ StubJob.perform_async.should == plain_sidekiq_job_id
86
128
  NoStatusConfirmationJob.perform_async(1)
87
129
  StubJob.perform_async.should == job_id_1
88
130
  NoStatusConfirmationJob.perform_async(2)
89
- }.should =~ [job_id, job_id_1] * 6
131
+ }.should =~ [plain_sidekiq_job_id, job_id_1] * 6
90
132
  end
91
133
  redis.mget('NoStatusConfirmationJob_1', 'NoStatusConfirmationJob_2').should == %w(done)*2
92
- Sidekiq::Status.status(job_id).should == :complete
134
+ Sidekiq::Status.status(plain_sidekiq_job_id).should == :complete
93
135
  Sidekiq::Status.status(job_id_1).should == :complete
94
136
  end
95
137
 
96
138
  it "retries failed jobs" do
97
- SecureRandom.should_receive(:hex).once.and_return(job_id)
139
+ SecureRandom.should_receive(:hex).once.and_return(retried_job_id)
98
140
  start_server do
99
141
  capture_status_updates(5) {
100
- RetriedJob.perform_async().should == job_id
101
- }.should == [job_id] * 5
142
+ RetriedJob.perform_async().should == retried_job_id
143
+ }.should == [retried_job_id] * 5
102
144
  end
103
- Sidekiq::Status.status(job_id).should == :complete
145
+ Sidekiq::Status.status(retried_job_id).should == :complete
104
146
  end
105
147
  end
106
148
 
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "rspec"
2
2
 
3
+ require 'celluloid'
3
4
  require 'sidekiq'
4
5
  require 'sidekiq/processor'
5
6
  require 'sidekiq/manager'
@@ -58,6 +58,7 @@ class RetriedJob < StubJob
58
58
  def perform()
59
59
  Sidekiq.redis do |conn|
60
60
  key = "RetriedJob_#{jid}"
61
+ sleep 1
61
62
  unless conn.exists key
62
63
  conn.set key, 'tried'
63
64
  raise StandardError
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-status
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-06 00:00:00.000000000 Z
12
+ date: 2013-07-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
@@ -118,4 +118,3 @@ test_files:
118
118
  - spec/lib/sidekiq-status_spec.rb
119
119
  - spec/spec_helper.rb
120
120
  - spec/support/test_jobs.rb
121
- has_rdoc: