sidekiq 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- data/.gitignore +5 -0
- data/.rvmrc +2 -1
- data/Changes.md +11 -0
- data/LICENSE +11 -4
- data/README.md +1 -7
- data/TODO.md +0 -1
- data/bin/sidekiq +2 -0
- data/examples/por.rb +17 -0
- data/examples/sinkiq.rb +57 -0
- data/lib/sidekiq.rb +1 -1
- data/lib/sidekiq/cli.rb +72 -34
- data/lib/sidekiq/client.rb +33 -16
- data/lib/sidekiq/manager.rb +37 -47
- data/lib/sidekiq/middleware/chain.rb +92 -0
- data/lib/sidekiq/middleware/client/resque_web_compatibility.rb +17 -0
- data/lib/sidekiq/middleware/client/unique_jobs.rb +30 -0
- data/lib/sidekiq/middleware/server/active_record.rb +13 -0
- data/lib/sidekiq/middleware/server/airbrake.rb +30 -0
- data/lib/sidekiq/middleware/server/unique_jobs.rb +17 -0
- data/lib/sidekiq/processor.rb +74 -16
- data/lib/sidekiq/redis_connection.rb +23 -0
- data/lib/sidekiq/testing.rb +34 -0
- data/lib/sidekiq/util.rb +21 -12
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/worker.rb +6 -7
- data/myapp/Gemfile +4 -1
- data/myapp/Gemfile.lock +29 -6
- data/myapp/app/controllers/work_controller.rb +9 -0
- data/myapp/app/views/work/index.html.erb +1 -0
- data/myapp/app/workers/hard_worker.rb +4 -2
- data/myapp/config/environments/development.rb +1 -0
- data/myapp/config/initializers/sidekiq.rb +1 -0
- data/myapp/config/routes.rb +4 -56
- data/sidekiq.gemspec +1 -0
- data/test/fake_env.rb +0 -0
- data/test/helper.rb +3 -0
- data/test/test_cli.rb +49 -0
- data/test/test_client.rb +52 -10
- data/test/test_manager.rb +11 -6
- data/test/test_middleware.rb +39 -20
- data/test/test_processor.rb +3 -2
- data/test/test_stats.rb +79 -0
- data/test/test_testing.rb +32 -0
- metadata +47 -18
- data/Gemfile.lock +0 -32
- data/lib/sidekiq/middleware.rb +0 -89
- data/test/timed_queue.rb +0 -42
@@ -0,0 +1,92 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
# Middleware is code configured to run before/after
|
3
|
+
# a message is processed. It is patterned after Rack
|
4
|
+
# middleware. Middleware exists for the client side
|
5
|
+
# (pushing jobs onto the queue) as well as the server
|
6
|
+
# side (when jobs are actually processed).
|
7
|
+
#
|
8
|
+
# Default middleware for the server side:
|
9
|
+
#
|
10
|
+
# Sidekiq::Processor.middleware.register do
|
11
|
+
# use Middleware::Server::Airbrake
|
12
|
+
# use Middleware::Server::ActiveRecord
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# To add middleware for the client, do:
|
16
|
+
#
|
17
|
+
# Sidekiq::Client.middleware.register do
|
18
|
+
# use MyClientHook
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# To add middleware for the server, do:
|
22
|
+
#
|
23
|
+
# Sidekiq::Processor.middleware.register do
|
24
|
+
# use MyServerHook
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# This is an example of a minimal middleware:
|
28
|
+
#
|
29
|
+
# class MyHook
|
30
|
+
# def initialize(options=nil)
|
31
|
+
# end
|
32
|
+
# def call(worker, msg)
|
33
|
+
# puts "Before work"
|
34
|
+
# yield
|
35
|
+
# puts "After work"
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
module Middleware
|
40
|
+
class Chain
|
41
|
+
attr_reader :entries
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@entries = []
|
45
|
+
end
|
46
|
+
|
47
|
+
def register(&block)
|
48
|
+
instance_eval(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def unregister(klass)
|
52
|
+
entries.delete_if { |entry| entry.klass == klass }
|
53
|
+
end
|
54
|
+
|
55
|
+
def use(klass, *args)
|
56
|
+
entries << Entry.new(klass, *args) unless exists?(klass)
|
57
|
+
end
|
58
|
+
|
59
|
+
def exists?(klass)
|
60
|
+
entries.any? { |entry| entry.klass == klass }
|
61
|
+
end
|
62
|
+
|
63
|
+
def retrieve
|
64
|
+
entries.map(&:make_new)
|
65
|
+
end
|
66
|
+
|
67
|
+
def invoke(*args, &final_action)
|
68
|
+
chain = retrieve.dup
|
69
|
+
traverse_chain = lambda do
|
70
|
+
if chain.empty?
|
71
|
+
final_action.call
|
72
|
+
else
|
73
|
+
chain.shift.call(*args, &traverse_chain)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
traverse_chain.call
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Entry
|
81
|
+
attr_reader :klass
|
82
|
+
def initialize(klass, *args)
|
83
|
+
@klass = klass
|
84
|
+
@args = args
|
85
|
+
end
|
86
|
+
|
87
|
+
def make_new
|
88
|
+
@klass.new(*@args)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Middleware
|
5
|
+
module Client
|
6
|
+
class UniqueJobs
|
7
|
+
HASH_KEY_EXPIRATION = 30 * 60
|
8
|
+
|
9
|
+
def initialize(redis)
|
10
|
+
@redis = redis
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(item, queue)
|
14
|
+
payload_hash = Digest::MD5.hexdigest(MultiJson.encode(item))
|
15
|
+
return if already_scheduled?(payload_hash)
|
16
|
+
|
17
|
+
@redis.setex(payload_hash, HASH_KEY_EXPIRATION, 1)
|
18
|
+
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def already_scheduled?(payload_hash)
|
25
|
+
!!@redis.get(payload_hash)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'sidekiq/util'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Middleware
|
5
|
+
module Server
|
6
|
+
class Airbrake
|
7
|
+
include Util
|
8
|
+
def call(*args)
|
9
|
+
yield
|
10
|
+
rescue => ex
|
11
|
+
logger.warn ex
|
12
|
+
logger.warn ex.backtrace.join("\n")
|
13
|
+
send_to_airbrake(args[1], ex) if defined?(::Airbrake)
|
14
|
+
raise
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def send_to_airbrake(msg, ex)
|
20
|
+
::Airbrake.notify(:error_class => ex.class.name,
|
21
|
+
:error_message => "#{ex.class.name}: #{ex.message}",
|
22
|
+
:parameters => msg)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Middleware
|
3
|
+
module Server
|
4
|
+
class UniqueJobs
|
5
|
+
def initialize(redis)
|
6
|
+
@redis = redis
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(*args)
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
@redis.del(Digest::MD5.hexdigest(MultiJson.encode(args[1])))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -1,37 +1,95 @@
|
|
1
|
-
require 'sidekiq/util'
|
2
|
-
require 'sidekiq/middleware'
|
3
1
|
require 'celluloid'
|
4
2
|
|
3
|
+
require 'sidekiq/util'
|
4
|
+
require 'sidekiq/middleware/chain'
|
5
|
+
require 'sidekiq/middleware/server/active_record'
|
6
|
+
require 'sidekiq/middleware/server/airbrake'
|
7
|
+
require 'sidekiq/middleware/server/unique_jobs'
|
8
|
+
|
5
9
|
module Sidekiq
|
6
10
|
class Processor
|
7
11
|
include Util
|
8
12
|
include Celluloid
|
9
13
|
|
10
|
-
def
|
11
|
-
@
|
14
|
+
def self.middleware
|
15
|
+
@middleware ||= begin
|
16
|
+
chain = Middleware::Chain.new
|
17
|
+
|
18
|
+
# default middleware
|
19
|
+
chain.register do
|
20
|
+
use Middleware::Server::Airbrake
|
21
|
+
use Middleware::Server::UniqueJobs, Sidekiq::Manager.redis
|
22
|
+
use Middleware::Server::ActiveRecord
|
23
|
+
end
|
24
|
+
chain
|
25
|
+
end
|
12
26
|
end
|
13
27
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
@boss.processor_done!(current_actor)
|
28
|
+
def initialize(boss)
|
29
|
+
@boss = boss
|
30
|
+
redis.sadd('workers', self)
|
18
31
|
end
|
19
32
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
33
|
+
def process(msg, queue)
|
34
|
+
klass = constantize(msg['class'])
|
35
|
+
worker = klass.new
|
36
|
+
stats(worker, msg, queue) do
|
37
|
+
self.class.middleware.invoke(worker, msg, queue) do
|
24
38
|
worker.perform(*msg['args'])
|
25
|
-
else
|
26
|
-
chain.shift.call(worker, msg, &traverse_chain)
|
27
39
|
end
|
28
40
|
end
|
29
|
-
|
41
|
+
@boss.processor_done!(current_actor)
|
30
42
|
end
|
31
43
|
|
32
44
|
# See http://github.com/tarcieri/celluloid/issues/22
|
33
45
|
def inspect
|
34
|
-
"
|
46
|
+
"#<Processor #{to_s}>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
@str ||= "#{hostname}:#{Process.pid}-#{Thread.current.object_id}:default"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def stats(worker, msg, queue)
|
56
|
+
redis.with_connection do |conn|
|
57
|
+
conn.multi do
|
58
|
+
conn.set("worker:#{self}:started", Time.now.to_s)
|
59
|
+
conn.set("worker:#{self}", MultiJson.encode(:queue => queue, :payload => msg,
|
60
|
+
:run_at => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z")))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
dying = false
|
65
|
+
begin
|
66
|
+
yield
|
67
|
+
rescue
|
68
|
+
dying = true
|
69
|
+
# Uh oh, error. We will die so unregister as much as we can first.
|
70
|
+
redis.with_connection do |conn|
|
71
|
+
conn.multi do
|
72
|
+
conn.incrby("stat:failed", 1)
|
73
|
+
conn.del("stat:processed:#{self}")
|
74
|
+
conn.srem("workers", self)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
raise
|
78
|
+
ensure
|
79
|
+
redis.with_connection do |conn|
|
80
|
+
conn.multi do
|
81
|
+
conn.del("worker:#{self}")
|
82
|
+
conn.del("worker:#{self}:started")
|
83
|
+
conn.incrby("stat:processed", 1)
|
84
|
+
conn.incrby("stat:processed:#{self}", 1) unless dying
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
def hostname
|
92
|
+
@h ||= `hostname`.strip
|
35
93
|
end
|
36
94
|
end
|
37
95
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'connection_pool'
|
2
|
+
require 'redis/namespace'
|
3
|
+
|
4
|
+
module Sidekiq
|
5
|
+
class RedisConnection
|
6
|
+
def self.create(options={})
|
7
|
+
url = options[:url] || ENV['REDISTOGO_URL'] || 'redis://localhost:6379/0'
|
8
|
+
client = build_client(url, options[:namespace])
|
9
|
+
return ConnectionPool.new(:timeout => 1, :size => 25) { client } unless options[:use_pool] == false
|
10
|
+
client
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.build_client(url, namespace)
|
14
|
+
client = Redis.connect(:url => url)
|
15
|
+
if namespace
|
16
|
+
Redis::Namespace.new(namespace, :redis => client)
|
17
|
+
else
|
18
|
+
client
|
19
|
+
end
|
20
|
+
end
|
21
|
+
private_class_method :build_client
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Worker
|
3
|
+
|
4
|
+
##
|
5
|
+
# The Sidekiq testing infrastructure overrides perform_async
|
6
|
+
# so that it does not actually touch the network. Instead it
|
7
|
+
# stores the asynchronous jobs in a per-class array so that
|
8
|
+
# their presence/absence can be asserted by your tests.
|
9
|
+
#
|
10
|
+
# This is similar to ActionMailer's :test delivery_method and its
|
11
|
+
# ActionMailer::Base.deliveries array.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'sidekiq/testing'
|
16
|
+
#
|
17
|
+
# assert_equal 0, HardWorker.jobs.size
|
18
|
+
# HardWorker.perform_async(:something)
|
19
|
+
# assert_equal 1, HardWorker.jobs.size
|
20
|
+
# assert_equal :something, HardWorker.jobs[0]['args'][0]
|
21
|
+
#
|
22
|
+
module ClassMethods
|
23
|
+
alias_method :perform_async_old, :perform_async
|
24
|
+
def perform_async(*args)
|
25
|
+
jobs << { 'class' => self.name, 'args' => args }
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def jobs
|
30
|
+
@pushed ||= []
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/sidekiq/util.rb
CHANGED
@@ -1,6 +1,20 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
module Sidekiq
|
2
4
|
module Util
|
3
5
|
|
6
|
+
def self.logger
|
7
|
+
@logger ||= begin
|
8
|
+
log = Logger.new(STDERR)
|
9
|
+
log.level = Logger::INFO
|
10
|
+
log
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.logger=(log)
|
15
|
+
@logger = (log ? log : Logger.new('/dev/null'))
|
16
|
+
end
|
17
|
+
|
4
18
|
def constantize(camel_cased_word)
|
5
19
|
names = camel_cased_word.split('::')
|
6
20
|
names.shift if names.empty? || names.first.empty?
|
@@ -15,22 +29,17 @@ module Sidekiq
|
|
15
29
|
def watchdog(last_words)
|
16
30
|
yield
|
17
31
|
rescue => ex
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
def err(msg)
|
24
|
-
STDERR.puts(msg)
|
32
|
+
logger.error last_words
|
33
|
+
logger.error ex
|
34
|
+
logger.error ex.backtrace.join("\n")
|
25
35
|
end
|
26
36
|
|
27
|
-
def
|
28
|
-
|
37
|
+
def logger
|
38
|
+
Sidekiq::Util.logger
|
29
39
|
end
|
30
40
|
|
31
|
-
def
|
32
|
-
|
41
|
+
def redis
|
42
|
+
Sidekiq::Manager.redis
|
33
43
|
end
|
34
|
-
|
35
44
|
end
|
36
45
|
end
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/worker.rb
CHANGED
@@ -24,19 +24,18 @@ module Sidekiq
|
|
24
24
|
base.extend(ClassMethods)
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
|
29
|
-
end
|
30
|
-
alias_method :log, :info
|
31
|
-
|
32
|
-
def debug(msg)
|
33
|
-
print "#{msg}\n" if $DEBUG
|
27
|
+
def logger
|
28
|
+
Sidekiq::Util.logger
|
34
29
|
end
|
35
30
|
|
36
31
|
module ClassMethods
|
37
32
|
def perform_async(*args)
|
38
33
|
Sidekiq::Client.push('class' => self.name, 'args' => args)
|
39
34
|
end
|
35
|
+
|
36
|
+
def queue(name)
|
37
|
+
Sidekiq::Client.queues[self.name] = name.to_s
|
38
|
+
end
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|