qs 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/bench/config.qs +4 -27
  2. data/bench/dispatcher.qs +24 -0
  3. data/bench/report.rb +80 -10
  4. data/bench/report.txt +10 -3
  5. data/bench/setup.rb +55 -0
  6. data/lib/qs.rb +75 -15
  7. data/lib/qs/client.rb +73 -22
  8. data/lib/qs/daemon.rb +21 -21
  9. data/lib/qs/daemon_data.rb +4 -4
  10. data/lib/qs/dispatch_job.rb +36 -0
  11. data/lib/qs/dispatch_job_handler.rb +79 -0
  12. data/lib/qs/dispatcher_queue.rb +19 -0
  13. data/lib/qs/error_handler.rb +12 -12
  14. data/lib/qs/event.rb +82 -0
  15. data/lib/qs/event_handler.rb +34 -0
  16. data/lib/qs/event_handler_test_helpers.rb +17 -0
  17. data/lib/qs/job.rb +19 -31
  18. data/lib/qs/job_handler.rb +6 -63
  19. data/lib/qs/{test_helpers.rb → job_handler_test_helpers.rb} +2 -2
  20. data/lib/qs/message.rb +29 -0
  21. data/lib/qs/message_handler.rb +84 -0
  22. data/lib/qs/payload.rb +98 -0
  23. data/lib/qs/payload_handler.rb +106 -54
  24. data/lib/qs/queue.rb +39 -6
  25. data/lib/qs/queue_item.rb +33 -0
  26. data/lib/qs/route.rb +7 -7
  27. data/lib/qs/runner.rb +6 -5
  28. data/lib/qs/test_runner.rb +41 -13
  29. data/lib/qs/version.rb +1 -1
  30. data/qs.gemspec +1 -1
  31. data/test/helper.rb +1 -1
  32. data/test/support/app_daemon.rb +77 -11
  33. data/test/support/factory.rb +34 -0
  34. data/test/system/daemon_tests.rb +146 -77
  35. data/test/system/queue_tests.rb +87 -0
  36. data/test/unit/client_tests.rb +184 -45
  37. data/test/unit/daemon_data_tests.rb +4 -4
  38. data/test/unit/daemon_tests.rb +32 -32
  39. data/test/unit/dispatch_job_handler_tests.rb +163 -0
  40. data/test/unit/dispatch_job_tests.rb +75 -0
  41. data/test/unit/dispatcher_queue_tests.rb +42 -0
  42. data/test/unit/error_handler_tests.rb +9 -9
  43. data/test/unit/event_handler_test_helpers_tests.rb +55 -0
  44. data/test/unit/event_handler_tests.rb +63 -0
  45. data/test/unit/event_tests.rb +162 -0
  46. data/test/unit/{test_helper_tests.rb → job_handler_test_helper_tests.rb} +13 -19
  47. data/test/unit/job_handler_tests.rb +17 -210
  48. data/test/unit/job_tests.rb +49 -79
  49. data/test/unit/message_handler_tests.rb +235 -0
  50. data/test/unit/message_tests.rb +64 -0
  51. data/test/unit/payload_handler_tests.rb +285 -86
  52. data/test/unit/payload_tests.rb +139 -0
  53. data/test/unit/qs_runner_tests.rb +6 -6
  54. data/test/unit/qs_tests.rb +167 -28
  55. data/test/unit/queue_item_tests.rb +51 -0
  56. data/test/unit/queue_tests.rb +126 -18
  57. data/test/unit/route_tests.rb +12 -13
  58. data/test/unit/runner_tests.rb +10 -10
  59. data/test/unit/test_runner_tests.rb +117 -24
  60. metadata +51 -21
  61. data/bench/queue.rb +0 -8
  62. data/lib/qs/redis_item.rb +0 -33
  63. data/test/unit/redis_item_tests.rb +0 -49
@@ -1,39 +1,18 @@
1
+ require 'qs/message_handler'
2
+
1
3
  module Qs
2
4
 
3
5
  module JobHandler
4
6
 
5
7
  def self.included(klass)
6
8
  klass.class_eval do
7
- extend ClassMethods
9
+ include Qs::MessageHandler
8
10
  include InstanceMethods
9
11
  end
10
12
  end
11
13
 
12
14
  module InstanceMethods
13
15
 
14
- def initialize(runner)
15
- @qs_runner = runner
16
- end
17
-
18
- def init
19
- run_callback 'before_init'
20
- self.init!
21
- run_callback 'after_init'
22
- end
23
-
24
- def init!
25
- end
26
-
27
- def run
28
- run_callback 'before_run'
29
- self.run!
30
- run_callback 'after_run'
31
- end
32
-
33
- def run!
34
- raise NotImplementedError
35
- end
36
-
37
16
  def inspect
38
17
  reference = '0x0%x' % (self.object_id << 1)
39
18
  "#<#{self.class}:#{reference} @job=#{job.inspect}>"
@@ -43,45 +22,9 @@ module Qs
43
22
 
44
23
  # Helpers
45
24
 
46
- def job; @qs_runner.job; end
47
- def params; @qs_runner.params; end
48
- def logger; @qs_runner.logger; end
49
-
50
- def run_callback(callback)
51
- (self.class.send("#{callback}_callbacks") || []).each do |callback|
52
- self.instance_eval(&callback)
53
- end
54
- end
55
-
56
- end
57
-
58
- module ClassMethods
59
-
60
- def timeout(value = nil)
61
- @timeout = value.to_f if value
62
- @timeout
63
- end
64
-
65
- def before_callbacks; @before_callbacks ||= []; end
66
- def after_callbacks; @after_callbacks ||= []; end
67
- def before_init_callbacks; @before_init_callbacks ||= []; end
68
- def after_init_callbacks; @after_init_callbacks ||= []; end
69
- def before_run_callbacks; @before_run_callbacks ||= []; end
70
- def after_run_callbacks; @after_run_callbacks ||= []; end
71
-
72
- def before(&block); self.before_callbacks << block; end
73
- def after(&block); self.after_callbacks << block; end
74
- def before_init(&block); self.before_init_callbacks << block; end
75
- def after_init(&block); self.after_init_callbacks << block; end
76
- def before_run(&block); self.before_run_callbacks << block; end
77
- def after_run(&block); self.after_run_callbacks << block; end
78
-
79
- def prepend_before(&block); self.before_callbacks.unshift(block); end
80
- def prepend_after(&block); self.after_callbacks.unshift(block); end
81
- def prepend_before_init(&block); self.before_init_callbacks.unshift(block); end
82
- def prepend_after_init(&block); self.after_init_callbacks.unshift(block); end
83
- def prepend_before_run(&block); self.before_run_callbacks.unshift(block); end
84
- def prepend_after_run(&block); self.after_run_callbacks.unshift(block); end
25
+ def job; @qs_runner.message; end
26
+ def job_name; job.name; end
27
+ def job_created_at; job.created_at; end
85
28
 
86
29
  end
87
30
 
@@ -1,11 +1,11 @@
1
1
  require 'qs/test_runner'
2
2
 
3
- module Qs
3
+ module Qs::JobHandler
4
4
 
5
5
  module TestHelpers
6
6
 
7
7
  def test_runner(handler_class, args = nil)
8
- TestRunner.new(handler_class, args)
8
+ Qs::JobTestRunner.new(handler_class, args)
9
9
  end
10
10
 
11
11
  def test_handler(handler_class, args = nil)
@@ -0,0 +1,29 @@
1
+ module Qs
2
+
3
+ class Message
4
+
5
+ attr_reader :payload_type, :params
6
+
7
+ def initialize(payload_type, options = nil)
8
+ options ||= {}
9
+ @payload_type = payload_type.to_s
10
+ @params = options[:params] || {}
11
+ end
12
+
13
+ def route_id
14
+ @route_id ||= RouteId.new(self.payload_type, self.route_name)
15
+ end
16
+
17
+ def route_name
18
+ raise NotImplementedError
19
+ end
20
+
21
+ module RouteId
22
+ def self.new(payload_type, route_name)
23
+ "#{payload_type}|#{route_name}"
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,84 @@
1
+ module Qs
2
+
3
+ module MessageHandler
4
+
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ extend ClassMethods
8
+ include InstanceMethods
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+
14
+ def initialize(runner)
15
+ @qs_runner = runner
16
+ end
17
+
18
+ def init
19
+ run_callback 'before_init'
20
+ self.init!
21
+ run_callback 'after_init'
22
+ end
23
+
24
+ def init!
25
+ end
26
+
27
+ def run
28
+ run_callback 'before_run'
29
+ self.run!
30
+ run_callback 'after_run'
31
+ end
32
+
33
+ def run!
34
+ raise NotImplementedError
35
+ end
36
+
37
+ private
38
+
39
+ # Helpers
40
+
41
+ def params; @qs_runner.params; end
42
+ def logger; @qs_runner.logger; end
43
+
44
+ def run_callback(callback)
45
+ (self.class.send("#{callback}_callbacks") || []).each do |callback|
46
+ self.instance_eval(&callback)
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ module ClassMethods
53
+
54
+ def timeout(value = nil)
55
+ @timeout = value.to_f if value
56
+ @timeout
57
+ end
58
+
59
+ def before_callbacks; @before_callbacks ||= []; end
60
+ def after_callbacks; @after_callbacks ||= []; end
61
+ def before_init_callbacks; @before_init_callbacks ||= []; end
62
+ def after_init_callbacks; @after_init_callbacks ||= []; end
63
+ def before_run_callbacks; @before_run_callbacks ||= []; end
64
+ def after_run_callbacks; @after_run_callbacks ||= []; end
65
+
66
+ def before(&block); self.before_callbacks << block; end
67
+ def after(&block); self.after_callbacks << block; end
68
+ def before_init(&block); self.before_init_callbacks << block; end
69
+ def after_init(&block); self.after_init_callbacks << block; end
70
+ def before_run(&block); self.before_run_callbacks << block; end
71
+ def after_run(&block); self.after_run_callbacks << block; end
72
+
73
+ def prepend_before(&block); self.before_callbacks.unshift(block); end
74
+ def prepend_after(&block); self.after_callbacks.unshift(block); end
75
+ def prepend_before_init(&block); self.before_init_callbacks.unshift(block); end
76
+ def prepend_after_init(&block); self.after_init_callbacks.unshift(block); end
77
+ def prepend_before_run(&block); self.before_run_callbacks.unshift(block); end
78
+ def prepend_after_run(&block); self.after_run_callbacks.unshift(block); end
79
+
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,98 @@
1
+ require 'qs'
2
+ require 'qs/event'
3
+ require 'qs/job'
4
+
5
+ module Qs
6
+
7
+ module Payload
8
+
9
+ METHOD_NAMES = Hash.new{ |h, t| raise(InvalidError.new(t)) }.tap do |h|
10
+ h[Job::PAYLOAD_TYPE] = 'job'
11
+ h[Event::PAYLOAD_TYPE] = 'event'
12
+ end.freeze
13
+
14
+ def self.type_method_name(payload_type)
15
+ METHOD_NAMES[payload_type]
16
+ end
17
+
18
+ def self.deserialize(encoded_payload)
19
+ payload_hash = Qs.decode(encoded_payload)
20
+ self.send(self.type_method_name(payload_hash['type']), payload_hash)
21
+ end
22
+
23
+ def self.serialize(message)
24
+ Qs.encode(self.send("#{self.type_method_name(message.payload_type)}_hash", message))
25
+ end
26
+
27
+ def self.job(payload_hash)
28
+ Qs::Job.new(payload_hash['name'], {
29
+ :params => payload_hash['params'],
30
+ :created_at => Timestamp.to_time(payload_hash['created_at'])
31
+ })
32
+ end
33
+
34
+ def self.job_hash(job)
35
+ self.message_hash(job, {
36
+ 'name' => job.name.to_s,
37
+ 'created_at' => Timestamp.new(job.created_at)
38
+ })
39
+ end
40
+
41
+ def self.event(payload_hash)
42
+ Qs::Event.new(payload_hash['channel'], payload_hash['name'], {
43
+ :params => payload_hash['params'],
44
+ :publisher => payload_hash['publisher'],
45
+ :published_at => Timestamp.to_time(payload_hash['published_at'])
46
+ })
47
+ end
48
+
49
+ def self.event_hash(event)
50
+ self.message_hash(event, {
51
+ 'channel' => event.channel.to_s,
52
+ 'name' => event.name.to_s,
53
+ 'publisher' => event.publisher.to_s,
54
+ 'published_at' => Timestamp.new(event.published_at)
55
+ })
56
+ end
57
+
58
+ # private
59
+
60
+ def self.message_hash(message, hash)
61
+ hash.tap do |h|
62
+ h['type'] = message.payload_type.to_s
63
+ h['params'] = StringifyParams.new(message.params)
64
+ end
65
+ end
66
+
67
+ module StringifyParams
68
+ def self.new(object)
69
+ case(object)
70
+ when Hash
71
+ object.inject({}){ |h, (k, v)| h.merge(k.to_s => self.new(v)) }
72
+ when Array
73
+ object.map{ |item| self.new(item) }
74
+ else
75
+ object
76
+ end
77
+ end
78
+ end
79
+
80
+ module Timestamp
81
+ def self.to_time(integer)
82
+ Time.at(integer)
83
+ end
84
+
85
+ def self.new(time)
86
+ time.to_i
87
+ end
88
+ end
89
+
90
+ class InvalidError < ArgumentError
91
+ def initialize(payload_type)
92
+ super "unknown payload type #{payload_type.inspect}"
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ end
@@ -1,19 +1,20 @@
1
1
  require 'benchmark'
2
2
  require 'dat-worker-pool'
3
- require 'qs'
4
3
  require 'qs/error_handler'
4
+ require 'qs/event'
5
5
  require 'qs/job'
6
6
  require 'qs/logger'
7
+ require 'qs/payload'
7
8
 
8
9
  module Qs
9
10
 
10
11
  class PayloadHandler
11
12
 
12
- attr_reader :daemon_data, :redis_item, :logger
13
+ attr_reader :daemon_data, :queue_item, :logger
13
14
 
14
- def initialize(daemon_data, redis_item)
15
+ def initialize(daemon_data, queue_item)
15
16
  @daemon_data = daemon_data
16
- @redis_item = redis_item
17
+ @queue_item = queue_item
17
18
  @logger = Qs::Logger.new(
18
19
  @daemon_data.logger,
19
20
  @daemon_data.verbose_logging
@@ -22,49 +23,48 @@ module Qs
22
23
 
23
24
  def run
24
25
  log_received
25
- benchmark = Benchmark.measure{ run!(@daemon_data, @redis_item) }
26
- @redis_item.time_taken = RoundedTime.new(benchmark.real)
27
- log_complete(@redis_item)
28
- raise_if_debugging!(@redis_item.exception)
26
+ benchmark = Benchmark.measure{ run!(@daemon_data, @queue_item) }
27
+ @queue_item.time_taken = RoundedTime.new(benchmark.real)
28
+ log_complete(@queue_item)
29
+ raise_if_debugging!(@queue_item.exception)
29
30
  end
30
31
 
31
32
  private
32
33
 
33
- def run!(daemon_data, redis_item)
34
- redis_item.started = true
34
+ def run!(daemon_data, queue_item)
35
+ queue_item.started = true
35
36
 
36
- payload = Qs.deserialize(redis_item.serialized_payload)
37
- job = Qs::Job.parse(payload)
38
- log_job(job)
39
- redis_item.job = job
37
+ message = Qs::Payload.deserialize(queue_item.encoded_payload)
38
+ log_message(message)
39
+ queue_item.message = message
40
40
 
41
- route = daemon_data.route_for(job.name)
41
+ route = daemon_data.route_for(message.route_id)
42
42
  log_handler_class(route.handler_class)
43
- redis_item.handler_class = route.handler_class
43
+ queue_item.handler_class = route.handler_class
44
44
 
45
- route.run(job, daemon_data)
46
- redis_item.finished = true
45
+ route.run(message, daemon_data)
46
+ queue_item.finished = true
47
47
  rescue DatWorkerPool::ShutdownError => exception
48
- if redis_item.started
48
+ if queue_item.started
49
49
  error = ShutdownError.new(exception.message)
50
50
  error.set_backtrace(exception.backtrace)
51
- handle_exception(error, daemon_data, redis_item)
51
+ handle_exception(error, daemon_data, queue_item)
52
52
  end
53
53
  raise exception
54
54
  rescue StandardError => exception
55
- handle_exception(exception, daemon_data, redis_item)
55
+ handle_exception(exception, daemon_data, queue_item)
56
56
  end
57
57
 
58
- def handle_exception(exception, daemon_data, redis_item)
58
+ def handle_exception(exception, daemon_data, queue_item)
59
59
  error_handler = Qs::ErrorHandler.new(exception, {
60
- :daemon_data => daemon_data,
61
- :queue_redis_key => redis_item.queue_redis_key,
62
- :serialized_payload => redis_item.serialized_payload,
63
- :job => redis_item.job,
64
- :handler_class => redis_item.handler_class
60
+ :daemon_data => daemon_data,
61
+ :queue_redis_key => queue_item.queue_redis_key,
62
+ :encoded_payload => queue_item.encoded_payload,
63
+ :message => queue_item.message,
64
+ :handler_class => queue_item.handler_class
65
65
  }).tap(&:run)
66
- redis_item.exception = error_handler.exception
67
- log_exception(redis_item.exception)
66
+ queue_item.exception = error_handler.exception
67
+ log_exception(queue_item.exception)
68
68
  end
69
69
 
70
70
  def raise_if_debugging!(exception)
@@ -72,32 +72,30 @@ module Qs
72
72
  end
73
73
 
74
74
  def log_received
75
- log_verbose "===== Running job ====="
75
+ log_verbose "===== Received message ====="
76
+ end
77
+
78
+ def log_message(message)
79
+ self.send("log_#{Qs::Payload.type_method_name(message.payload_type)}", message)
80
+ log_verbose " Params: #{message.params.inspect}"
76
81
  end
77
82
 
78
83
  def log_job(job)
79
- log_verbose " Job: #{job.name.inspect}"
80
- log_verbose " Params: #{job.params.inspect}"
84
+ log_verbose " Job: #{job.route_name.inspect}"
85
+ end
86
+
87
+ def log_event(event)
88
+ log_verbose " Event: #{event.route_name.inspect}"
89
+ log_verbose " Publisher: #{event.publisher.inspect}"
81
90
  end
82
91
 
83
92
  def log_handler_class(handler_class)
84
- log_verbose " Handler: #{handler_class}"
93
+ log_verbose " Handler: #{handler_class}"
85
94
  end
86
95
 
87
- def log_complete(redis_item)
88
- log_verbose "===== Completed in #{redis_item.time_taken}ms ====="
89
- summary_line_args = {
90
- 'time' => redis_item.time_taken,
91
- 'handler' => redis_item.handler_class
92
- }
93
- if (job = redis_item.job)
94
- summary_line_args['job'] = job.name
95
- summary_line_args['params'] = job.params
96
- end
97
- if (exception = redis_item.exception)
98
- summary_line_args['error'] = "#{exception.inspect}"
99
- end
100
- log_summary SummaryLine.new(summary_line_args)
96
+ def log_complete(queue_item)
97
+ log_verbose "===== Completed in #{queue_item.time_taken}ms ====="
98
+ log_summary build_summary_line(queue_item)
101
99
  end
102
100
 
103
101
  def log_exception(exception)
@@ -114,18 +112,72 @@ module Qs
114
112
  self.logger.summary.send(level, "[Qs] #{message}")
115
113
  end
116
114
 
117
- module RoundedTime
118
- ROUND_PRECISION = 2
119
- ROUND_MODIFIER = 10 ** ROUND_PRECISION
120
- def self.new(time_in_seconds)
121
- (time_in_seconds * 1000 * ROUND_MODIFIER).to_i / ROUND_MODIFIER.to_f
115
+ def build_summary_line(queue_item)
116
+ summary_line_args = {
117
+ 'time' => queue_item.time_taken,
118
+ 'handler' => queue_item.handler_class
119
+ }
120
+ if (exception = queue_item.exception)
121
+ summary_line_args['error'] = "#{exception.class}: #{exception.message}"
122
122
  end
123
+ if (message = queue_item.message)
124
+ summary_line_args['params'] = message.params
125
+ self.send(
126
+ "#{Qs::Payload.type_method_name(message.payload_type)}_summary_line",
127
+ message,
128
+ summary_line_args
129
+ )
130
+ else
131
+ UnknownSummaryLine.new(summary_line_args)
132
+ end
133
+ end
134
+
135
+ def job_summary_line(job, summary_line_args)
136
+ JobSummaryLine.new(job, summary_line_args)
137
+ end
138
+
139
+ def event_summary_line(event, summary_line_args)
140
+ EventSummaryLine.new(event, summary_line_args)
123
141
  end
124
142
 
125
143
  module SummaryLine
144
+ def self.new(keys, line_attrs)
145
+ keys.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
146
+ end
147
+ end
148
+
149
+ module UnknownSummaryLine
150
+ ORDERED_KEYS = %w(time handler error).freeze
151
+
126
152
  def self.new(line_attrs)
127
- attr_keys = %w{time handler job params error}
128
- attr_keys.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
153
+ SummaryLine.new(ORDERED_KEYS, line_attrs)
154
+ end
155
+ end
156
+
157
+ module JobSummaryLine
158
+ ORDERED_KEYS = %w(time handler job params error).freeze
159
+
160
+ def self.new(job, line_attrs)
161
+ SummaryLine.new(ORDERED_KEYS, line_attrs.merge('job' => job.route_name))
162
+ end
163
+ end
164
+
165
+ module EventSummaryLine
166
+ ORDERED_KEYS = %w(time handler event publisher params error).freeze
167
+
168
+ def self.new(event, line_attrs)
169
+ SummaryLine.new(ORDERED_KEYS, line_attrs.merge({
170
+ 'event' => event.route_name,
171
+ 'publisher' => event.publisher
172
+ }))
173
+ end
174
+ end
175
+
176
+ module RoundedTime
177
+ ROUND_PRECISION = 2
178
+ ROUND_MODIFIER = 10 ** ROUND_PRECISION
179
+ def self.new(time_in_seconds)
180
+ (time_in_seconds * 1000 * ROUND_MODIFIER).to_i / ROUND_MODIFIER.to_f
129
181
  end
130
182
  end
131
183