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,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
- @enqueued_jobs = []
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
- @routes.push(Qs::Route.new(name, handler_name))
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
@@ -4,10 +4,10 @@ module Qs
4
4
 
5
5
  class Route
6
6
 
7
- attr_reader :name, :handler_class_name, :handler_class
7
+ attr_reader :id, :handler_class_name, :handler_class
8
8
 
9
- def initialize(name, handler_class_name)
10
- @name = name.to_s
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(job, daemon_data)
19
+ def run(message, daemon_data)
20
20
  QsRunner.new(self.handler_class, {
21
- :job => job,
22
- :params => job.params,
23
- :logger => daemon_data.logger
21
+ :message => message,
22
+ :params => message.params,
23
+ :logger => daemon_data.logger
24
24
  }).run
25
25
  end
26
26
 
@@ -5,16 +5,17 @@ module Qs
5
5
  class Runner
6
6
 
7
7
  attr_reader :handler_class, :handler
8
- attr_reader :job, :params, :logger
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
- @job = a[:job]
16
- @params = a[:params] || {}
17
- @logger = a[:logger] || Qs::NullLogger.new
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
@@ -1,24 +1,19 @@
1
1
  require 'qs'
2
- require 'qs/job'
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
- :job => args.delete(:job),
20
- :params => normalize_params(args.delete(:params) || {}),
21
- :logger => args.delete(: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 serialize/deserialize to ensure params are valid and are
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 = Job::StringifyParams.new(params)
38
- Qs.deserialize(Qs.serialize(params))
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
@@ -1,3 +1,3 @@
1
1
  module Qs
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
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.2"])
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
 
@@ -11,4 +11,4 @@ ROOT_PATH = Pathname.new(File.expand_path('../..', __FILE__))
11
11
 
12
12
  require 'test/support/factory'
13
13
 
14
- require 'json' # so the default serializer/deserializer procs will work
14
+ require 'json' # so the default encoder/decoder procs will work
@@ -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 'app_main'
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
- job_name = context.job.name if context.job
29
- case(job_name)
30
- when 'error', 'timeout'
31
- message = "#{exception.class}: #{exception.message}"
32
- Qs.redis.with{ |c| c.set('last_error', message) }
33
- when 'slow'
34
- Qs.redis.with{ |c| c.set('last_error', exception.class.to_s) }
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
 
@@ -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