activemessaging 0.9.0 → 0.10.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.
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # Active Messaging
2
+
3
+ ActiveMessaging is an attempt to bring the simplicity and elegance of rails development to the world of messaging. Messaging, (or event-driven architecture) is widely used for enterprise integration, with frameworks such as Java's JMS, and products such as ActiveMQ, Tibco, IBM MQSeries, etc.
4
+
5
+ ActiveMessaging is a generic framework to ease using messaging, but is not tied to any particular messaging system - in fact, it now has support for Stomp, AMQP, beanstalk, Amazon Simple Queue Service (SQS), JMS (using StompConnect or direct on JRuby), WebSphere MQ, the all-Ruby ReliableMessaging, a mock 'test' adapter, and a 'synch' adapter for use in development that processes calls synchronously (of course) and so requires no broker or additional processes to be running.
6
+
7
+ Here's a sample of a processor class that handles incoming messages:
8
+
9
+ class HelloWorldProcessor < ActiveMessaging::Processor
10
+ subscribes_to :hello_world
11
+ def on_message(message)
12
+ puts "received: " + message
13
+ end
14
+ end
15
+
16
+
17
+ # Support
18
+
19
+ Best bet is the google groups mailing list:
20
+
21
+ http://groups.google.com/group/activemessaging-discuss
data/Rakefile CHANGED
@@ -33,6 +33,7 @@ begin
33
33
 
34
34
  # added
35
35
  gemspec.add_dependency('activesupport', '>= 1.0.0')
36
+ gemspec.add_dependency('celluloid')
36
37
 
37
38
  end
38
39
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.0
1
+ 0.10.0
@@ -4,19 +4,19 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{activemessaging}
8
- s.version = "0.9.0"
7
+ s.name = "activemessaging"
8
+ s.version = "0.10.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jon Tirsen", "Andrew Kuklewicz", "Olle Jonsson", "Sylvain Perez", "Cliff Moon", "Uwe Kubosch"]
12
- s.date = %q{2011-12-05}
13
- s.description = %q{ActiveMessaging is an attempt to bring the simplicity and elegance of rails development to the world of messaging. Messaging, (or event-driven architecture) is widely used for enterprise integration, with frameworks such as Java's JMS, and products such as ActiveMQ, Tibco, IBM MQSeries, etc. Now supporting Rails 3 as of version 0.8.0.}
14
- s.email = %q{activemessaging-discuss@googlegroups.com}
12
+ s.date = "2012-07-13"
13
+ s.description = "ActiveMessaging is an attempt to bring the simplicity and elegance of rails development to the world of messaging. Messaging, (or event-driven architecture) is widely used for enterprise integration, with frameworks such as Java's JMS, and products such as ActiveMQ, Tibco, IBM MQSeries, etc. Now supporting Rails 3 as of version 0.8.0."
14
+ s.email = "activemessaging-discuss@googlegroups.com"
15
15
  s.extra_rdoc_files = [
16
- "README"
16
+ "README.md"
17
17
  ]
18
18
  s.files = [
19
- "README",
19
+ "README.md",
20
20
  "Rakefile",
21
21
  "VERSION",
22
22
  "activemessaging.gemspec",
@@ -66,6 +66,7 @@ Gem::Specification.new do |s|
66
66
  "lib/activemessaging/processor.rb",
67
67
  "lib/activemessaging/railtie.rb",
68
68
  "lib/activemessaging/test_helper.rb",
69
+ "lib/activemessaging/threaded_poller.rb",
69
70
  "lib/activemessaging/trace_filter.rb",
70
71
  "lib/generators/active_messaging/install/USAGE",
71
72
  "lib/generators/active_messaging/install/install_generator.rb",
@@ -73,6 +74,7 @@ Gem::Specification.new do |s|
73
74
  "lib/generators/active_messaging/install/templates/broker.yml",
74
75
  "lib/generators/active_messaging/install/templates/poller",
75
76
  "lib/generators/active_messaging/install/templates/poller.rb",
77
+ "lib/generators/active_messaging/install/templates/threaded_poller",
76
78
  "lib/generators/active_messaging/processor/USAGE",
77
79
  "lib/generators/active_messaging/processor/processor_generator.rb",
78
80
  "lib/generators/active_messaging/processor/templates/messaging.rb",
@@ -93,33 +95,24 @@ Gem::Specification.new do |s|
93
95
  "test/test_helper.rb",
94
96
  "test/tracer_test.rb"
95
97
  ]
96
- s.homepage = %q{http://github.com/kookster/activemessaging}
98
+ s.homepage = "http://github.com/kookster/activemessaging"
97
99
  s.require_paths = ["lib"]
98
- s.rubygems_version = %q{1.4.2}
99
- s.summary = %q{Official activemessaging gem, now hosted on github.com/kookster. (kookster prefix temporary)}
100
- s.test_files = [
101
- "test/all_tests.rb",
102
- "test/asqs_test.rb",
103
- "test/config_test.rb",
104
- "test/filter_test.rb",
105
- "test/gateway_test.rb",
106
- "test/jms_test.rb",
107
- "test/reliable_msg_test.rb",
108
- "test/stomp_test.rb",
109
- "test/test_helper.rb",
110
- "test/tracer_test.rb"
111
- ]
100
+ s.rubygems_version = "1.8.22"
101
+ s.summary = "Official activemessaging gem, now hosted on github.com/kookster. (kookster prefix temporary)"
112
102
 
113
103
  if s.respond_to? :specification_version then
114
104
  s.specification_version = 3
115
105
 
116
106
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
117
107
  s.add_runtime_dependency(%q<activesupport>, [">= 1.0.0"])
108
+ s.add_runtime_dependency(%q<celluloid>, [">= 0"])
118
109
  else
119
110
  s.add_dependency(%q<activesupport>, [">= 1.0.0"])
111
+ s.add_dependency(%q<celluloid>, [">= 0"])
120
112
  end
121
113
  else
122
114
  s.add_dependency(%q<activesupport>, [">= 1.0.0"])
115
+ s.add_dependency(%q<celluloid>, [">= 0"])
123
116
  end
124
117
  end
125
118
 
@@ -1,4 +1,5 @@
1
- require 'active_support'
1
+ require 'logger'
2
+ require 'active_support/all'
2
3
  require 'ostruct'
3
4
 
4
5
  if defined?(Rails::Railtie)
@@ -45,6 +46,7 @@ module ActiveMessaging
45
46
  def self.load_extensions
46
47
  require 'logger'
47
48
  require 'activemessaging/gateway'
49
+ require 'activemessaging/threaded_poller'
48
50
  require 'activemessaging/adapter'
49
51
  require 'activemessaging/message_sender'
50
52
  require 'activemessaging/processor'
@@ -66,8 +68,6 @@ module ActiveMessaging
66
68
  path = File.expand_path("#{app_root}/config/messaging.rb")
67
69
  begin
68
70
  load path
69
- rescue MissingSourceFile
70
- logger.error "ActiveMessaging: no '#{path}' file to load"
71
71
  rescue
72
72
  raise $!, " ActiveMessaging: problems trying to load '#{path}': \n\t#{$!.message}"
73
73
  end
@@ -126,7 +126,3 @@ module ActiveMessaging
126
126
  end
127
127
 
128
128
  end
129
-
130
- if !defined?(Rails::Railtie)
131
- ActiveMessaging.load_activemessaging
132
- end
@@ -79,7 +79,7 @@ module ActiveMessaging
79
79
  client.server.send_frame(::Carrot::AMQP::Protocol::Basic::Reject.new(:delivery_tag => message.headers[:delivery_tag]))
80
80
  end
81
81
 
82
- def receive
82
+ def receive(options={})
83
83
  while true
84
84
  message = queue.pop(:ack => true)
85
85
  unless message.nil?
@@ -17,10 +17,10 @@ module ActiveMessaging
17
17
  class Connection < ActiveMessaging::Adapters::BaseConnection
18
18
  register :asqs
19
19
 
20
- QUEUE_NAME_LENGTH = 1..80
21
- MESSAGE_SIZE = 1..(8 * 1024)
22
- VISIBILITY_TIMEOUT = 0..(24 * 60 * 60)
23
- NUMBER_OF_MESSAGES = 1..255
20
+ QUEUE_NAME_LENGTH = 1..80
21
+ MESSAGE_SIZE = 1..(8 * 1024)
22
+ VISIBILITY_TIMEOUT = 0..(24 * 60 * 60)
23
+ NUMBER_OF_MESSAGES = 1..255
24
24
  GET_QUEUE_ATTRIBUTES = ['All', 'ApproximateNumberOfMessages', 'VisibilityTimeout']
25
25
  SET_QUEUE_ATTRIBUTES = ['VisibilityTimeout']
26
26
 
@@ -50,6 +50,7 @@ module ActiveMessaging
50
50
 
51
51
  #initialize the subscriptions and queues
52
52
  @subscriptions = {}
53
+ @queues_by_priority = {}
53
54
  @current_subscription = 0
54
55
  queues
55
56
  end
@@ -69,6 +70,10 @@ module ActiveMessaging
69
70
  else
70
71
  @subscriptions[queue.name] = Subscription.new(queue.name, message_headers)
71
72
  end
73
+ priority = @subscriptions[queue.name].priority
74
+
75
+ @queues_by_priority[priority] = [] unless @queues_by_priority.has_key?(priority)
76
+ @queues_by_priority[priority] << queue.name unless @queues_by_priority[priority].include?(queue.name)
72
77
  end
73
78
 
74
79
  # queue_name string, headers hash
@@ -76,7 +81,10 @@ module ActiveMessaging
76
81
  def unsubscribe queue_name, message_headers={}
77
82
  if @subscriptions[queue_name]
78
83
  @subscriptions[queue_name].remove
79
- @subscriptions.delete(queue_name) if @subscriptions[queue_name].count <= 0
84
+ if @subscriptions[queue_name].count <= 0
85
+ sub = @subscriptions.delete(queue_name)
86
+ @queues_by_priority[sub.priority].delete(queue_name)
87
+ end
80
88
  end
81
89
  end
82
90
 
@@ -87,24 +95,60 @@ module ActiveMessaging
87
95
  send_messsage queue, message_body
88
96
  end
89
97
 
90
- # receive a single message from any of the subscribed queues
91
- # check each queue once, then sleep for poll_interval
92
- def receive
93
- raise "No subscriptions to receive messages from." if (@subscriptions.nil? || @subscriptions.empty?)
94
- start = @current_subscription
95
- while true
96
- # puts "calling receive..."
97
- @current_subscription = ((@current_subscription < @subscriptions.length-1) ? @current_subscription + 1 : 0)
98
- sleep poll_interval if (@current_subscription == start)
99
- queue_name = @subscriptions.keys.sort[@current_subscription]
100
- queue = queues[queue_name]
101
- subscription = @subscriptions[queue_name]
102
- unless queue.nil?
103
- messages = retrieve_messsages queue, 1, subscription.headers[:visibility_timeout]
104
- return messages[0] unless (messages.nil? or messages.empty? or messages[0].nil?)
98
+ # new receive respects priorities
99
+ def receive(options={})
100
+ message = nil
101
+
102
+ only_priorities = options[:priorities]
103
+
104
+ # loop through the priorities
105
+ @queues_by_priority.keys.sort.each do |priority|
106
+
107
+ # skip this priority if there is a list, and it is not in the list
108
+ next if only_priorities && !only_priorities.include?(priority.to_i)
109
+
110
+ # puts " - priority: #{priority}"
111
+ # loop through queues for the priority in random order each time
112
+ @queues_by_priority[priority].shuffle.each do |queue_name|
113
+ # puts " - queue_name: #{queue_name}"
114
+ queue = queues[queue_name]
115
+ subscription = @subscriptions[queue_name]
116
+
117
+ next if queue.nil? || subscription.nil?
118
+ messages = retrieve_messsages(queue, 1, subscription.headers[:visibility_timeout])
119
+
120
+ if (messages && !messages.empty?)
121
+ message = messages[0]
122
+ end
123
+
124
+ break if message
105
125
  end
126
+
127
+ break if message
106
128
  end
107
- end
129
+
130
+ # puts " - message: #{message}"
131
+ message
132
+ end
133
+
134
+ # # receive a single message from any of the subscribed queues
135
+ # # check each queue once, then sleep for poll_interval
136
+ # def receive
137
+ # raise "No subscriptions to receive messages from." if (@subscriptions.nil? || @subscriptions.empty?)
138
+ # start = @current_subscription
139
+ # while true
140
+ # # puts "calling receive..."
141
+ # @current_subscription = ((@current_subscription < @subscriptions.length-1) ? @current_subscription + 1 : 0)
142
+ # sleep poll_interval if (@current_subscription == start)
143
+ # queue_name = @subscriptions.keys.sort[@current_subscription]
144
+ # queue = queues[queue_name]
145
+ # subscription = @subscriptions[queue_name]
146
+ # unless queue.nil?
147
+ # messages = retrieve_messsages queue, 1, subscription.headers[:visibility_timeout]
148
+ # return messages[0] unless (messages.nil? or messages.empty? or messages[0].nil?)
149
+ # end
150
+ # end
151
+ # end
108
152
 
109
153
  def received message, headers={}
110
154
  begin
@@ -125,7 +169,7 @@ module ActiveMessaging
125
169
  def create_queue(name)
126
170
  validate_new_queue name
127
171
  response = make_request('CreateQueue', nil, {'QueueName'=>name})
128
- add_queue response.get_text("//QueueUrl") unless response.nil?
172
+ add_queue(response.get_text("//QueueUrl")) unless response.nil?
129
173
  end
130
174
 
131
175
  def delete_queue queue
@@ -264,7 +308,9 @@ module ActiveMessaging
264
308
 
265
309
  def get_or_create_queue queue_name
266
310
  qs = queues
267
- qs.has_key?(queue_name) ? qs[queue_name] : create_queue(queue_name)
311
+ q = qs.has_key?(queue_name) ? qs[queue_name] : create_queue(queue_name)
312
+ raise "could not get or create queue: #{queue_name}" unless q
313
+ q
268
314
  end
269
315
 
270
316
  def queues
@@ -359,9 +405,10 @@ module ActiveMessaging
359
405
  end
360
406
 
361
407
  class Subscription
362
- attr_accessor :name, :headers, :count
408
+ attr_accessor :destination, :headers, :count, :priority
363
409
 
364
410
  def initialize(destination, headers={}, count=1)
411
+ @priority = headers.delete(:priority) || 1001
365
412
  @destination, @headers, @count = destination, headers, count
366
413
  end
367
414
 
@@ -39,7 +39,8 @@ module ActiveMessaging
39
39
 
40
40
  # receive a single message from any of the subscribed destinations
41
41
  # check each destination once, then sleep for poll_interval
42
- def receive
42
+ # adding options,optionally, so a poller can get certain messages (e.g. by priority)
43
+ def receive(options={})
43
44
  end
44
45
 
45
46
  # called after a message is successfully received and processed
@@ -46,7 +46,7 @@ module ActiveMessaging
46
46
  @connection.put(message, priority, delay, ttr)
47
47
  end
48
48
 
49
- def receive
49
+ def receive(options={})
50
50
  message = @connection.reserve
51
51
  Beanstalk::Message.new message
52
52
  end
@@ -84,22 +84,24 @@ module ActiveMessaging
84
84
  end
85
85
  producer.send message
86
86
  end
87
-
88
- def receive_any
89
- @consumers.find do |k, c|
90
- message = c.receive(1)
91
- return condition_message(message) unless message.nil?
92
- end
87
+
88
+ def receive(options={})
89
+ queue_name = options[:queue_name]
90
+ headers = options[:headers] || {}
91
+ receive_message(queue_name, headers)
93
92
  end
94
-
95
- def receive queue_name=nil, headers={}
93
+
94
+ def receive_message(queue_name=nil, headers={})
96
95
  if queue_name.nil?
97
- receive_any
96
+ @consumers.find do |k, c|
97
+ message = c.receive(1)
98
+ return condition_message(message) unless message.nil?
99
+ end
98
100
  else
99
- consumer = subscribe queue_name, headers
101
+ consumer = subscribe(queue_name, headers)
100
102
  message = consumer.receive(1)
101
- unsubscribe queue_name, headers
102
- condition_message message
103
+ unsubscribe(queue_name, headers)
104
+ condition_message(message)
103
105
  end
104
106
  end
105
107
 
@@ -89,7 +89,7 @@ module ActiveMessaging
89
89
 
90
90
  # receive a single message from any of the subscribed destinations
91
91
  # check each destination once, then sleep for poll_interval
92
- def receive
92
+ def receive(options={})
93
93
 
94
94
  raise "No subscriptions to receive messages from." if (subscriptions.nil? || subscriptions.empty?)
95
95
  start = current_subscription
@@ -73,7 +73,7 @@ module ActiveMessaging
73
73
 
74
74
  # receive a single message from any of the subscribed destinations
75
75
  # check each destination once, then sleep for poll_interval
76
- def receive
76
+ def receive(options={})
77
77
  m = @stomp_connection.receive
78
78
  Message.new(m) if m
79
79
  end
@@ -44,7 +44,7 @@ module ActiveMessaging
44
44
  destination.send Message.new(message_body, nil, message_headers, destination_name)
45
45
  end
46
46
 
47
- def receive
47
+ def receive(options={})
48
48
  destination = @destinations.find do |q|
49
49
  find_subscription(q.name) && !q.empty?
50
50
  end
@@ -49,7 +49,7 @@ module ActiveMessaging
49
49
  end
50
50
 
51
51
  # Receive method needed by a13g
52
- def receive
52
+ def receive(options={})
53
53
  raise "No subscription to receive messages from" if (@queue_names.nil? || @queue_names.empty?)
54
54
  start = @current_queue
55
55
  while true
@@ -1,25 +1,28 @@
1
1
  require 'yaml'
2
2
  require 'ostruct'
3
3
  require 'erb'
4
+ require 'thread'
4
5
 
5
6
  module ActiveMessaging
6
7
 
7
8
  class Gateway
8
- cattr_accessor :adapters, :subscriptions, :named_destinations, :filters, :processor_groups, :connections
9
- @@adapters = {}
10
- @@subscriptions = {}
11
- @@named_destinations = {}
12
- @@filters = []
13
- @@connections = {}
14
- @@processor_groups = {}
15
- @@current_processor_group = nil
9
+
10
+ @adapters = {}
11
+ @subscriptions = {}
12
+ @named_destinations = {}
13
+ @filters = []
14
+ @connections = {}
15
+ @processor_groups = {}
16
+ @current_processor_group = nil
16
17
 
17
18
  # these are used to manage the running connection threads
18
- @@running = true
19
- @@connection_threads = {}
20
- @@guard = Mutex.new
21
-
22
- class <<self
19
+ @running = true
20
+ @connection_threads = {}
21
+ @guard = Mutex.new
22
+
23
+ class << self
24
+
25
+ attr_accessor :adapters, :subscriptions, :named_destinations, :filters, :processor_groups, :connections
23
26
 
24
27
  # Starts up an message listener to start polling for messages on each configured connection, and dispatching processing
25
28
  def start
@@ -28,9 +31,9 @@ module ActiveMessaging
28
31
  subscribe
29
32
 
30
33
  # for each connection, start a thread
31
- @@connections.each do |name, conn|
32
- @@connection_threads[name] = Thread.start do
33
- while @@running
34
+ @connections.each do |name, conn|
35
+ @connection_threads[name] = Thread.start do
36
+ while @running
34
37
  begin
35
38
  Thread.current[:message] = nil
36
39
  Thread.current[:message] = conn.receive
@@ -42,7 +45,11 @@ module ActiveMessaging
42
45
  rescue Object=>exception
43
46
  ActiveMessaging.logger.error "ActiveMessaging: thread[#{name}]: Exception from connection.receive: #{exception.message}\n" + exception.backtrace.join("\n\t")
44
47
  ensure
45
- dispatch Thread.current[:message] if Thread.current[:message]
48
+ if Thread.current[:message]
49
+ @guard.synchronize {
50
+ dispatch Thread.current[:message]
51
+ }
52
+ end
46
53
  Thread.current[:message] = nil
47
54
  end
48
55
  Thread.pass
@@ -51,11 +58,11 @@ module ActiveMessaging
51
58
  end
52
59
  end
53
60
 
54
- while @@running
61
+ while @running
55
62
  trap("TERM", "EXIT")
56
63
  living = false
57
- @@connection_threads.each { |name, thread| living ||= thread.alive? }
58
- @@running = living
64
+ @connection_threads.each { |name, thread| living ||= thread.alive? }
65
+ @running = living
59
66
  sleep 1
60
67
  end
61
68
  ActiveMessaging.logger.error "All connection threads have died..."
@@ -72,14 +79,14 @@ module ActiveMessaging
72
79
 
73
80
  def stop
74
81
  # first tell the threads to stop their looping, so they'll stop when next complete a receive/dispatch cycle
75
- @@running = false
82
+ @running = false
76
83
 
77
84
  # if they are dispatching (i.e. !thread[:message].nil?), wait for them to finish
78
85
  # if they are receiving (i.e. thread[:message].nil?), stop them by raising exception
79
86
  dispatching = true
80
87
  while dispatching
81
88
  dispatching = false
82
- @@connection_threads.each do |name, thread|
89
+ @connection_threads.each do |name, thread|
83
90
  if thread[:message]
84
91
  dispatching = true
85
92
  # if thread got killed, but dispatch not done, try it again
@@ -109,15 +116,15 @@ module ActiveMessaging
109
116
  end
110
117
 
111
118
  def connection broker_name='default'
112
- return @@connections[broker_name] if @@connections.has_key?(broker_name)
119
+ return @connections[broker_name] if @connections.has_key?(broker_name)
113
120
  config = load_connection_configuration(broker_name)
114
121
  adapter_class = Gateway.adapters[config[:adapter]]
115
122
  raise "Unknown messaging adapter #{config[:adapter].inspect}!" if adapter_class.nil?
116
- @@connections[broker_name] = adapter_class.new(config)
123
+ @connections[broker_name] = adapter_class.new(config)
117
124
  end
118
125
 
119
126
  def register_adapter adapter_name, adapter_class
120
- adapters[adapter_name] = adapter_class
127
+ Gateway.adapters[adapter_name] = adapter_class
121
128
  end
122
129
 
123
130
  def filter filter, options = {}
@@ -134,8 +141,8 @@ module ActiveMessaging
134
141
  end
135
142
 
136
143
  def disconnect
137
- @@connections.each { |key,connection| connection.disconnect }
138
- @@connections = {}
144
+ @connections.each { |key,connection| connection.disconnect }
145
+ @connections = {}
139
146
  end
140
147
 
141
148
  def execute_filter_chain(direction, message, details={})
@@ -199,19 +206,15 @@ module ActiveMessaging
199
206
  end
200
207
 
201
208
  def dispatch(message)
202
- @@guard.synchronize {
203
- begin
204
- prepare_application
205
- _dispatch(message)
206
- rescue Object => exc
207
- ActiveMessaging.logger.error "Dispatch exception: #{exc}"
208
- ActiveMessaging.logger.error exc.backtrace.join("\n\t")
209
- raise exc
210
- ensure
211
- ActiveMessaging.logger.flush rescue nil
212
- reset_application
213
- end
214
- }
209
+ prepare_application
210
+ _dispatch(message)
211
+ rescue Object => exc
212
+ ActiveMessaging.logger.error "Dispatch exception: #{exc}"
213
+ ActiveMessaging.logger.error exc.backtrace.join("\n\t")
214
+ raise exc
215
+ ensure
216
+ ActiveMessaging.logger.flush rescue nil
217
+ reset_application
215
218
  end
216
219
 
217
220
  def _dispatch(message)
@@ -279,7 +282,7 @@ module ActiveMessaging
279
282
  proc_name = processor.name.underscore
280
283
  proc_sym = processor.name.underscore.to_sym
281
284
  if (!current_processor_group || processor_groups[current_processor_group].include?(proc_sym))
282
- @@subscriptions["#{proc_name}:#{destination_name}"]= Subscription.new(find_destination(destination_name), processor, headers)
285
+ @subscriptions["#{proc_name}:#{destination_name}"]= Subscription.new(find_destination(destination_name), processor, headers)
283
286
  end
284
287
  end
285
288
 
@@ -335,13 +338,13 @@ module ActiveMessaging
335
338
  end
336
339
 
337
340
  def current_processor_group
338
- if ARGV.length > 0 && !@@current_processor_group
341
+ if ARGV.length > 0 && !@current_processor_group
339
342
  ARGV.each {|arg|
340
343
  pair = arg.split('=')
341
344
  if pair[0] == 'process-group'
342
345
  group_sym = pair[1].to_sym
343
346
  if processor_groups.has_key? group_sym
344
- @@current_processor_group = group_sym
347
+ @current_processor_group = group_sym
345
348
  else
346
349
  ActiveMessaging.logger.error "Unrecognized process-group."
347
350
  ActiveMessaging.logger.error "You specified process-group #{pair[1]}, make sure this is specified in config/messaging.rb"
@@ -353,7 +356,7 @@ module ActiveMessaging
353
356
  end
354
357
  }
355
358
  end
356
- @@current_processor_group
359
+ @current_processor_group
357
360
  end
358
361
 
359
362
  def load_connection_configuration(label='default')
@@ -0,0 +1,191 @@
1
+ # still working on prioritized worker requests
2
+
3
+
4
+ # This owes no small debt to sidekiq for showing how to use celluloid for polling for messages.
5
+ # https://github.com/mperham/sidekiq/blob/poller/lib/sidekiq/manager.rb
6
+
7
+ require 'celluloid'
8
+
9
+ module ActiveMessaging
10
+
11
+ class ThreadedPoller
12
+
13
+ include Celluloid
14
+
15
+ # traps when any worker dies
16
+ trap_exit :died
17
+
18
+ attr_accessor :configuration, :receiver, :connection, :workers, :busy, :running
19
+
20
+ #
21
+ # connection is a string, name of the connection from broker.yml to use for this threaded poller instance
22
+ #
23
+ # configuration is a list of hashes
24
+ # each has describes a group of worker threads
25
+ # for each group, define what priorities those workers will process
26
+ # [
27
+ # {
28
+ # :pool_size => 1 # number of workers of this type
29
+ # :priorities => [1,2,3] # what message priorities this thread will process
30
+ # }
31
+ # ]
32
+ #
33
+ def initialize(connection='default', configuration={})
34
+ # default config is a pool size of 3 worker threads
35
+ self.configuration = configuration || [{:pool_size => 3}]
36
+ self.connection = connection
37
+ end
38
+
39
+ def start
40
+ logger.info "ActiveMessaging::ThreadedPoller start"
41
+
42
+ # these are workers ready to use
43
+ self.workers = []
44
+
45
+ # these are workers already working
46
+ self.busy = []
47
+
48
+ # this indicates if we are running or not, helps threads to stop gracefully
49
+ self.running = true
50
+
51
+ # subscribe will create the connections based on subscriptions in processsors
52
+ # (you can't find or use the connection until it is created by calling this)
53
+ ActiveMessaging::Gateway.subscribe
54
+
55
+ # create a message receiver actor, ony need one, using connection
56
+ receiver_connection = ActiveMessaging::Gateway.connection(connection)
57
+ self.receiver = MessageReceiver.new(current_actor, receiver_connection)
58
+
59
+ # start the workers based on the config
60
+ configuration.each do |c|
61
+ (c[:pool_size] || 1).times{ self.workers << Worker.new_link(current_actor, c) }
62
+ end
63
+
64
+ # once all workers are created, start them up
65
+ self.workers.each{|worker| receive(worker)}
66
+
67
+ # in debug level, log info about workers every 10 seconds
68
+ log_status
69
+ end
70
+
71
+ def stop
72
+ logger.info "ActiveMessaging::ThreadedPoller stop"
73
+ # indicates to all busy workers not to pick up another messages, but does not interrupt
74
+ # also indicates to the message receiver to stop getting more messages
75
+ self.running = false
76
+
77
+ # tell each waiting worker to shut down. Running ones will be allowed to finish
78
+ workers.each { |w| w.terminate if w.alive? }
79
+ end
80
+
81
+ # recursive method, uses celluloid 'after' to keep calling
82
+ def log_status
83
+ return unless logger.debug?
84
+ logger.debug("ActiveMessaging::ThreadedPoller: conn:#{connection}, #{workers.count}, #{busy.count}, #{running}")
85
+ after(10){ log_status }
86
+ end
87
+
88
+ def receive(worker)
89
+ receiver.receive!(worker) if (receiver && running && worker)
90
+ end
91
+
92
+ def dispatch(message, worker)
93
+ workers.delete(worker)
94
+ busy << worker
95
+ worker.execute!(message)
96
+ end
97
+
98
+ def executed(worker)
99
+ busy.delete(worker)
100
+
101
+ if running
102
+ workers << worker
103
+ receive(worker)
104
+ else
105
+ worker.terminate if worker.alive?
106
+ if busy.empty?
107
+ logger.info "all executed: signal stopped"
108
+ self.terminate if alive?
109
+ end
110
+ end
111
+ end
112
+
113
+ def died(worker, reason)
114
+ logger.info "uh oh, #{worker.inspect} died because of #{reason.class}"
115
+ busy.delete(worker)
116
+
117
+ if running
118
+ worker = Worker.new_link(current_actor)
119
+ workers << worker
120
+ receive(worker)
121
+ else
122
+ logger.info "check to see if busy is empty: #{busy.inspect}"
123
+ if busy.empty?
124
+ logger.info "all died: signal stopped"
125
+ after(0){ self.terminate } if alive?
126
+ end
127
+ end
128
+ end
129
+
130
+ def stopped?
131
+ !alive? || (!running && busy.empty?)
132
+ end
133
+
134
+ def logger; ActiveMessaging.logger; end
135
+
136
+ end
137
+
138
+ class MessageReceiver
139
+ include Celluloid
140
+
141
+ attr_accessor :poller, :connection, :pause
142
+
143
+ def initialize(poller, connection, pause=1)
144
+ logger.debug("MessageReceiver initialize: poller:#{poller}, connection:#{connection}, pause:#{pause}")
145
+
146
+ raise "No connection found for '#{poller.connection}'" unless connection
147
+
148
+ self.poller = poller
149
+ self.connection = connection
150
+ self.pause = pause
151
+ end
152
+
153
+ def receive(worker)
154
+ return unless poller.running
155
+
156
+ message = self.connection.receive(worker.options)
157
+
158
+ if message
159
+ logger.debug("ActiveMessaging::MessageReceiver.receive: message:'#{message.inspect}'")
160
+ poller.dispatch!(message, worker)
161
+ else
162
+ self.terminate if !poller.running && alive?
163
+ logger.debug("ActiveMessaging::MessageReceiver.receive: no message, retry in #{pause} sec")
164
+ after(pause) { receive(worker) }
165
+ end
166
+
167
+ end
168
+
169
+ def logger; ActiveMessaging.logger; end
170
+ end
171
+
172
+ class Worker
173
+ include Celluloid
174
+
175
+ attr_accessor :poller, :options
176
+
177
+ def initialize(poller, options)
178
+ self.poller = poller
179
+ self.options = options
180
+ end
181
+
182
+ def execute(message)
183
+ ActiveMessaging::Gateway.dispatch(message)
184
+ poller.executed!(current_actor)
185
+ end
186
+
187
+ def logger; ActiveMessaging.logger; end
188
+
189
+ end
190
+
191
+ end
@@ -12,7 +12,12 @@ module ActiveMessaging
12
12
  template "poller", "script/#{poller_name}"
13
13
  chmod("script/#{poller_name}", 0755)
14
14
  end
15
-
15
+
16
+ def copy_threaded_poller
17
+ template "threaded_poller", "script/threaded_#{poller_name}"
18
+ chmod("script/threaded_#{poller_name}", 0755)
19
+ end
20
+
16
21
  def copy_poller_rb
17
22
  copy_file "poller.rb", "lib/#{poller_name}.rb"
18
23
  end
@@ -3,25 +3,20 @@
3
3
  STDOUT.sync = true; STDOUT.flush
4
4
  STDERR.sync = true; STDERR.flush
5
5
 
6
- #Try to Load Merb
7
- merb_init_file = File.expand_path(File.dirname(__FILE__)+'/../config/merb_init')
8
- if File.exists? merb_init_file
9
- require File.expand_path(File.dirname(__FILE__)+'/../config/boot')
10
- #need this because of the CWD
11
- Merb.root = MERB_ROOT
12
- require merb_init_file
6
+ app_root = ENV['APP_ROOT'] || File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
7
+ application_file = File.join(app_root, 'config', 'environment.rb')
8
+
9
+ if File.exist?(application_file)
10
+ load application_file
13
11
  else
14
- # Load Rails
15
- RAILS_ROOT=File.expand_path(File.join(File.dirname(__FILE__), '..'))
16
- require File.join(RAILS_ROOT, 'config', 'boot')
17
- require File.join(RAILS_ROOT, 'config', 'environment')
12
+ raise "#{application_file} does not exist!"
18
13
  end
19
14
 
20
- require 'active_support'
21
- require 'activemessaging'
15
+ Rails.logger = Logger.new(STDOUT)
16
+ ActiveMessaging.logger = Rails.logger
22
17
 
23
- # Load ActiveMessaging processors
24
- #ActiveMessaging::load_processors
18
+ # Load ActiveMessaging
19
+ ActiveMessaging::load_processors
25
20
 
26
21
  # Start it up!
27
22
  ActiveMessaging::start
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # The threaded poller will use celluloid to generate many threads for a single poller process to handle messages
4
+ # Yes, I absolutely looked at the sidekiq manager code, and this code is very very similar
5
+ # This script also shows how to use the poller without loading Rails, though it loads the app lib directory
6
+ # this is tested on 1.9.3, and rails 3+
7
+
8
+ # Make sure stdout and stderr write out without delay for using with daemon like scripts
9
+ STDOUT.sync = true; STDOUT.flush
10
+ STDERR.sync = true; STDERR.flush
11
+
12
+ # we're not going to load rails, but activemessaging does expect there to be an app root
13
+ # this goes back to when it was used in both rails or merb, but work with just not loading a framework
14
+ app_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
15
+ ENV['APP_ROOT'] ||= app_root
16
+
17
+ # minimal requirements, other requires are in the lib
18
+ require 'rubygems'
19
+ require 'bundler/setup'
20
+
21
+ # load the app lib directory
22
+ lib_path = File.dirname("#{app_root}/lib")
23
+ $:.unshift(lib_path)
24
+ Dir["#{lib_path}/**/*.rb"].each {|file| require file unless file.match(/poller\.rb/) }
25
+
26
+ # require and load activemessaging
27
+ require 'activemessaging'
28
+ ActiveMessaging.load_activemessaging
29
+
30
+ # configure the connection (there can be multiple defined in broker.yml) and number of threads
31
+ connection_name = 'default'
32
+ configuration = [{:pool_size => 3}]
33
+
34
+ # start it up!
35
+ begin
36
+ trap("TERM", "EXIT")
37
+ @poller = ActiveMessaging::ThreadedPoller.new(connection_name, configuration)
38
+ @poller.start!
39
+ sleep
40
+ rescue Interrupt
41
+ puts "-- Interrupt --"
42
+ @poller.stop!
43
+ @poller.wait(:shutdown)
44
+ exit(0)
45
+ end
metadata CHANGED
@@ -1,15 +1,10 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: activemessaging
3
- version: !ruby/object:Gem::Version
4
- hash: 59
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 9
9
- - 0
10
- version: 0.9.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Jon Tirsen
14
9
  - Andrew Kuklewicz
15
10
  - Olle Jonsson
@@ -19,36 +14,52 @@ authors:
19
14
  autorequire:
20
15
  bindir: bin
21
16
  cert_chain: []
22
-
23
- date: 2011-12-05 00:00:00 -05:00
24
- default_executable:
25
- dependencies:
26
- - !ruby/object:Gem::Dependency
17
+ date: 2012-07-13 00:00:00.000000000 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
27
20
  name: activesupport
21
+ requirement: !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ type: :runtime
28
28
  prerelease: false
29
- requirement: &id001 !ruby/object:Gem::Requirement
29
+ version_requirements: !ruby/object:Gem::Requirement
30
30
  none: false
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- hash: 23
35
- segments:
36
- - 1
37
- - 0
38
- - 0
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
39
34
  version: 1.0.0
35
+ - !ruby/object:Gem::Dependency
36
+ name: celluloid
37
+ requirement: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
40
43
  type: :runtime
41
- version_requirements: *id001
42
- description: ActiveMessaging is an attempt to bring the simplicity and elegance of rails development to the world of messaging. Messaging, (or event-driven architecture) is widely used for enterprise integration, with frameworks such as Java's JMS, and products such as ActiveMQ, Tibco, IBM MQSeries, etc. Now supporting Rails 3 as of version 0.8.0.
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ description: ActiveMessaging is an attempt to bring the simplicity and elegance of
52
+ rails development to the world of messaging. Messaging, (or event-driven architecture)
53
+ is widely used for enterprise integration, with frameworks such as Java's JMS, and
54
+ products such as ActiveMQ, Tibco, IBM MQSeries, etc. Now supporting Rails 3 as of
55
+ version 0.8.0.
43
56
  email: activemessaging-discuss@googlegroups.com
44
57
  executables: []
45
-
46
58
  extensions: []
47
-
48
- extra_rdoc_files:
49
- - README
50
- files:
51
- - README
59
+ extra_rdoc_files:
60
+ - README.md
61
+ files:
62
+ - README.md
52
63
  - Rakefile
53
64
  - VERSION
54
65
  - activemessaging.gemspec
@@ -98,6 +109,7 @@ files:
98
109
  - lib/activemessaging/processor.rb
99
110
  - lib/activemessaging/railtie.rb
100
111
  - lib/activemessaging/test_helper.rb
112
+ - lib/activemessaging/threaded_poller.rb
101
113
  - lib/activemessaging/trace_filter.rb
102
114
  - lib/generators/active_messaging/install/USAGE
103
115
  - lib/generators/active_messaging/install/install_generator.rb
@@ -105,6 +117,7 @@ files:
105
117
  - lib/generators/active_messaging/install/templates/broker.yml
106
118
  - lib/generators/active_messaging/install/templates/poller
107
119
  - lib/generators/active_messaging/install/templates/poller.rb
120
+ - lib/generators/active_messaging/install/templates/threaded_poller
108
121
  - lib/generators/active_messaging/processor/USAGE
109
122
  - lib/generators/active_messaging/processor/processor_generator.rb
110
123
  - lib/generators/active_messaging/processor/templates/messaging.rb
@@ -124,48 +137,29 @@ files:
124
137
  - test/stomp_test.rb
125
138
  - test/test_helper.rb
126
139
  - test/tracer_test.rb
127
- has_rdoc: true
128
140
  homepage: http://github.com/kookster/activemessaging
129
141
  licenses: []
130
-
131
142
  post_install_message:
132
143
  rdoc_options: []
133
-
134
- require_paths:
144
+ require_paths:
135
145
  - lib
136
- required_ruby_version: !ruby/object:Gem::Requirement
146
+ required_ruby_version: !ruby/object:Gem::Requirement
137
147
  none: false
138
- requirements:
139
- - - ">="
140
- - !ruby/object:Gem::Version
141
- hash: 3
142
- segments:
143
- - 0
144
- version: "0"
145
- required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
153
  none: false
147
- requirements:
148
- - - ">="
149
- - !ruby/object:Gem::Version
150
- hash: 3
151
- segments:
152
- - 0
153
- version: "0"
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
154
158
  requirements: []
155
-
156
159
  rubyforge_project:
157
- rubygems_version: 1.4.2
160
+ rubygems_version: 1.8.22
158
161
  signing_key:
159
162
  specification_version: 3
160
- summary: Official activemessaging gem, now hosted on github.com/kookster. (kookster prefix temporary)
161
- test_files:
162
- - test/all_tests.rb
163
- - test/asqs_test.rb
164
- - test/config_test.rb
165
- - test/filter_test.rb
166
- - test/gateway_test.rb
167
- - test/jms_test.rb
168
- - test/reliable_msg_test.rb
169
- - test/stomp_test.rb
170
- - test/test_helper.rb
171
- - test/tracer_test.rb
163
+ summary: Official activemessaging gem, now hosted on github.com/kookster. (kookster
164
+ prefix temporary)
165
+ test_files: []
data/README DELETED
@@ -1,28 +0,0 @@
1
- ActiveMessaging is an attempt to bring the simplicity and elegance of rails development to the world of messaging. Messaging, (or event-driven architecture) is widely used for enterprise integration, with frameworks such as Java's JMS, and products such as ActiveMQ, Tibco, IBM MQSeries, etc.
2
-
3
- ActiveMessaging is a generic framework to ease using messaging, but is not tied to any particular messaging system - in fact, it now has support for Stomp, Amazon Simple Queue Service (SQS), Beanstalk, JMS (using StompConnect or [JMSWithJRuby direct on JRuby]), WebSphere MQ, and the all-Ruby ReliableMessaging.
4
-
5
- Here's a sample of a processor class that handles incoming messages:
6
-
7
- class HelloWorldProcessor < ActiveMessaging::Processor
8
-
9
- subscribes_to :hello_world
10
-
11
- def on_message(message)
12
- puts "received: " + message
13
- end
14
-
15
- end
16
-
17
-
18
- h1. Support
19
-
20
- Best bet is the google groups mailing list:
21
-
22
- http://groups.google.com/group/activemessaging-discuss
23
-
24
- h1. Fork Details
25
-
26
- This fork adds the ability to specify a dead letter queue prefix so instead of all messages going to 1 dead letter queue, each message will go to a dead letter queue that is based on the deadLetterQueuePrefix configuration property and the originating queue.
27
-
28
- For example, if you specific a deadLetterQueuePrefix of "DLQ." in your broker.yml and the originating queue is /queue/myqueue then the dead letter queue will be /queue/DLQ.myqueue