qs 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bench/config.qs +4 -27
- data/bench/dispatcher.qs +24 -0
- data/bench/report.rb +80 -10
- data/bench/report.txt +10 -3
- data/bench/setup.rb +55 -0
- data/lib/qs.rb +75 -15
- data/lib/qs/client.rb +73 -22
- data/lib/qs/daemon.rb +21 -21
- data/lib/qs/daemon_data.rb +4 -4
- data/lib/qs/dispatch_job.rb +36 -0
- data/lib/qs/dispatch_job_handler.rb +79 -0
- data/lib/qs/dispatcher_queue.rb +19 -0
- data/lib/qs/error_handler.rb +12 -12
- data/lib/qs/event.rb +82 -0
- data/lib/qs/event_handler.rb +34 -0
- data/lib/qs/event_handler_test_helpers.rb +17 -0
- data/lib/qs/job.rb +19 -31
- data/lib/qs/job_handler.rb +6 -63
- data/lib/qs/{test_helpers.rb → job_handler_test_helpers.rb} +2 -2
- data/lib/qs/message.rb +29 -0
- data/lib/qs/message_handler.rb +84 -0
- data/lib/qs/payload.rb +98 -0
- data/lib/qs/payload_handler.rb +106 -54
- data/lib/qs/queue.rb +39 -6
- data/lib/qs/queue_item.rb +33 -0
- data/lib/qs/route.rb +7 -7
- data/lib/qs/runner.rb +6 -5
- data/lib/qs/test_runner.rb +41 -13
- data/lib/qs/version.rb +1 -1
- data/qs.gemspec +1 -1
- data/test/helper.rb +1 -1
- data/test/support/app_daemon.rb +77 -11
- data/test/support/factory.rb +34 -0
- data/test/system/daemon_tests.rb +146 -77
- data/test/system/queue_tests.rb +87 -0
- data/test/unit/client_tests.rb +184 -45
- data/test/unit/daemon_data_tests.rb +4 -4
- data/test/unit/daemon_tests.rb +32 -32
- data/test/unit/dispatch_job_handler_tests.rb +163 -0
- data/test/unit/dispatch_job_tests.rb +75 -0
- data/test/unit/dispatcher_queue_tests.rb +42 -0
- data/test/unit/error_handler_tests.rb +9 -9
- data/test/unit/event_handler_test_helpers_tests.rb +55 -0
- data/test/unit/event_handler_tests.rb +63 -0
- data/test/unit/event_tests.rb +162 -0
- data/test/unit/{test_helper_tests.rb → job_handler_test_helper_tests.rb} +13 -19
- data/test/unit/job_handler_tests.rb +17 -210
- data/test/unit/job_tests.rb +49 -79
- data/test/unit/message_handler_tests.rb +235 -0
- data/test/unit/message_tests.rb +64 -0
- data/test/unit/payload_handler_tests.rb +285 -86
- data/test/unit/payload_tests.rb +139 -0
- data/test/unit/qs_runner_tests.rb +6 -6
- data/test/unit/qs_tests.rb +167 -28
- data/test/unit/queue_item_tests.rb +51 -0
- data/test/unit/queue_tests.rb +126 -18
- data/test/unit/route_tests.rb +12 -13
- data/test/unit/runner_tests.rb +10 -10
- data/test/unit/test_runner_tests.rb +117 -24
- metadata +51 -21
- data/bench/queue.rb +0 -8
- data/lib/qs/redis_item.rb +0 -33
- data/test/unit/redis_item_tests.rb +0 -49
data/lib/qs/queue.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
require 'qs'
|
1
2
|
require 'qs/route'
|
2
3
|
|
3
4
|
module Qs
|
4
5
|
|
5
6
|
class Queue
|
6
7
|
|
7
|
-
attr_reader :routes
|
8
|
+
attr_reader :routes, :event_route_names
|
8
9
|
attr_reader :enqueued_jobs
|
9
10
|
|
10
11
|
def initialize(&block)
|
11
|
-
@routes
|
12
|
-
@
|
12
|
+
@routes = []
|
13
|
+
@event_route_names = []
|
14
|
+
@enqueued_jobs = []
|
13
15
|
self.instance_eval(&block) if !block.nil?
|
14
16
|
raise InvalidError, "a queue must have a name" if self.name.nil?
|
15
17
|
end
|
@@ -28,12 +30,30 @@ module Qs
|
|
28
30
|
@job_handler_ns
|
29
31
|
end
|
30
32
|
|
33
|
+
def event_handler_ns(value = nil)
|
34
|
+
@event_handler_ns = value if !value.nil?
|
35
|
+
@event_handler_ns
|
36
|
+
end
|
37
|
+
|
31
38
|
def job(name, handler_name)
|
32
39
|
if self.job_handler_ns && !(handler_name =~ /^::/)
|
33
40
|
handler_name = "#{self.job_handler_ns}::#{handler_name}"
|
34
41
|
end
|
35
42
|
|
36
|
-
|
43
|
+
route_id = Message::RouteId.new(Qs::Job::PAYLOAD_TYPE, name)
|
44
|
+
@routes.push(Qs::Route.new(route_id, handler_name))
|
45
|
+
end
|
46
|
+
|
47
|
+
def event(channel, name, handler_name)
|
48
|
+
if self.event_handler_ns && !(handler_name =~ /^::/)
|
49
|
+
handler_name = "#{self.event_handler_ns}::#{handler_name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
route_name = Qs::Event::RouteName.new(channel, name)
|
53
|
+
route_id = Qs::Message::RouteId.new(Qs::Event::PAYLOAD_TYPE, route_name)
|
54
|
+
|
55
|
+
@event_route_names.push(route_name)
|
56
|
+
@routes.push(Qs::Route.new(route_id, handler_name))
|
37
57
|
end
|
38
58
|
|
39
59
|
def enqueue(job_name, params = nil)
|
@@ -41,6 +61,18 @@ module Qs
|
|
41
61
|
end
|
42
62
|
alias :add :enqueue
|
43
63
|
|
64
|
+
def sync_subscriptions
|
65
|
+
Qs.sync_subscriptions(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
def clear_subscriptions
|
69
|
+
Qs.clear_subscriptions(self)
|
70
|
+
end
|
71
|
+
|
72
|
+
def published_events
|
73
|
+
self.enqueued_jobs.map(&:event)
|
74
|
+
end
|
75
|
+
|
44
76
|
def reset!
|
45
77
|
self.enqueued_jobs.clear
|
46
78
|
end
|
@@ -49,14 +81,15 @@ module Qs
|
|
49
81
|
reference = '0x0%x' % (self.object_id << 1)
|
50
82
|
"#<#{self.class}:#{reference} " \
|
51
83
|
"@name=#{self.name.inspect} " \
|
52
|
-
"@job_handler_ns=#{self.job_handler_ns.inspect}
|
84
|
+
"@job_handler_ns=#{self.job_handler_ns.inspect} " \
|
85
|
+
"@event_handler_ns=#{self.event_handler_ns.inspect}>"
|
53
86
|
end
|
54
87
|
|
55
88
|
InvalidError = Class.new(RuntimeError)
|
56
89
|
|
57
90
|
module RedisKey
|
58
91
|
def self.parse_name(key)
|
59
|
-
key.split(':').last
|
92
|
+
key.split(':', 2).last
|
60
93
|
end
|
61
94
|
|
62
95
|
def self.new(name)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Qs
|
2
|
+
|
3
|
+
class QueueItem
|
4
|
+
|
5
|
+
attr_reader :queue_redis_key, :encoded_payload
|
6
|
+
attr_accessor :started, :finished
|
7
|
+
attr_accessor :message, :handler_class
|
8
|
+
attr_accessor :exception, :time_taken
|
9
|
+
|
10
|
+
def initialize(queue_redis_key, encoded_payload)
|
11
|
+
@queue_redis_key = queue_redis_key
|
12
|
+
@encoded_payload = encoded_payload
|
13
|
+
@started = false
|
14
|
+
@finished = false
|
15
|
+
|
16
|
+
@message = nil
|
17
|
+
@handler_class = nil
|
18
|
+
@exception = nil
|
19
|
+
@time_taken = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
if other.kind_of?(self.class)
|
24
|
+
self.queue_redis_key == other.queue_redis_key &&
|
25
|
+
self.encoded_payload == other.encoded_payload
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/qs/route.rb
CHANGED
@@ -4,10 +4,10 @@ module Qs
|
|
4
4
|
|
5
5
|
class Route
|
6
6
|
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :id, :handler_class_name, :handler_class
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@
|
9
|
+
def initialize(route_id, handler_class_name)
|
10
|
+
@id = route_id.to_s
|
11
11
|
@handler_class_name = handler_class_name
|
12
12
|
@handler_class = nil
|
13
13
|
end
|
@@ -16,11 +16,11 @@ module Qs
|
|
16
16
|
@handler_class = constantize_handler_class(@handler_class_name)
|
17
17
|
end
|
18
18
|
|
19
|
-
def run(
|
19
|
+
def run(message, daemon_data)
|
20
20
|
QsRunner.new(self.handler_class, {
|
21
|
-
:
|
22
|
-
:params
|
23
|
-
:logger
|
21
|
+
:message => message,
|
22
|
+
:params => message.params,
|
23
|
+
:logger => daemon_data.logger
|
24
24
|
}).run
|
25
25
|
end
|
26
26
|
|
data/lib/qs/runner.rb
CHANGED
@@ -5,16 +5,17 @@ module Qs
|
|
5
5
|
class Runner
|
6
6
|
|
7
7
|
attr_reader :handler_class, :handler
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :message, :params, :logger
|
9
9
|
|
10
10
|
def initialize(handler_class, args = nil)
|
11
11
|
@handler_class = handler_class
|
12
|
-
@handler = @handler_class.new(self)
|
13
12
|
|
14
13
|
a = args || {}
|
15
|
-
@
|
16
|
-
@params
|
17
|
-
@logger
|
14
|
+
@message = a[:message]
|
15
|
+
@params = a[:params] || {}
|
16
|
+
@logger = a[:logger] || Qs::NullLogger.new
|
17
|
+
|
18
|
+
@handler = @handler_class.new(self)
|
18
19
|
end
|
19
20
|
|
20
21
|
def run
|
data/lib/qs/test_runner.rb
CHANGED
@@ -1,24 +1,19 @@
|
|
1
1
|
require 'qs'
|
2
|
-
require 'qs/
|
2
|
+
require 'qs/event_handler'
|
3
3
|
require 'qs/job_handler'
|
4
|
+
require 'qs/payload'
|
4
5
|
require 'qs/runner'
|
5
6
|
|
6
7
|
module Qs
|
7
8
|
|
8
|
-
InvalidJobHandlerError = Class.new(StandardError)
|
9
|
-
|
10
9
|
class TestRunner < Runner
|
11
10
|
|
12
11
|
def initialize(handler_class, args = nil)
|
13
|
-
if !handler_class.include?(Qs::JobHandler)
|
14
|
-
raise InvalidJobHandlerError, "#{handler_class.inspect} is not a"\
|
15
|
-
" Qs::JobHandler"
|
16
|
-
end
|
17
12
|
args = (args || {}).dup
|
18
13
|
super(handler_class, {
|
19
|
-
:
|
20
|
-
:params
|
21
|
-
:logger
|
14
|
+
:message => args.delete(:message),
|
15
|
+
:params => normalize_params(args.delete(:params) || {}),
|
16
|
+
:logger => args.delete(:logger)
|
22
17
|
})
|
23
18
|
args.each{ |key, value| self.handler.send("#{key}=", value) }
|
24
19
|
|
@@ -31,13 +26,46 @@ module Qs
|
|
31
26
|
|
32
27
|
private
|
33
28
|
|
34
|
-
# Stringify and
|
29
|
+
# Stringify and encode/decode to ensure params are valid and are
|
35
30
|
# in the format they would normally be when a handler is built and run.
|
36
31
|
def normalize_params(params)
|
37
|
-
params =
|
38
|
-
Qs.
|
32
|
+
params = Qs::Payload::StringifyParams.new(params)
|
33
|
+
Qs.decode(Qs.encode(params))
|
39
34
|
end
|
40
35
|
|
41
36
|
end
|
42
37
|
|
38
|
+
class JobTestRunner < TestRunner
|
39
|
+
|
40
|
+
def initialize(handler_class, args = nil)
|
41
|
+
if !handler_class.include?(Qs::JobHandler)
|
42
|
+
raise InvalidJobHandlerError, "#{handler_class.inspect} is not a"\
|
43
|
+
" Qs::JobHandler"
|
44
|
+
end
|
45
|
+
|
46
|
+
args = (args || {}).dup
|
47
|
+
args[:message] = args.delete(:job) if args.key?(:job)
|
48
|
+
super(handler_class, args)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
class EventTestRunner < TestRunner
|
54
|
+
|
55
|
+
def initialize(handler_class, args = nil)
|
56
|
+
if !handler_class.include?(Qs::EventHandler)
|
57
|
+
raise InvalidEventHandlerError, "#{handler_class.inspect} is not a"\
|
58
|
+
" Qs::EventHandler"
|
59
|
+
end
|
60
|
+
|
61
|
+
args = (args || {}).dup
|
62
|
+
args[:message] = args.delete(:event) if args.key?(:event)
|
63
|
+
super(handler_class, args)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
InvalidJobHandlerError = Class.new(StandardError)
|
69
|
+
InvalidEventHandlerError = Class.new(StandardError)
|
70
|
+
|
43
71
|
end
|
data/lib/qs/version.rb
CHANGED
data/qs.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
21
|
gem.add_dependency("dat-worker-pool", ["~> 0.5"])
|
22
|
-
gem.add_dependency("hella-redis", ["~> 0.
|
22
|
+
gem.add_dependency("hella-redis", ["~> 0.3"])
|
23
23
|
gem.add_dependency("ns-options", ["~> 1.1"])
|
24
24
|
gem.add_dependency("SystemTimer", ["~> 1.2"])
|
25
25
|
|
data/test/helper.rb
CHANGED
data/test/support/app_daemon.rb
CHANGED
@@ -4,7 +4,7 @@ LOGGER = Logger.new(ROOT_PATH.join('log/app_daemon.log').to_s)
|
|
4
4
|
LOGGER.datetime_format = "" # turn off the datetime in the logs
|
5
5
|
|
6
6
|
AppQueue = Qs::Queue.new do
|
7
|
-
name '
|
7
|
+
name 'qs-app-main'
|
8
8
|
|
9
9
|
job_handler_ns 'AppHandlers'
|
10
10
|
|
@@ -12,12 +12,19 @@ AppQueue = Qs::Queue.new do
|
|
12
12
|
job 'error', 'Error'
|
13
13
|
job 'timeout', 'Timeout'
|
14
14
|
job 'slow', 'Slow'
|
15
|
+
|
16
|
+
event_handler_ns 'AppHandlers'
|
17
|
+
|
18
|
+
event 'qs-app', 'basic', 'BasicEvent'
|
19
|
+
event 'qs-app', 'error', 'ErrorEvent'
|
20
|
+
event 'qs-app', 'timeout', 'TimeoutEvent'
|
21
|
+
event 'qs-app', 'slow', 'SlowEvent'
|
15
22
|
end
|
16
23
|
|
17
24
|
class AppDaemon
|
18
25
|
include Qs::Daemon
|
19
26
|
|
20
|
-
name 'app'
|
27
|
+
name 'qs-app'
|
21
28
|
|
22
29
|
logger LOGGER
|
23
30
|
verbose_logging true
|
@@ -25,25 +32,49 @@ class AppDaemon
|
|
25
32
|
queue AppQueue
|
26
33
|
|
27
34
|
error do |exception, context|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
Qs.redis.with{ |c| c.set(
|
35
|
+
return unless (message = context.message)
|
36
|
+
payload_type = message.payload_type
|
37
|
+
route_name = message.route_name
|
38
|
+
case(route_name)
|
39
|
+
when 'error', 'timeout', 'qs-app:error', 'qs-app:timeout'
|
40
|
+
error = "#{exception.class}: #{exception.message}"
|
41
|
+
Qs.redis.with{ |c| c.set("qs-app:last_#{payload_type}_error", error) }
|
42
|
+
when 'slow', 'qs-app:slow'
|
43
|
+
error = exception.class.to_s
|
44
|
+
Qs.redis.with{ |c| c.set("qs-app:last_#{payload_type}_error", error) }
|
35
45
|
end
|
36
46
|
end
|
37
47
|
|
38
48
|
end
|
39
49
|
|
50
|
+
DISPATCH_LOGGER = Logger.new(ROOT_PATH.join('log/app_dispatcher_daemon.log').to_s)
|
51
|
+
DISPATCH_LOGGER.datetime_format = "" # turn off the datetime in the logs
|
52
|
+
|
53
|
+
class DispatcherDaemon
|
54
|
+
include Qs::Daemon
|
55
|
+
|
56
|
+
name 'qs-app-dispatcher'
|
57
|
+
|
58
|
+
logger DISPATCH_LOGGER
|
59
|
+
verbose_logging true
|
60
|
+
|
61
|
+
# we build a "custom" dispatcher because we can't rely on Qs being initialized
|
62
|
+
# when this is required
|
63
|
+
queue Qs::DispatcherQueue.new({
|
64
|
+
:queue_class => Qs.config.dispatcher_queue_class,
|
65
|
+
:queue_name => 'qs-app-dispatcher',
|
66
|
+
:job_name => Qs.config.dispatcher.job_name,
|
67
|
+
:job_handler_class_name => Qs.config.dispatcher.job_handler_class_name
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
40
71
|
module AppHandlers
|
41
72
|
|
42
73
|
class Basic
|
43
74
|
include Qs::JobHandler
|
44
75
|
|
45
76
|
def run!
|
46
|
-
Qs.redis.with{ |c| c.set(params['key'], params['value']) }
|
77
|
+
Qs.redis.with{ |c| c.set("qs-app:#{params['key']}", params['value']) }
|
47
78
|
end
|
48
79
|
end
|
49
80
|
|
@@ -70,7 +101,42 @@ module AppHandlers
|
|
70
101
|
|
71
102
|
def run!
|
72
103
|
sleep 5
|
73
|
-
Qs.redis.with{ |c| c.set('slow', 'finished') }
|
104
|
+
Qs.redis.with{ |c| c.set('qs-app:slow', 'finished') }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class BasicEvent
|
109
|
+
include Qs::EventHandler
|
110
|
+
|
111
|
+
def run!
|
112
|
+
Qs.redis.with{ |c| c.set("qs-app:#{params['key']}", params['value']) }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class ErrorEvent
|
117
|
+
include Qs::EventHandler
|
118
|
+
|
119
|
+
def run!
|
120
|
+
raise params['error_message']
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class TimeoutEvent
|
125
|
+
include Qs::EventHandler
|
126
|
+
|
127
|
+
timeout 0.2
|
128
|
+
|
129
|
+
def run!
|
130
|
+
sleep 2
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class SlowEvent
|
135
|
+
include Qs::EventHandler
|
136
|
+
|
137
|
+
def run!
|
138
|
+
sleep 5
|
139
|
+
Qs.redis.with{ |c| c.set('qs-app:slow:event', 'finished') }
|
74
140
|
end
|
75
141
|
end
|
76
142
|
|
data/test/support/factory.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
require 'assert/factory'
|
2
|
+
require 'qs/dispatch_job'
|
3
|
+
require 'qs/job'
|
4
|
+
require 'qs/event'
|
2
5
|
|
3
6
|
module Factory
|
4
7
|
extend Assert::Factory
|
@@ -11,4 +14,35 @@ module Factory
|
|
11
14
|
exception
|
12
15
|
end
|
13
16
|
|
17
|
+
def self.message(params = nil)
|
18
|
+
self.send([:job, :event].choice, params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.job(args = nil)
|
22
|
+
args ||= {}
|
23
|
+
name = args.delete(:name) || Factory.string
|
24
|
+
args[:params] ||= { Factory.string => Factory.string }
|
25
|
+
args[:created_at] ||= Factory.time
|
26
|
+
Qs::Job.new(name, args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.dispatch_job(args = nil)
|
30
|
+
args ||= {}
|
31
|
+
event_channel = args.delete(:event_channel) || Factory.string
|
32
|
+
event_name = args.delete(:event_name) || Factory.string
|
33
|
+
args[:event_params] ||= { Factory.string => Factory.string }
|
34
|
+
args[:created_at] ||= Factory.time
|
35
|
+
Qs::DispatchJob.new(event_channel, event_name, args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.event(args = nil)
|
39
|
+
args ||= {}
|
40
|
+
channel = args.delete(:channel) || Factory.string
|
41
|
+
name = args.delete(:name) || Factory.string
|
42
|
+
args[:params] ||= { Factory.string => Factory.string }
|
43
|
+
args[:publisher] ||= Factory.string
|
44
|
+
args[:published_at] ||= Factory.time
|
45
|
+
Qs::Event.new(channel, name, args)
|
46
|
+
end
|
47
|
+
|
14
48
|
end
|