sidekiq_status 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|