q 0.0.0 → 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +2 -3
- data/QUEUE_AUTHORS.md +444 -0
- data/README.md +179 -24
- data/Rakefile +13 -0
- data/lib/q.rb +79 -2
- data/lib/q/errors.rb +32 -0
- data/lib/q/helpers.rb +25 -0
- data/lib/q/methods.rb +30 -0
- data/lib/q/methods/base.rb +53 -0
- data/lib/q/methods/delayed_job.rb +76 -0
- data/lib/q/methods/resque.rb +73 -0
- data/lib/q/methods/sidekiq.rb +123 -0
- data/lib/q/methods/threaded_in_memory_queue.rb +66 -0
- data/lib/q/tasks.rb +14 -0
- data/lib/q/version.rb +1 -1
- data/q.gemspec +25 -19
- data/test/methods/base_test.rb +103 -0
- data/test/methods/delayed_job_test.rb +35 -0
- data/test/methods/resque_test.rb +37 -0
- data/test/methods/sidekiq_test.rb +37 -0
- data/test/methods/threaded_test.rb +89 -0
- data/test/methods_test.rb +63 -0
- data/test/q_test.rb +12 -0
- data/test/support/delayed_job.rb +66 -0
- data/test/support/resque.rb +25 -0
- data/test/support/sidekiq.rb +38 -0
- data/test/support/threaded_in_memory_queue.rb +5 -0
- data/test/test_helper.rb +22 -0
- metadata +174 -120
- data/.rvmrc +0 -2
- data/bin/q +0 -7
- data/spec/.gitignore +0 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
module Q::Methods::DelayedJob
|
2
|
+
include Q::Methods::Base
|
3
|
+
class NotSetupError < StandardError
|
4
|
+
def initialize
|
5
|
+
msg = "Delayed job not setup, please run:\n"
|
6
|
+
msg << " $ bundle exec rails generate delayed_job:active_record\n"
|
7
|
+
msg << " $ bundle exec rake db:migrate\n"
|
8
|
+
super msg
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class QueueConfig
|
13
|
+
def self.call
|
14
|
+
::Delayed::Job
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
class QueueTask
|
20
|
+
def self.call(*rake_args)
|
21
|
+
Rake::Task["jobs:work"].invoke(rake_args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# class NewsletterJob < Struct.new(:text, :emails)
|
26
|
+
# def perform
|
27
|
+
# emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
class QueueBuild
|
31
|
+
def self.call(options={}, &job)
|
32
|
+
base = options[:base]
|
33
|
+
queue_name = options[:queue_name]
|
34
|
+
queue_klass_name = options[:queue_klass_name]
|
35
|
+
|
36
|
+
raise NotSetupError unless ActiveRecord::Base.connection.table_exists? 'delayed_jobs'
|
37
|
+
raise Q::DuplicateQueueClassError.new(base, queue_klass_name) if Q.const_defined_on?(base, queue_klass_name)
|
38
|
+
|
39
|
+
queue_klass = Class.new do
|
40
|
+
def self.perform(*args)
|
41
|
+
@job.call(*args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.job=(job)
|
45
|
+
@job = job
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.queue=(queue)
|
49
|
+
@queue = queue
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
queue_klass.job = job
|
54
|
+
queue_klass.queue = queue_name
|
55
|
+
|
56
|
+
queue_klass = base.const_set(queue_klass_name, queue_klass)
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email))
|
62
|
+
class QueueMethod
|
63
|
+
def self.call(options = {})
|
64
|
+
base = options[:base]
|
65
|
+
queue_name = options[:queue_name]
|
66
|
+
queue_klass_name = options[:queue_klass_name]
|
67
|
+
queue_klass = base.const_get(queue_klass_name)
|
68
|
+
|
69
|
+
raise Q::DuplicateQueueMethodError.new(base, queue_name) if base.queue.respond_to?(queue_name)
|
70
|
+
|
71
|
+
base.queue.define_singleton_method(queue_name) do |*args|
|
72
|
+
::Delayed::Job.enqueue(queue_klass, *args)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Q::Methods::Resque
|
2
|
+
include Q::Methods::Base
|
3
|
+
|
4
|
+
class QueueConfig
|
5
|
+
def self.call
|
6
|
+
::Resque
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class QueueTask
|
11
|
+
def self.call(*rake_args)
|
12
|
+
Resque.logger.level ||= Integer(ENV['VVERBOSE'] || 1)
|
13
|
+
ENV['QUEUE'] ||= "*"
|
14
|
+
ENV['VERBOSE'] ||= "1"
|
15
|
+
ENV['TERM_CHILD'] ||= '1'
|
16
|
+
ENV['VVERBOSE'] = nil
|
17
|
+
define_setup!
|
18
|
+
Rake::Task["resque:work"].invoke(rake_args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.define_setup!
|
22
|
+
return true unless Rake::Task.task_defined?("resque:setup")
|
23
|
+
Rake::Task.define_task("resque:setup" => :environment) do
|
24
|
+
Resque.before_fork = Proc.new { ActiveRecord::Base.establish_connection } if defined?(ActiveRecord)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class QueueBuild
|
30
|
+
def self.call(options={}, &job)
|
31
|
+
base = options[:base]
|
32
|
+
queue_name = options[:queue_name]
|
33
|
+
queue_klass_name = options[:queue_klass_name]
|
34
|
+
|
35
|
+
raise Q::DuplicateQueueClassError.new(base, queue_klass_name) if Q.const_defined_on?(base, queue_klass_name)
|
36
|
+
|
37
|
+
queue_klass = Class.new do
|
38
|
+
def self.perform(*args)
|
39
|
+
@job.call(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.job=(job)
|
43
|
+
@job = job
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.queue=(queue)
|
47
|
+
@queue = queue
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
queue_klass.job = job
|
52
|
+
queue_klass.queue = queue_name
|
53
|
+
|
54
|
+
queue_klass = base.const_set(queue_klass_name, queue_klass)
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class QueueMethod
|
60
|
+
def self.call(options = {})
|
61
|
+
base = options[:base]
|
62
|
+
queue_name = options[:queue_name]
|
63
|
+
queue_klass_name = options[:queue_klass_name]
|
64
|
+
queue_klass = base.const_get(queue_klass_name)
|
65
|
+
|
66
|
+
raise Q::DuplicateQueueMethodError.new(base, queue_name) if base.queue.respond_to?(queue_name)
|
67
|
+
|
68
|
+
base.queue.define_singleton_method(queue_name) do |*args|
|
69
|
+
::Resque.enqueue(queue_klass, *args)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Q::Methods::Sidekiq
|
2
|
+
include Q::Methods::Base
|
3
|
+
|
4
|
+
class QueueConfig
|
5
|
+
def self.call
|
6
|
+
setup_inline!
|
7
|
+
::Sidekiq
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.setup_inline!
|
11
|
+
return if @regular_client
|
12
|
+
@regular_client = ::Sidekiq::Client
|
13
|
+
|
14
|
+
::Sidekiq.define_singleton_method(:inline) do
|
15
|
+
::Sidekiq::Client == @inline_client
|
16
|
+
end
|
17
|
+
|
18
|
+
::Sidekiq.define_singleton_method(:inline=) do |val|
|
19
|
+
@regular_client ||= ::Sidekiq::Client
|
20
|
+
|
21
|
+
if val
|
22
|
+
require 'sidekiq/testing/inline'
|
23
|
+
@inline_client ||= ::Sidekiq::Client
|
24
|
+
Sidekiq.const_set("Client", @inline_client)
|
25
|
+
else
|
26
|
+
Sidekiq.const_set("Client", @regular_client)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class QueueTask
|
33
|
+
|
34
|
+
# -c, --concurrency INT processor threads to use
|
35
|
+
# -d, --daemon Daemonize process
|
36
|
+
# -e, --environment ENV Application environment
|
37
|
+
# -g, --tag TAG Process tag for procline
|
38
|
+
# -i, --index INT unique process index on this machine
|
39
|
+
# -p, --profile Profile all code run by Sidekiq
|
40
|
+
# -q, --queue QUEUE[,WEIGHT]... Queues to process with optional weights
|
41
|
+
# -r, --require [PATH|DIR] Location of Rails application with workers or file to require
|
42
|
+
# -t, --timeout NUM Shutdown timeout
|
43
|
+
# -v, --verbose Print more verbose output
|
44
|
+
# -C, --config PATH path to YAML config file
|
45
|
+
# -L, --logfile PATH path to writable logfile
|
46
|
+
# -P, --pidfile PATH path to pidfile
|
47
|
+
# -V, --version Print version and exit
|
48
|
+
# -h, --help Show help
|
49
|
+
def self.call(*args)
|
50
|
+
setup!
|
51
|
+
cmd = "bundle exec sidekiq "
|
52
|
+
cmd << ENV["QUEUE"] if ENV["QUEUE"]
|
53
|
+
cmd << args.join(" ") if args.any?
|
54
|
+
puts cmd.inspect
|
55
|
+
exec cmd
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.setup!
|
59
|
+
return unless database_url = ENV['DATABASE_URL']
|
60
|
+
Sidekiq.configure_server do |config|
|
61
|
+
ENV['DATABASE_URL'] = "#{database_url}?pool=25"
|
62
|
+
ActiveRecord::Base.establish_connection
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# example
|
68
|
+
# class SinatraWorker
|
69
|
+
# include Sidekiq::Worker
|
70
|
+
#
|
71
|
+
# def perform(msg="lulz you forgot a msg!")
|
72
|
+
# $redis.lpush("sinkiq-example-messages", msg)
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
class QueueBuild
|
76
|
+
def self.call(options={}, &job)
|
77
|
+
base = options[:base]
|
78
|
+
queue_name = options[:queue_name]
|
79
|
+
queue_klass_name = options[:queue_klass_name]
|
80
|
+
|
81
|
+
raise Q::DuplicateQueueClassError.new(base, queue_klass_name) if Q.const_defined_on?(base, queue_klass_name)
|
82
|
+
|
83
|
+
queue_klass = Class.new do
|
84
|
+
include ::Sidekiq::Worker
|
85
|
+
|
86
|
+
def perform(*args)
|
87
|
+
@job.call(*args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.job=(job)
|
91
|
+
@job = job
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.queue=(queue)
|
95
|
+
@queue = queue
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
queue_klass.job = job
|
100
|
+
queue_klass.queue = queue_name
|
101
|
+
|
102
|
+
queue_klass = base.const_set(queue_klass_name, queue_klass)
|
103
|
+
return true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Example
|
108
|
+
# SinatraWorker.perform_async params[:msg]
|
109
|
+
class QueueMethod
|
110
|
+
def self.call(options = {})
|
111
|
+
base = options[:base]
|
112
|
+
queue_name = options[:queue_name]
|
113
|
+
queue_klass_name = options[:queue_klass_name]
|
114
|
+
queue_klass = base.const_get(queue_klass_name)
|
115
|
+
|
116
|
+
raise Q::DuplicateQueueMethodError.new(base, queue_name) if base.queue.respond_to?(queue_name)
|
117
|
+
|
118
|
+
base.queue.define_singleton_method(queue_name) do |*args|
|
119
|
+
queue_klass.perform_async(*args)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Q::Methods::ThreadedInMemoryQueue
|
2
|
+
def self.included(base)
|
3
|
+
require 'threaded_in_memory_queue'
|
4
|
+
at_exit do
|
5
|
+
ThreadedInMemoryQueue.stop unless ::ThreadedInMemoryQueue.stopped?
|
6
|
+
end
|
7
|
+
super base
|
8
|
+
end
|
9
|
+
|
10
|
+
include Q::Methods::Base
|
11
|
+
|
12
|
+
class QueueConfig
|
13
|
+
def self.call
|
14
|
+
::ThreadedInMemoryQueue
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class QueueTask
|
19
|
+
def self.call(*rake_args)
|
20
|
+
raise "Threaded In Memory Queue runs in web process, no need to start"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class QueueBuild
|
25
|
+
def self.call(options={}, &job)
|
26
|
+
base = options[:base]
|
27
|
+
queue_name = options[:queue_name]
|
28
|
+
queue_klass_name = options[:queue_klass_name]
|
29
|
+
|
30
|
+
raise Q::DuplicateQueueClassError.new(base, queue_klass_name) if Q.const_defined_on?(base, queue_klass_name)
|
31
|
+
|
32
|
+
queue_klass = Class.new do
|
33
|
+
def self.call(*args)
|
34
|
+
@job.call(*args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.job=(job)
|
38
|
+
@job = job
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
queue_klass.job = job
|
43
|
+
base.const_set(queue_klass_name, queue_klass)
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class QueueMethod
|
49
|
+
def self.call(options = {})
|
50
|
+
base = options[:base]
|
51
|
+
queue_name = options[:queue_name]
|
52
|
+
queue_klass_name = options[:queue_klass_name]
|
53
|
+
queue_klass = base.const_get(queue_klass_name)
|
54
|
+
|
55
|
+
raise Q::DuplicateQueueMethodError.new(base, queue_name) if base.queue.respond_to?(queue_name)
|
56
|
+
base.queue.define_singleton_method(queue_name) do |*args|
|
57
|
+
::ThreadedInMemoryQueue.start unless ::ThreadedInMemoryQueue.started?
|
58
|
+
::ThreadedInMemoryQueue.enqueue(queue_klass, *args)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# alias
|
65
|
+
Q::Methods::Threaded = Q::Methods::ThreadedInMemoryQueue
|
66
|
+
Q.queue_lookup[:threaded] = Proc.new {Q::Methods::Threaded}
|
data/lib/q/tasks.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Q
|
2
|
+
module QueueTask
|
3
|
+
def self.call(*args)
|
4
|
+
Q.queue::QueueTask.call(args)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
if defined?(Rake)
|
10
|
+
task = Rake::Task.define_task("q:work" => :environment) do |t, args|
|
11
|
+
Q::QueueTask.call(args)
|
12
|
+
end
|
13
|
+
task.add_description "Processes background work using the Q library"
|
14
|
+
end
|
data/lib/q/version.rb
CHANGED
data/q.gemspec
CHANGED
@@ -1,24 +1,30 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'q/version'
|
4
5
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "q"
|
8
|
+
gem.version = Q::VERSION
|
9
|
+
gem.authors = ["Richard Schneeman"]
|
10
|
+
gem.email = ["richard.schneeman+rubygems@gmail.com"]
|
11
|
+
gem.description = %q{ A universal interface for Ruby queueing backends. }
|
12
|
+
gem.summary = %q{ Use Q to switch betwen queue backends as you please. Simplifies creating and calling background jobs }
|
13
|
+
gem.homepage = "https://github.com/schneems/q"
|
14
|
+
gem.license = "MIT"
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
s.add_dependency("dm-migrations")
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
gem.add_dependency "proc_to_lambda"
|
22
|
+
gem.add_dependency "threaded_in_memory_queue"
|
23
|
+
|
24
|
+
gem.add_development_dependency "rake"
|
25
|
+
gem.add_development_dependency "mocha"
|
26
|
+
gem.add_development_dependency "resque"
|
27
|
+
gem.add_development_dependency "sidekiq"
|
28
|
+
gem.add_development_dependency "delayed_job_active_record"
|
29
|
+
gem.add_development_dependency "sqlite3"
|
24
30
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module FakeQueueKlass
|
4
|
+
include Q::Methods::Base
|
5
|
+
|
6
|
+
class QueueMethod; end
|
7
|
+
class QueueBuild; end
|
8
|
+
class QueueTask; end
|
9
|
+
class QueueConfig; end
|
10
|
+
end
|
11
|
+
|
12
|
+
class FakeUserKlass
|
13
|
+
include FakeQueueKlass
|
14
|
+
end
|
15
|
+
|
16
|
+
module FakeIncludesKlass
|
17
|
+
def self.bar(val)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.included(klass)
|
21
|
+
self.bar
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
include FakeQueueKlass
|
26
|
+
end
|
27
|
+
|
28
|
+
class QMethodsBaseTest < Test::Unit::TestCase
|
29
|
+
|
30
|
+
def test_includes_propper_things
|
31
|
+
assert_includes FakeQueueKlass, Q::Methods::Base
|
32
|
+
assert_respond_to FakeUserKlass, :queue
|
33
|
+
assert_respond_to FakeUserKlass.new, :queue
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_queue_klass
|
37
|
+
assert_equal FakeQueueKlass, FakeUserKlass.class_variable_get("@@_q_klass")
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_includes_gets_called
|
41
|
+
FakeIncludesKlass.expects(:bar).once
|
42
|
+
Class.new do
|
43
|
+
include FakeIncludesKlass
|
44
|
+
end
|
45
|
+
assert_includes FakeIncludesKlass, Q::Methods::Base
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_raises_error_missing_klasses
|
49
|
+
# assert no raise
|
50
|
+
foo = Module.new do
|
51
|
+
include Q::Methods::Base
|
52
|
+
end
|
53
|
+
foo.const_set(:QueueMethod, "")
|
54
|
+
foo.const_set(:QueueBuild, "")
|
55
|
+
foo.const_set(:QueueTask, "")
|
56
|
+
foo.const_set(:QueueConfig, "")
|
57
|
+
Class.new { include foo }
|
58
|
+
|
59
|
+
error = assert_raise(Q::MissingClassError) do
|
60
|
+
foo = Module.new do
|
61
|
+
include Q::Methods::Base
|
62
|
+
end
|
63
|
+
foo.const_set(:QueueBuild, "")
|
64
|
+
foo.const_set(:QueueTask, "")
|
65
|
+
foo.const_set(:QueueConfig, "")
|
66
|
+
Class.new { include foo }
|
67
|
+
end
|
68
|
+
assert_match "QueueMethod", error.message
|
69
|
+
|
70
|
+
error = assert_raise(Q::MissingClassError) do
|
71
|
+
foo = Module.new do
|
72
|
+
include Q::Methods::Base
|
73
|
+
end
|
74
|
+
foo.const_set(:QueueMethod, "")
|
75
|
+
foo.const_set(:QueueTask, "")
|
76
|
+
foo.const_set(:QueueConfig, "")
|
77
|
+
Class.new { include foo }
|
78
|
+
end
|
79
|
+
assert_match "QueueBuild", error.message
|
80
|
+
|
81
|
+
error = assert_raise(Q::MissingClassError) do
|
82
|
+
foo = Module.new do
|
83
|
+
include Q::Methods::Base
|
84
|
+
end
|
85
|
+
foo.const_set(:QueueMethod, "")
|
86
|
+
foo.const_set(:QueueBuild, "")
|
87
|
+
foo.const_set(:QueueConfig, "")
|
88
|
+
Class.new { include foo }
|
89
|
+
end
|
90
|
+
assert_match "QueueTask", error.message
|
91
|
+
|
92
|
+
error = assert_raise(Q::MissingClassError) do
|
93
|
+
foo = Module.new do
|
94
|
+
include Q::Methods::Base
|
95
|
+
end
|
96
|
+
foo.const_set(:QueueMethod, "")
|
97
|
+
foo.const_set(:QueueBuild, "")
|
98
|
+
foo.const_set(:QueueTask, "")
|
99
|
+
Class.new { include foo }
|
100
|
+
end
|
101
|
+
assert_match "QueueConfig", error.message
|
102
|
+
end
|
103
|
+
end
|