qs 0.3.0 → 0.4.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/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
|