modern_times 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -10,8 +10,9 @@ begin
10
10
  gemspec.authors = ['Brad Pardee', 'Reid Morrison']
11
11
  gemspec.email = ['bradpardee@gmail.com', 'rubywmq@gmail.com']
12
12
  gemspec.homepage = 'http://github.com/ClarityServices/modern_times'
13
- gemspec.add_dependency 'jruby-hornetq', ['>= 0.3.2']
14
- gemspec.add_dependency 'ClarityServices-jmxjr'
13
+ gemspec.add_dependency 'jruby-hornetq', ['>= 0.3.3']
14
+ gemspec.add_dependency 'jmx', ['>= 0.6']
15
+ gemspec.add_dependency 'json'
15
16
  end
16
17
  rescue LoadError
17
18
  puts 'Jeweler not available. Install it with: gem install jeweler'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -0,0 +1,2 @@
1
+ data
2
+ *.state
@@ -0,0 +1,11 @@
1
+ # Step 1
2
+ # Start a hornetq server (gem install jruby-hornetq if necessary)
3
+ hornetq_server hornetq.yml server
4
+
5
+ # Step 2
6
+ # Start up the manager
7
+ jruby manager.rb
8
+
9
+ # Step 3
10
+ # Request 'my string' get reversed
11
+ jruby request.rb 'my string'
@@ -0,0 +1,14 @@
1
+ server:
2
+ :uri: hornetq://localhost
3
+ :data_directory: ./data
4
+ :persistence_enabled: true
5
+ :security_enabled: true
6
+ :cluster_user: myuser
7
+ :cluster_password: mypassword
8
+
9
+ client:
10
+ :connection:
11
+ :uri: hornetq://localhost
12
+ :session:
13
+ :username: myuser
14
+ :password: mypassword
@@ -0,0 +1,15 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
3
+
4
+ require 'rubygems'
5
+ require 'modern_times'
6
+ require 'yaml'
7
+ require 'reverse_echo_worker'
8
+
9
+ config = YAML.load_file('hornetq.yml')
10
+ ModernTimes::HornetQ::Client.init(config['client'])
11
+
12
+ manager = ModernTimes::Manager.new
13
+ manager.stop_on_signal
14
+ manager.add(ReverseEchoWorker, 1, {})
15
+ manager.join
@@ -0,0 +1,45 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
3
+
4
+ require 'rubygems'
5
+ require 'modern_times'
6
+ require 'yaml'
7
+ require 'reverse_echo_worker'
8
+
9
+ if ARGV.size < 1
10
+ $stderr.puts "Usage: {$0} <reverse-echo-string> [<timeout>] [<sleep-time>] [<simultaneous-count>]"
11
+ exit 1
12
+ end
13
+
14
+ $echo_string = ARGV[0]
15
+ $timeout = (ARGV[1] || 4).to_f
16
+ $sleep_time = (ARGV[2] || 2).to_i
17
+ $sim_count = (ARGV[3] || 1).to_i
18
+
19
+ config = YAML.load_file('hornetq.yml')
20
+ ModernTimes::HornetQ::Client.init(config['client'])
21
+ $requestor = ModernTimes::HornetQRequestor::Requestor.new(ReverseEchoWorker.address_name, :marshal => :string)
22
+
23
+ def make_request(ident='')
24
+ puts "#{ident}Making request at #{Time.now.to_f}"
25
+ handle = $requestor.request("#{ident}#{$echo_string}", $timeout)
26
+ # Here's where we'd go off and do other work
27
+ sleep $sleep_time
28
+ puts "#{ident}Finished sleeping at #{Time.now.to_f}"
29
+ response = handle.read_response
30
+ puts "#{ident}Received at #{Time.now.to_f}: #{response}"
31
+ rescue Exception => e
32
+ puts "#{ident}Exception: #{e.message}"
33
+ end
34
+
35
+ if $sim_count == 1
36
+ make_request
37
+ else
38
+ threads = []
39
+ (1..$sim_count).each do |i|
40
+ threads << Thread.new(i) do |i|
41
+ make_request("Thread ##{i}: ")
42
+ end
43
+ end
44
+ threads.each {|t| t.join}
45
+ end
@@ -0,0 +1,12 @@
1
+ class ReverseEchoWorker < ModernTimes::HornetQRequestor::Worker
2
+ include ModernTimes::HornetQ::MarshalStrategy::String
3
+
4
+ def request(obj)
5
+ puts "#{self}: Received #{obj} at #{Time.now}"
6
+ if obj =~ /^sleep (.*)/
7
+ sleep $1.to_f
8
+ puts "#{self}: Finished sleeping at #{Time.now}"
9
+ end
10
+ obj.reverse
11
+ end
12
+ end
@@ -1,15 +1,20 @@
1
1
  module ModernTimes
2
2
  module Base
3
3
  class Supervisor
4
- attr_reader :manager, :worker_klass
4
+ attr_reader :manager, :worker_klass, :name, :worker_options
5
5
 
6
- def initialize(manager, worker_klass, options)
7
- @stopped = false
8
- @manager = manager
9
- @worker_klass = worker_klass
10
- @workers = []
11
- @worker_mutex = Mutex.new
12
- @failure_mutex = Mutex.new
6
+ # Create new supervisor to manage a number of similar workers
7
+ # supervisor_options are those options defined on the Worker's Supervisor line
8
+ # worker_options are options passed in when creating a new instance
9
+ def initialize(manager, worker_klass, supervisor_options, worker_options)
10
+ @stopped = false
11
+ @manager = manager
12
+ @worker_klass = worker_klass
13
+ @name = worker_options.delete(:name) || worker_klass.default_name
14
+ @worker_options = worker_options
15
+ @workers = []
16
+ @worker_mutex = Mutex.new
17
+ @failure_mutex = Mutex.new
13
18
  end
14
19
 
15
20
  def worker_count
@@ -23,7 +28,7 @@ module ModernTimes
23
28
  curr_count = @workers.size
24
29
  if curr_count < count
25
30
  (curr_count...count).each do |index|
26
- worker = @worker_klass.new
31
+ worker = @worker_klass.new(@worker_options)
27
32
  worker.supervisor = self
28
33
  worker.index = index
29
34
  if index == 0
@@ -1,7 +1,9 @@
1
+ require 'json'
2
+
1
3
  module ModernTimes
2
4
  module Base
3
5
  class Worker
4
- attr_accessor :name, :index, :supervisor, :thread
6
+ attr_accessor :index, :supervisor, :thread
5
7
 
6
8
  def self.supervisor(klass, options={})
7
9
  # self.class_eval do
@@ -12,8 +14,8 @@ module ModernTimes
12
14
  # end
13
15
  # TODO: This is nasty but I'm not sure how to create a dynamic class method within a scope
14
16
  eval <<-EOS
15
- def self.create_supervisor(manager)
16
- #{klass.name}.new(manager, self, #{options.inspect})
17
+ def self.create_supervisor(manager, worker_options)
18
+ #{klass.name}.new(manager, self, #{options.to_json}, worker_options)
17
19
  end
18
20
  EOS
19
21
  end
@@ -22,11 +24,6 @@ module ModernTimes
22
24
  supervisor Supervisor
23
25
 
24
26
  def initialize(opts={})
25
- @name = opts[:name] || self.class.default_name
26
- end
27
-
28
- def name
29
- @name
30
27
  end
31
28
 
32
29
  # One time initialization prior to first thread
@@ -4,6 +4,7 @@ require 'hornetq'
4
4
  module ModernTimes
5
5
  module HornetQ
6
6
  class Publisher
7
+ attr_reader :address
7
8
 
8
9
  # TODO: Possible performance enhancements on producer
9
10
  # setDisableMessageID()
@@ -31,22 +32,14 @@ module ModernTimes
31
32
  end
32
33
 
33
34
  # Publish the given object to the address.
34
- def publish(object)
35
+ def publish(object, user_id=nil, props={})
35
36
  Client.session_pool.producer(@address) do |session, producer|
36
37
  message = marshal(session, object, @durable)
37
- first_time = true
38
- begin
39
- producer.send(message)
40
- rescue Java::org.hornetq.api.core.HornetQException => e
41
- ModernTimes.logger.warn "Received producer exception: #{e.message} with code=#{e.cause.code}"
42
- if first_time && e.cause.code == Java::org.hornetq.api.core.HornetQException::UNBLOCKED
43
- ModernTimes.logger.info "Retrying the send"
44
- first_time = false
45
- retry
46
- else
47
- raise
48
- end
38
+ message.user_id = user_id if user_id
39
+ props.each do |key, value|
40
+ message.putStringProperty(key, value)
49
41
  end
42
+ producer.send_with_retry(message)
50
43
  end
51
44
  end
52
45
 
@@ -6,13 +6,16 @@ module ModernTimes
6
6
 
7
7
  attr_reader :message_count
8
8
 
9
- def initialize(manager, worker_name, opts={})
10
- super(manager, worker_name, opts)
9
+ def initialize(manager, worker_name, supervisor_options, worker_options)
10
+ super
11
11
  @message_count = 0
12
+ @message_count_mutex = Mutex.new
12
13
  end
13
14
 
14
15
  def incr_message_count
15
- @message_count += 1
16
+ @message_count_mutex.synchronize do
17
+ @message_count += 1
18
+ end
16
19
  end
17
20
  end
18
21
  end
@@ -1,12 +1,12 @@
1
1
  module ModernTimes
2
2
  module HornetQ
3
3
  class SupervisorMBean < ModernTimes::Base::SupervisorMBean
4
+ r_attribute :message_count, :int, 'Total message count', :message_count
4
5
 
5
- operation 'Total message count'
6
- returns :int
7
6
  def message_count
8
7
  supervisor.message_count
9
8
  end
9
+
10
10
  end
11
11
  end
12
12
  end
@@ -11,7 +11,8 @@ module ModernTimes
11
11
 
12
12
  attr_reader :session, :message_count
13
13
 
14
- def initialize
14
+ def initialize(opts={})
15
+ super
15
16
  @status = 'initialized'
16
17
  @message_count = 0
17
18
  end
@@ -64,9 +65,7 @@ module ModernTimes
64
65
 
65
66
  # Start the event loop for handling messages off the queue
66
67
  def start
67
- @session = Client.create_consumer_session
68
- @consumer = @session.create_consumer(queue_name)
69
- @session.start
68
+ session_init
70
69
  ModernTimes.logger.debug "#{self}: Starting receive loop"
71
70
  @status = nil
72
71
  while msg = @consumer.receive
@@ -102,6 +101,13 @@ module ModernTimes
102
101
  protected
103
102
  #########
104
103
 
104
+ # Create session information and allow extenders to initialize anything necessary prior to the event loop
105
+ def session_init
106
+ @session = Client.create_consumer_session
107
+ @consumer = @session.create_consumer(queue_name)
108
+ @session.start
109
+ end
110
+
105
111
  # Create a queue, assigned to the specified address
106
112
  # Every time a message arrives, the perform instance method
107
113
  # will be called. The parameter to the method is the Ruby
@@ -0,0 +1,49 @@
1
+ require 'timeout'
2
+
3
+ module ModernTimes
4
+ module HornetQRequestor
5
+ class RequestHandle
6
+ def initialize(requestor, message_id, start, timeout)
7
+ @requestor = requestor
8
+ @reply_queue = requestor.reply_queue
9
+ @message_id = message_id
10
+ @start = start
11
+ @timeout = timeout
12
+ end
13
+
14
+ def read_response
15
+ message = nil
16
+ leftover_timeout = ((@start + @timeout - Time.now) * 1000).to_i
17
+ ModernTimes::HornetQ::Client.session_pool.session do |s|
18
+ consumer = nil
19
+ begin
20
+ consumer = s.create_consumer(@reply_queue, "#{MESSAGE_ID}='#{@message_id}'")
21
+ if leftover_timeout > 0
22
+ message = consumer.receive(leftover_timeout)
23
+ else
24
+ message = consumer.receive(1)
25
+ end
26
+ puts "funked at #{Time.now.to_f}" unless message
27
+ consumer.receive_immediate unless message
28
+ ensure
29
+ consumer.close if consumer
30
+ end
31
+ end
32
+ raise Timeout::Error, "Timeout waiting for message #{@message_id} on queue #{@reply_queue}" unless message
33
+ return @requestor.unmarshal(message)
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ #handle = Intercept::Client.async_cair(bank_account_array, tracking_number, timeout)
40
+ #... do other stuff ...
41
+ #begin
42
+ # # Following call will block until the queue receives the reply or what's left of the timeout expires
43
+ # intercept_statuses = handle.read_response
44
+ # ... process intercept statuses ...
45
+ #rescue Timeout::Error => e
46
+ # Rails.logger.warn "We didn't receive a reply back on the queue in time"
47
+ #rescue Intercept::Error => e
48
+ # Rails.logger.warn "Error during intercept call: #{e.message}"
49
+ #end
@@ -0,0 +1,48 @@
1
+ module ModernTimes
2
+ module HornetQRequestor
3
+ class Requestor < ModernTimes::HornetQ::Publisher
4
+ attr_reader :reply_queue
5
+
6
+ def initialize(address, options={})
7
+ super
8
+ @reply_queue = "#{address}.#{Java::java.util::UUID.randomUUID.toString}"
9
+ @reply_queue_simple = Java::org.hornetq.api.core.SimpleString.new(@reply_queue)
10
+ ModernTimes::HornetQ::Client.session_pool.session do |session|
11
+ session.create_temporary_queue(@reply_queue, @reply_queue)
12
+ end
13
+ end
14
+
15
+ def request(object, timeout)
16
+ start = Time.now
17
+ message_id = Java::org.hornetq.utils.UUIDGenerator.instance.generateUUID.toString
18
+ publish(object,
19
+ nil,
20
+ MESSAGE_ID => message_id,
21
+ Java::OrgHornetqCoreClientImpl::ClientMessageImpl::REPLYTO_HEADER_NAME => @reply_queue_simple)
22
+ #HornetQMessage.CORRELATIONID_HEADER_NAME
23
+ #REPLY_QUEUE => @reply_queue,
24
+ #MESSAGE_ID => message_id)
25
+ return RequestHandle.new(self, message_id, start, timeout)
26
+ end
27
+
28
+ # For non-configured Rails projects, The above request method will be overridden to
29
+ # call this request method instead which calls all the HornetQ workers that
30
+ # operate on the given address.
31
+ def dummy_request(object)
32
+ @@worker_instances.each do |worker|
33
+ if worker.kind_of?(Worker) && worker.address_name == address
34
+ ModernTimes.logger.debug "Dummy requesting #{object} to #{worker}"
35
+ return new OpenStruct(:read_response => worker.request(object))
36
+ end
37
+ end
38
+ raise "No worker to handle #{address} request of #{object}"
39
+ end
40
+
41
+ def self.setup_dummy_requesting(workers)
42
+ @@worker_instances = workers.map {|worker| worker.new}
43
+ alias_method :real_request, :request
44
+ alias_method :request, :dummy_request
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ module ModernTimes
2
+ module HornetQRequestor
3
+
4
+ # Base Worker Class for any class that will be processing messages from queues
5
+ class Worker < ModernTimes::HornetQ::Worker
6
+ # Make HornetQRequestor::Supervisor our supervisor
7
+ #supervisor Supervisor
8
+
9
+ def on_message(message)
10
+ @reply_queue = message.get_string_property(Java::OrgHornetqCoreClientImpl::ClientMessageImpl::REPLYTO_HEADER_NAME)
11
+ @message_id = message.get_string_property(MESSAGE_ID)
12
+ super
13
+ end
14
+
15
+ def perform(object)
16
+ response = request(object)
17
+ session.producer(@reply_queue) do |producer|
18
+ reply_message = marshal(session, response, false)
19
+ reply_message.put_string_property(MESSAGE_ID, @message_id)
20
+ producer.send_with_retry(reply_message)
21
+ end
22
+ end
23
+
24
+ def request(object)
25
+ raise "#{self}: Need to override request method in #{self.class.name} in order to act on #{object}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ require 'modern_times/hornetq_requestor/request_handle'
2
+ require 'modern_times/hornetq_requestor/requestor'
3
+ require 'modern_times/hornetq_requestor/worker'
4
+
5
+ module ModernTimes
6
+ module HornetQRequestor
7
+ MESSAGE_ID = 'ModernTimesMessageId'
8
+ end
9
+ end
@@ -3,13 +3,14 @@ module ModernTimes
3
3
  attr_accessor :allowed_workers
4
4
 
5
5
  def initialize(config={})
6
+ @stopped = false
6
7
  @config = config
7
8
  @domain = config[:domain] || 'ModernTimes'
8
9
  @supervisors = []
9
10
  @jmx_server = JMX::MBeanServer.new
10
11
  bean = ManagerMBean.new("#{@domain}.Manager", "Manager", self)
11
12
  @jmx_server.register_mbean(bean, "#{@domain}:type=Manager")
12
- persist_file = config[:persist_file]
13
+ self.persist_file = config[:persist_file]
13
14
  end
14
15
 
15
16
  def add(worker_klass, num_workers, worker_options)
@@ -24,11 +25,11 @@ module ModernTimes
24
25
  if @allowed_workers && !@allowed_workers.include?(worker_klass)
25
26
  raise ModernTimes::Exception.new("Error: #{worker_klass.name} is not an allowed worker")
26
27
  end
27
- supervisor = worker_klass.create_supervisor(self)
28
+ supervisor = worker_klass.create_supervisor(self, worker_options)
28
29
  mbean = supervisor.create_mbean(@domain)
29
30
  @supervisors << supervisor
30
31
  supervisor.worker_count = num_workers
31
- @jmx_server.register_mbean(mbean, "#{@domain}:worker=#{worker_klass.name},type=Worker")
32
+ @jmx_server.register_mbean(mbean, "#{@domain}:worker=#{supervisor.name},type=Worker")
32
33
  ModernTimes.logger.info "Started #{worker_klass.name} with #{num_workers} workers"
33
34
  rescue Exception => e
34
35
  ModernTimes.logger.error "Exception trying to add #{worker_klass.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
@@ -69,8 +70,12 @@ module ModernTimes
69
70
  @persist_file = file
70
71
  if File.exist?(file)
71
72
  hash = YAML.load_file(file)
72
- hash.each do |worker_klass, count|
73
- add(worker_klass, count)
73
+ hash.each do |worker_name, worker_hash|
74
+ klass = worker_hash[:klass]
75
+ count = worker_hash[:count]
76
+ options = worker_hash[:options]
77
+ options[:name] = worker_name
78
+ add(klass, count, options)
74
79
  end
75
80
  end
76
81
  end
@@ -79,9 +84,10 @@ module ModernTimes
79
84
  return unless @persist_file
80
85
  hash = {}
81
86
  @supervisors.each do |supervisor|
82
- hash[supervisor.worker_name] = {
83
- :worker_count => supervisor.worker_count,
84
- :options => supervisor.worker_options
87
+ hash[supervisor.name] = {
88
+ :klass => supervisor.worker_klass.name,
89
+ :count => supervisor.worker_count,
90
+ :options => supervisor.worker_options
85
91
  }
86
92
  end
87
93
  File.open(@persist_file, 'w') do |out|
@@ -3,18 +3,17 @@ require 'jmx'
3
3
  module ModernTimes
4
4
  class ManagerMBean < RubyDynamicMBean
5
5
  attr_reader :manager
6
- rw_attribute :foobar, :int, "Number of workers"
6
+ r_attribute :allowed_workers, :list, 'Allowed workers'
7
7
 
8
8
  def initialize(name, description, manager)
9
9
  super(name, description)
10
10
  @manager = manager
11
11
  end
12
12
 
13
- operation 'Allowed workers'
14
- returns :list
15
13
  def allowed_workers
16
14
  all = manager.allowed_workers || ['No Restrictions']
17
- all.map {|worker_klass| worker_klass.name }
15
+ all = all.map {|worker_klass| worker_klass.name }
16
+ java.util.ArrayList.new(all)
18
17
  end
19
18
 
20
19
  operation 'Start worker'
@@ -23,7 +22,7 @@ module ModernTimes
23
22
  returns :string
24
23
  def start_worker(worker, count)
25
24
  ModernTimes.logger.debug "Attempting to start #{worker} with count=#{count}"
26
- manager.add(worker, count)
25
+ manager.add(worker, count, {})
27
26
  return 'Successfuly started'
28
27
  rescue Exception => e
29
28
  ModernTimes.logger.error "Exception starting worker #{worker}: {e.message}\n\t#{e.backtrace.join("\n\t")}"
data/lib/modern_times.rb CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
2
2
  require 'modern_times/exception'
3
3
  require 'modern_times/base'
4
4
  require 'modern_times/hornetq'
5
+ require 'modern_times/hornetq_requestor'
5
6
  require 'modern_times/manager_mbean'
6
7
  require 'modern_times/manager'
7
8
  require 'modern_times/loggable'
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: modern_times
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.1
5
+ version: 0.1.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Brad Pardee
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2011-03-14 00:00:00 -04:00
14
+ date: 2011-03-24 00:00:00 -04:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
@@ -22,20 +22,31 @@ dependencies:
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 0.3.2
25
+ version: 0.3.3
26
26
  type: :runtime
27
27
  version_requirements: *id001
28
28
  - !ruby/object:Gem::Dependency
29
- name: ClarityServices-jmxjr
29
+ name: jmx
30
30
  prerelease: false
31
31
  requirement: &id002 !ruby/object:Gem::Requirement
32
32
  none: false
33
33
  requirements:
34
34
  - - ">="
35
35
  - !ruby/object:Gem::Version
36
- version: "0"
36
+ version: "0.6"
37
37
  type: :runtime
38
38
  version_requirements: *id002
39
+ - !ruby/object:Gem::Dependency
40
+ name: json
41
+ prerelease: false
42
+ requirement: &id003 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ type: :runtime
49
+ version_requirements: *id003
39
50
  description: Generic asynchronous task library
40
51
  email:
41
52
  - bradpardee@gmail.com
@@ -52,6 +63,12 @@ files:
52
63
  - README.rdoc
53
64
  - Rakefile
54
65
  - VERSION
66
+ - examples/requestor/.gitignore
67
+ - examples/requestor/README
68
+ - examples/requestor/hornetq.yml
69
+ - examples/requestor/manager.rb
70
+ - examples/requestor/request.rb
71
+ - examples/requestor/reverse_echo_worker.rb
55
72
  - examples/simple/.gitignore
56
73
  - examples/simple/README
57
74
  - examples/simple/bar_worker.rb
@@ -75,6 +92,10 @@ files:
75
92
  - lib/modern_times/hornetq/supervisor.rb
76
93
  - lib/modern_times/hornetq/supervisor_mbean.rb
77
94
  - lib/modern_times/hornetq/worker.rb
95
+ - lib/modern_times/hornetq_requestor.rb
96
+ - lib/modern_times/hornetq_requestor/request_handle.rb
97
+ - lib/modern_times/hornetq_requestor/requestor.rb
98
+ - lib/modern_times/hornetq_requestor/worker.rb
78
99
  - lib/modern_times/loggable.rb
79
100
  - lib/modern_times/manager.rb
80
101
  - lib/modern_times/manager_mbean.rb
@@ -113,6 +134,9 @@ signing_key:
113
134
  specification_version: 3
114
135
  summary: Asynchronous task library
115
136
  test_files:
137
+ - examples/requestor/manager.rb
138
+ - examples/requestor/request.rb
139
+ - examples/requestor/reverse_echo_worker.rb
116
140
  - examples/simple/bar_worker.rb
117
141
  - examples/simple/baz_worker.rb
118
142
  - examples/simple/manager.rb