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