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.
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