sidekiq-status 0.3.0 → 0.3.1

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.
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: