sidekiq_status 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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +8 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +159 -0
- data/Rakefile +9 -0
- data/lib/sidekiq_status/client_middleware.rb +25 -0
- data/lib/sidekiq_status/container.rb +354 -0
- data/lib/sidekiq_status/version.rb +5 -0
- data/lib/sidekiq_status/web.rb +65 -0
- data/lib/sidekiq_status/worker.rb +64 -0
- data/lib/sidekiq_status.rb +13 -0
- data/sidekiq_status.gemspec +27 -0
- data/spec/container_spec.rb +313 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/worker_spec.rb +159 -0
- data/web/views/status.slim +44 -0
- data/web/views/statuses.slim +37 -0
- metadata +183 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module SidekiqStatus
|
3
|
+
module Worker
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
include Sidekiq::Worker
|
7
|
+
|
8
|
+
include(InstanceMethods)
|
9
|
+
|
10
|
+
base.define_singleton_method(:new) do |*args, &block|
|
11
|
+
super(*args, &block).extend(Prepending)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Prepending
|
17
|
+
def perform(jid)
|
18
|
+
@status_container = SidekiqStatus::Container.load(jid)
|
19
|
+
|
20
|
+
begin
|
21
|
+
catch(:killed) do
|
22
|
+
set_status('working')
|
23
|
+
super(*@status_container.args)
|
24
|
+
set_status('complete')
|
25
|
+
end
|
26
|
+
rescue Exception => exc
|
27
|
+
set_status('failed', exc.class.name + ': ' + exc.message + " \n\n " + exc.backtrace.join("\n "))
|
28
|
+
raise exc
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module InstanceMethods
|
34
|
+
def status_container
|
35
|
+
kill if @status_container.kill_requested?
|
36
|
+
@status_container
|
37
|
+
end
|
38
|
+
alias_method :sc, :status_container
|
39
|
+
|
40
|
+
def kill
|
41
|
+
# NOTE: status_container below should be accessed by instance var instead of an accessor method
|
42
|
+
# because the second option will lead to infinite recursing
|
43
|
+
@status_container.kill
|
44
|
+
throw(:killed)
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_status(status, message = nil)
|
48
|
+
self.sc.update_attributes('status' => status, 'message' => message)
|
49
|
+
end
|
50
|
+
|
51
|
+
def at(at, message = nil)
|
52
|
+
self.sc.update_attributes('at' => at, 'message' => message)
|
53
|
+
end
|
54
|
+
|
55
|
+
def total=(total)
|
56
|
+
self.sc.update_attributes('total' => total)
|
57
|
+
end
|
58
|
+
|
59
|
+
def payload=(payload)
|
60
|
+
self.sc.update_attributes('payload' => payload)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'sidekiq'
|
3
|
+
|
4
|
+
require 'securerandom'
|
5
|
+
require "sidekiq_status/version"
|
6
|
+
require "sidekiq_status/client_middleware"
|
7
|
+
require "sidekiq_status/container"
|
8
|
+
require "sidekiq_status/worker"
|
9
|
+
Sidekiq.client_middleware do |chain|
|
10
|
+
chain.add SidekiqStatus::ClientMiddleware
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'sidekiq_status/web' if defined?(Sidekiq::Web)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/sidekiq_status/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Artem Ignatyev"]
|
6
|
+
gem.email = ["cryo28@gmail.com"]
|
7
|
+
gem.description = "Job status tracking extension for Sidekiq"
|
8
|
+
gem.summary = "A Sidekiq extension to track job execution statuses and return job results back to the client in a convenient manner"
|
9
|
+
gem.homepage = "https://github.com/cryo28/sidekiq_status"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "sidekiq_status"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = SidekiqStatus::VERSION
|
17
|
+
|
18
|
+
gem.add_runtime_dependency("sidekiq", "~> 2.3.3")
|
19
|
+
|
20
|
+
gem.add_development_dependency("rspec")
|
21
|
+
gem.add_development_dependency("simplecov")
|
22
|
+
gem.add_development_dependency("rake")
|
23
|
+
gem.add_development_dependency("timecop")
|
24
|
+
|
25
|
+
gem.add_development_dependency("yard")
|
26
|
+
gem.add_development_dependency("maruku")
|
27
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
def test_container(container, hash, jid = nil)
|
5
|
+
hash.reject { |k, v| k == :last_updated_at }.find do |k, v|
|
6
|
+
container.send(k).should == v
|
7
|
+
end
|
8
|
+
|
9
|
+
container.last_updated_at.should == Time.at(hash['last_updated_at']) if hash['last_updated_at']
|
10
|
+
container.jid.should == jid if jid
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
describe SidekiqStatus::Container do
|
15
|
+
let(:jid) { "c2db8b1b460608fb32d76b7a" }
|
16
|
+
let(:status_key) { described_class.status_key(jid) }
|
17
|
+
let(:sample_json_hash) do
|
18
|
+
{
|
19
|
+
'args' => ['arg1', 'arg2'],
|
20
|
+
'worker' => 'SidekiqStatus::Worker',
|
21
|
+
'queue' => '',
|
22
|
+
|
23
|
+
'status' => "completed",
|
24
|
+
'at' => 50,
|
25
|
+
'total' => 200,
|
26
|
+
'message' => "Some message",
|
27
|
+
|
28
|
+
'payload' => {},
|
29
|
+
'last_updated_at' => 1344855831
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
specify ".status_key" do
|
34
|
+
jid = SecureRandom.base64
|
35
|
+
described_class.status_key(jid).should == "sidekiq_status:#{jid}"
|
36
|
+
end
|
37
|
+
|
38
|
+
specify ".kill_key" do
|
39
|
+
described_class.kill_key.should == described_class::KILL_KEY
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
context "finders" do
|
44
|
+
let!(:containers) do
|
45
|
+
described_class::STATUS_NAMES.inject({}) do |accum, status_name|
|
46
|
+
container = described_class.create()
|
47
|
+
container.update_attributes(:status => status_name)
|
48
|
+
|
49
|
+
accum[status_name] = container
|
50
|
+
accum
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
specify ".size" do
|
55
|
+
described_class.size.should == containers.size
|
56
|
+
end
|
57
|
+
|
58
|
+
specify ".status_jids" do
|
59
|
+
expected = containers.values.map(&:jid).map{ |jid| [jid, anything()] }
|
60
|
+
described_class.status_jids.should =~ expected
|
61
|
+
described_class.status_jids(0, 0).size.should == 1
|
62
|
+
end
|
63
|
+
|
64
|
+
specify ".statuses" do
|
65
|
+
described_class.statuses.should be_all{|st| st.is_a?(described_class) }
|
66
|
+
described_class.statuses.size.should == containers.size
|
67
|
+
described_class.statuses(0, 0).size.should == 1
|
68
|
+
end
|
69
|
+
|
70
|
+
describe ".delete" do
|
71
|
+
before do
|
72
|
+
described_class.status_jids.map(&:first).should =~ containers.values.map(&:jid)
|
73
|
+
end
|
74
|
+
|
75
|
+
specify "deletes jobs in specific status" do
|
76
|
+
statuses_to_delete = ['waiting', 'complete']
|
77
|
+
described_class.delete(statuses_to_delete)
|
78
|
+
|
79
|
+
described_class.status_jids.map(&:first).should =~ containers.
|
80
|
+
reject{ |status_name, container| statuses_to_delete.include?(status_name) }.
|
81
|
+
values.
|
82
|
+
map(&:jid)
|
83
|
+
end
|
84
|
+
|
85
|
+
specify "deletes jobs in all statuses" do
|
86
|
+
described_class.delete()
|
87
|
+
|
88
|
+
described_class.status_jids.should be_empty
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
specify ".create" do
|
94
|
+
SecureRandom.should_receive(:hex).with(12).and_return(jid)
|
95
|
+
args = ['arg1', 'arg2', {arg3: 'val3'}]
|
96
|
+
|
97
|
+
container = described_class.create('args' => args)
|
98
|
+
container.should be_a(described_class)
|
99
|
+
container.args.should == args
|
100
|
+
|
101
|
+
# Check default values are set
|
102
|
+
test_container(container, described_class::DEFAULTS.reject{|k, v| k == 'args' }, jid)
|
103
|
+
|
104
|
+
Sidekiq.redis do |conn|
|
105
|
+
conn.exists(status_key).should be_true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe ".load" do
|
110
|
+
it "raises StatusNotFound exception if status is missing in Redis" do
|
111
|
+
expect { described_class.load(jid) }.to raise_exception(described_class::StatusNotFound, jid)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "loads a container from the redis key" do
|
115
|
+
json = MultiJson.dump(sample_json_hash)
|
116
|
+
Sidekiq.redis { |conn| conn.set(status_key, json) }
|
117
|
+
|
118
|
+
container = described_class.load(jid)
|
119
|
+
test_container(container, sample_json_hash, jid)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "cleans up unprocessed expired kill requests as well" do
|
123
|
+
Sidekiq.redis do |conn|
|
124
|
+
conn.zadd(described_class.kill_key, [
|
125
|
+
[(Time.now - described_class.ttl - 1).to_i, 'a'],
|
126
|
+
[(Time.now - described_class.ttl + 1).to_i, 'b'],
|
127
|
+
]
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
json = MultiJson.dump(sample_json_hash)
|
132
|
+
Sidekiq.redis { |conn| conn.set(status_key, json) }
|
133
|
+
described_class.load(jid)
|
134
|
+
|
135
|
+
Sidekiq.redis do |conn|
|
136
|
+
conn.zscore(described_class.kill_key, 'a').should be_nil
|
137
|
+
conn.zscore(described_class.kill_key, 'b').should_not be_nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
specify "#dump" do
|
143
|
+
hash = sample_json_hash.reject{ |k, v| k == 'last_updated_at' }
|
144
|
+
container = described_class.new(jid, hash)
|
145
|
+
dump = container.send(:dump)
|
146
|
+
dump.should == hash.merge('last_updated_at' => Time.now.to_i)
|
147
|
+
end
|
148
|
+
|
149
|
+
specify "#save saves container to Redis" do
|
150
|
+
hash = sample_json_hash.reject{ |k, v| k == 'last_updated_at' }
|
151
|
+
described_class.new(jid, hash).save
|
152
|
+
|
153
|
+
result = Sidekiq.redis{ |conn| conn.get(status_key) }
|
154
|
+
result = MultiJson.load(result)
|
155
|
+
|
156
|
+
result.should == hash.merge('last_updated_at' => Time.now.to_i)
|
157
|
+
|
158
|
+
Sidekiq.redis{ |conn| conn.ttl(status_key).should >= 0 }
|
159
|
+
end
|
160
|
+
|
161
|
+
specify "#delete" do
|
162
|
+
Sidekiq.redis do |conn|
|
163
|
+
conn.set(status_key, "something")
|
164
|
+
conn.zadd(described_class.kill_key, 0, jid)
|
165
|
+
end
|
166
|
+
|
167
|
+
container = described_class.new(jid)
|
168
|
+
container.delete
|
169
|
+
|
170
|
+
Sidekiq.redis do |conn|
|
171
|
+
conn.exists(status_key).should be_false
|
172
|
+
conn.zscore(described_class.kill_key, jid).should be_nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
specify "#request_kill, #should_kill?, #killable?" do
|
177
|
+
container = described_class.new(jid)
|
178
|
+
container.kill_requested?.should be_false
|
179
|
+
container.should be_killable
|
180
|
+
|
181
|
+
Sidekiq.redis do |conn|
|
182
|
+
conn.zscore(described_class.kill_key, jid).should be_nil
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
container.request_kill
|
187
|
+
|
188
|
+
Sidekiq.redis do |conn|
|
189
|
+
conn.zscore(described_class.kill_key, jid).should == Time.now.to_i
|
190
|
+
end
|
191
|
+
container.should be_kill_requested
|
192
|
+
container.should_not be_killable
|
193
|
+
end
|
194
|
+
|
195
|
+
specify "#kill" do
|
196
|
+
container = described_class.new(jid)
|
197
|
+
container.request_kill
|
198
|
+
Sidekiq.redis do |conn|
|
199
|
+
conn.zscore(described_class.kill_key, jid).should == Time.now.to_i
|
200
|
+
end
|
201
|
+
container.status.should_not == 'killed'
|
202
|
+
|
203
|
+
|
204
|
+
container.kill
|
205
|
+
|
206
|
+
Sidekiq.redis do |conn|
|
207
|
+
conn.zscore(described_class.kill_key, jid).should be_nil
|
208
|
+
end
|
209
|
+
|
210
|
+
container.status.should == 'killed'
|
211
|
+
described_class.load(jid).status.should == 'killed'
|
212
|
+
end
|
213
|
+
|
214
|
+
specify "#pct_complete" do
|
215
|
+
container = described_class.new(jid)
|
216
|
+
container.at = 1
|
217
|
+
container.total = 100
|
218
|
+
container.pct_complete.should == 1
|
219
|
+
|
220
|
+
container.at = 5
|
221
|
+
container.total = 200
|
222
|
+
container.pct_complete.should == 3 # 2.5.round(0) => 3
|
223
|
+
end
|
224
|
+
|
225
|
+
context "setters" do
|
226
|
+
let(:container) { described_class.new(jid) }
|
227
|
+
|
228
|
+
describe "#at=" do
|
229
|
+
it "sets numeric value" do
|
230
|
+
container.total = 100
|
231
|
+
container.at = 3
|
232
|
+
container.at.should == 3
|
233
|
+
container.total.should == 100
|
234
|
+
end
|
235
|
+
|
236
|
+
it "raises ArgumentError otherwise" do
|
237
|
+
expect{ container.at = "Wrong" }.to raise_exception(ArgumentError)
|
238
|
+
end
|
239
|
+
|
240
|
+
it "adjusts total if its less than new at" do
|
241
|
+
container.total = 200
|
242
|
+
container.at = 250
|
243
|
+
container.total.should == 250
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
describe "#total=" do
|
248
|
+
it "sets numeric value" do
|
249
|
+
container.total = 50
|
250
|
+
container.total.should == 50
|
251
|
+
end
|
252
|
+
|
253
|
+
it "raises ArgumentError otherwise" do
|
254
|
+
expect{ container.total = "Wrong" }.to raise_exception(ArgumentError)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe "#status=" do
|
259
|
+
described_class::STATUS_NAMES.each do |status|
|
260
|
+
it "sets status #{status.inspect}" do
|
261
|
+
container.status = status
|
262
|
+
container.status.should == status
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
it "raises ArgumentError otherwise" do
|
267
|
+
expect{ container.status = 'Wrong' }.to raise_exception(ArgumentError)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
specify "#message=" do
|
272
|
+
container.message = 'abcd'
|
273
|
+
container.message.should == 'abcd'
|
274
|
+
|
275
|
+
container.message = nil
|
276
|
+
container.message.should be_nil
|
277
|
+
|
278
|
+
message = double('Message', :to_s => 'to_s')
|
279
|
+
container.message = message
|
280
|
+
container.message.should == 'to_s'
|
281
|
+
end
|
282
|
+
|
283
|
+
specify "#payload=" do
|
284
|
+
container.should respond_to(:payload=)
|
285
|
+
end
|
286
|
+
|
287
|
+
specify "update_attributes" do
|
288
|
+
container.update_attributes(:at => 1, 'total' => 3, :message => 'msg', 'status' => 'working')
|
289
|
+
reloaded_container = described_class.load(container.jid)
|
290
|
+
|
291
|
+
reloaded_container.at.should == 1
|
292
|
+
reloaded_container.total.should == 3
|
293
|
+
reloaded_container.message.should == 'msg'
|
294
|
+
reloaded_container.status.should == 'working'
|
295
|
+
|
296
|
+
expect{ container.update_attributes(:at => 'Invalid') }.to raise_exception(ArgumentError)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context "predicates" do
|
301
|
+
described_class::STATUS_NAMES.each do |status_name1|
|
302
|
+
context "status is #{status_name1}" do
|
303
|
+
subject{ described_class.create().tap{|c| c.status = status_name1} }
|
304
|
+
|
305
|
+
its("#{status_name1}?") { should be_true }
|
306
|
+
|
307
|
+
(described_class::STATUS_NAMES - [status_name1]).each do |status_name2|
|
308
|
+
its("#{status_name2}?") { should be_false }
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = 'test'
|
6
|
+
|
7
|
+
require 'simplecov'
|
8
|
+
SimpleCov.start
|
9
|
+
|
10
|
+
#require 'sidekiq'
|
11
|
+
|
12
|
+
require 'sidekiq_status'
|
13
|
+
require 'sidekiq/util'
|
14
|
+
|
15
|
+
require 'timecop'
|
16
|
+
|
17
|
+
Sidekiq.logger.level = Logger::ERROR
|
18
|
+
|
19
|
+
require 'sidekiq/redis_connection'
|
20
|
+
REDIS = Sidekiq::RedisConnection.create(:url => "redis://localhost/15", :namespace => 'test', :size => 1)
|
21
|
+
|
22
|
+
RSpec.configure do |c|
|
23
|
+
c.before do
|
24
|
+
Sidekiq.redis = REDIS
|
25
|
+
Sidekiq.redis{ |conn| conn.flushdb }
|
26
|
+
end
|
27
|
+
|
28
|
+
c.around do |example|
|
29
|
+
Timecop.freeze(Time.utc(2012)) do
|
30
|
+
example.call
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/worker_spec.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sidekiq::Worker do
|
4
|
+
class SomeWorker
|
5
|
+
include SidekiqStatus::Worker
|
6
|
+
|
7
|
+
def perform(*args)
|
8
|
+
some_method(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def some_method(*args); end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:args) { ['arg1', 'arg2', {'arg3' => 'val3'}]}
|
15
|
+
|
16
|
+
describe ".perform_async" do
|
17
|
+
it "invokes middleware which creates sidekiq_status container with the same jid" do
|
18
|
+
jid = SomeWorker.perform_async(*args)
|
19
|
+
jid.should be_a(String)
|
20
|
+
|
21
|
+
container = SidekiqStatus::Container.load(jid)
|
22
|
+
container.args.should == args
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#perform (Worker context)" do
|
27
|
+
let(:worker) { SomeWorker.new }
|
28
|
+
|
29
|
+
it "receives jid as parameters, loads container and runs original perform with enqueued args" do
|
30
|
+
worker.should_receive(:some_method).with(*args)
|
31
|
+
jid = SomeWorker.perform_async(*args)
|
32
|
+
worker.perform(jid)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "changes status to working" do
|
36
|
+
has_been_run = false
|
37
|
+
worker.extend(Module.new do
|
38
|
+
define_method(:some_method) do |*args|
|
39
|
+
status_container.status.should == 'working'
|
40
|
+
has_been_run = true
|
41
|
+
end
|
42
|
+
end)
|
43
|
+
|
44
|
+
jid = SomeWorker.perform_async(*args)
|
45
|
+
worker.perform(jid)
|
46
|
+
|
47
|
+
has_been_run.should be_true
|
48
|
+
worker.status_container.reload.status.should == 'complete'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "intercepts failures and set status to 'failed' then re-raises the exception" do
|
52
|
+
exc = RuntimeError.new('Some error')
|
53
|
+
worker.stub(:some_method).and_raise(exc)
|
54
|
+
|
55
|
+
jid = SomeWorker.perform_async(*args)
|
56
|
+
|
57
|
+
expect{ worker.perform(jid) }.to raise_exception(exc)
|
58
|
+
|
59
|
+
container = SidekiqStatus::Container.load(jid)
|
60
|
+
container.status.should == 'failed'
|
61
|
+
end
|
62
|
+
|
63
|
+
it "sets status to 'complete' if finishes without errors" do
|
64
|
+
jid = SomeWorker.perform_async(*args)
|
65
|
+
worker.perform(jid)
|
66
|
+
|
67
|
+
container = SidekiqStatus::Container.load(jid)
|
68
|
+
container.status.should == 'complete'
|
69
|
+
end
|
70
|
+
|
71
|
+
it "handles kill requests if kill requested before job execution" do
|
72
|
+
jid = SomeWorker.perform_async(*args)
|
73
|
+
container = SidekiqStatus::Container.load(jid)
|
74
|
+
container.request_kill
|
75
|
+
|
76
|
+
worker.perform(jid)
|
77
|
+
|
78
|
+
container.reload
|
79
|
+
container.status.should == 'killed'
|
80
|
+
end
|
81
|
+
|
82
|
+
it "handles kill requests if kill requested amid job execution" do
|
83
|
+
jid = SomeWorker.perform_async(*args)
|
84
|
+
container = SidekiqStatus::Container.load(jid)
|
85
|
+
container.status.should == 'waiting'
|
86
|
+
|
87
|
+
i = 0
|
88
|
+
i_mut = Mutex.new
|
89
|
+
|
90
|
+
worker.extend(Module.new do
|
91
|
+
define_method(:some_method) do |*args|
|
92
|
+
loop do
|
93
|
+
i_mut.synchronize do
|
94
|
+
i += 1
|
95
|
+
end
|
96
|
+
|
97
|
+
status_container.at = i
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end)
|
101
|
+
|
102
|
+
worker_thread = Thread.new{ worker.perform(jid) }
|
103
|
+
|
104
|
+
|
105
|
+
killer_thread = Thread.new do
|
106
|
+
sleep(0.01) while i < 100
|
107
|
+
container.reload.status.should == 'working'
|
108
|
+
container.request_kill
|
109
|
+
end
|
110
|
+
|
111
|
+
worker_thread.join(2)
|
112
|
+
killer_thread.join(1)
|
113
|
+
|
114
|
+
container.reload
|
115
|
+
container.status.should == 'killed'
|
116
|
+
container.at.should >= 100
|
117
|
+
end
|
118
|
+
|
119
|
+
it "allows to set at, total and customer payload from the worker" do
|
120
|
+
jid = SomeWorker.perform_async(*args)
|
121
|
+
container = SidekiqStatus::Container.load(jid)
|
122
|
+
|
123
|
+
ready = false
|
124
|
+
lets_stop = false
|
125
|
+
|
126
|
+
worker.extend(Module.new do
|
127
|
+
define_method(:some_method) do |*args|
|
128
|
+
self.total=(200)
|
129
|
+
self.at(50, "25% done")
|
130
|
+
self.payload = 'some payload'
|
131
|
+
ready = true
|
132
|
+
sleep(0.01) unless lets_stop
|
133
|
+
end
|
134
|
+
end)
|
135
|
+
|
136
|
+
worker_thread = Thread.new{ worker.perform(jid) }
|
137
|
+
checker_thread = Thread.new do
|
138
|
+
sleep(0.01) unless ready
|
139
|
+
|
140
|
+
container.reload
|
141
|
+
container.status.should == 'working'
|
142
|
+
container.at.should == 50
|
143
|
+
container.total.should == 200
|
144
|
+
container.message.should == '25% done'
|
145
|
+
container.payload == 'some payload'
|
146
|
+
|
147
|
+
lets_stop = true
|
148
|
+
end
|
149
|
+
|
150
|
+
worker_thread.join(10)
|
151
|
+
checker_thread.join(10)
|
152
|
+
|
153
|
+
container.reload
|
154
|
+
container.status.should == 'complete'
|
155
|
+
container.payload.should == 'some payload'
|
156
|
+
container.message.should be_nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
h1.wi
|
2
|
+
| Job #{@status.jid} is #{@status.status} (#{@status.pct_complete}% done)
|
3
|
+
p.intro
|
4
|
+
|
5
|
+
div id="status_#{@status.jid}" rel="#{@status.status}"
|
6
|
+
h2 Details
|
7
|
+
div
|
8
|
+
table class="table table-striped table-bordered"
|
9
|
+
thead
|
10
|
+
tr
|
11
|
+
th Attribute
|
12
|
+
th Value
|
13
|
+
tbody
|
14
|
+
tr
|
15
|
+
td jid
|
16
|
+
td= @status.jid
|
17
|
+
tr
|
18
|
+
td status
|
19
|
+
td= @status.status
|
20
|
+
tr
|
21
|
+
td last updated at
|
22
|
+
td= @status.last_updated_at
|
23
|
+
tr
|
24
|
+
td at
|
25
|
+
td= @status.at
|
26
|
+
tr
|
27
|
+
td total
|
28
|
+
td= @status.total
|
29
|
+
tr
|
30
|
+
td message
|
31
|
+
td= @status.message
|
32
|
+
tr
|
33
|
+
td payload
|
34
|
+
td
|
35
|
+
code class="prettyprint language-javascript"
|
36
|
+
= @status.payload.to_json
|
37
|
+
tr
|
38
|
+
td job args
|
39
|
+
td
|
40
|
+
code class="prettyprint language-javascript"
|
41
|
+
= @status.args.to_json
|
42
|
+
|
43
|
+
a href=(to(:statuses)) Back
|
44
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
h1.wi Recent job statuses
|
2
|
+
|
3
|
+
div.delete_jobs
|
4
|
+
| Delete jobs in
|
5
|
+
a href="#{to(:statuses)}/delete/complete" onclick="return confirm('Are you sure? Delete is irreversible')" = "complete"
|
6
|
+
|,
|
7
|
+
a href="#{to(:statuses)}/delete/finished" onclick="return confirm('Are you sure? Delete is irreversible')" title="#{SidekiqStatus::Container::FINISHED_STATUS_NAMES.join(', ')}" = "finished"
|
8
|
+
|,
|
9
|
+
a href="#{to(:statuses)}/delete/all" onclick="return confirm('Are you sure? Delete is irreversible')" = "all"
|
10
|
+
| statuses
|
11
|
+
|
12
|
+
table class="table table-striped table-bordered"
|
13
|
+
tr
|
14
|
+
th jid
|
15
|
+
th Status
|
16
|
+
th Last Updated ↆ
|
17
|
+
th Progress
|
18
|
+
th Message
|
19
|
+
- @statuses.each do |container|
|
20
|
+
tr
|
21
|
+
td
|
22
|
+
a href="#{to(:statuses)}/#{container.jid}" = container.jid
|
23
|
+
td= container.status
|
24
|
+
td= container.last_updated_at
|
25
|
+
td
|
26
|
+
- if container.killable?
|
27
|
+
a.kill href="#{to(:statuses)}/#{container.jid}/kill" onclick="return confirm('Are you sure?');" Kill
|
28
|
+
- elsif container.kill_requested?
|
29
|
+
|Kill requested
|
30
|
+
td= container.message
|
31
|
+
- if @statuses.empty?
|
32
|
+
tr
|
33
|
+
td colspan="5"
|
34
|
+
|
35
|
+
== slim :_paging, :locals => { :url => "#{root_path}statuses" }
|
36
|
+
|
37
|
+
|