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 +9 -0
- data/lib/sidekiq-status.rb +6 -0
- data/lib/sidekiq-status/storage.rb +45 -0
- data/lib/sidekiq-status/version.rb +1 -1
- data/spec/lib/sidekiq-status_spec.rb +50 -8
- data/spec/spec_helper.rb +1 -0
- data/spec/support/test_jobs.rb +1 -0
- metadata +2 -3
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.
|
data/lib/sidekiq-status.rb
CHANGED
@@ -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
|
@@ -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(
|
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 ==
|
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 =~ [
|
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(
|
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(
|
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 ==
|
101
|
-
}.should == [
|
142
|
+
RetriedJob.perform_async().should == retried_job_id
|
143
|
+
}.should == [retried_job_id] * 5
|
102
144
|
end
|
103
|
-
Sidekiq::Status.status(
|
145
|
+
Sidekiq::Status.status(retried_job_id).should == :complete
|
104
146
|
end
|
105
147
|
end
|
106
148
|
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/test_jobs.rb
CHANGED
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.
|
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-
|
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:
|