gilmour 0.3.4 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c95ad64d9f1f755d37f31ebfc42d86f480f609b3
4
- data.tar.gz: 4a6e00ddbf3eacc9eb31beee7319bd54f4152f53
3
+ metadata.gz: e019128d85d1f18f07ee9c90a8444950e17e77e4
4
+ data.tar.gz: 424ecd2839a45bd7f89ed8e5abf9c256315200bc
5
5
  SHA512:
6
- metadata.gz: 0c94da1ec0e755bd6106d62bf938dd40b88eb6d845f88ea754c8505f750f6433b27d4bc2842eb260f6faf92999bd140f7b3ed3f22628f33c0ace29982353b5c6
7
- data.tar.gz: 0e029a1923918f2374d0a3476693f267dd3f724e44e032ce4cf194313c709c0f0db2790a5c97b4420d23c32aa9288227523555442a30bfb0a823646aa8f1afe2
6
+ metadata.gz: 4bd6a702576f5cc27f791a6b25f6520dd08ae3212fa1ab1adf056976e8086f44de057e37aef55bbac3ae9ad278efe690b23adadf3a285d1600c8c39f870fd012
7
+ data.tar.gz: 2540aabffac9e90f958d28170ce143fd5194efb01ed77ad5dfbdc2d0e17e6337c12191491f7700f04b8024c1e178d4950b1268f5e3e91949c3a18302d5d948b8
data/.travis.yml CHANGED
@@ -14,3 +14,4 @@ script:
14
14
  - bundle exec rspec spec/test_service_base.rb -b --format documentation
15
15
  - bundle exec rspec spec/test_subscriber_redis.rb -b --format documentation
16
16
  - bundle exec rspec spec/test_subscriber_redis_forked.rb -b --format documentation
17
+ - bundle exec rspec spec/test_waiter.rb -b --format documentation
data/README.md CHANGED
@@ -3,7 +3,35 @@
3
3
  Gilmour is a framework for writing micro-services that exchange data over
4
4
  non-http transports. Currently the supported backend is Redis PubSub.
5
5
  Redis pubsub channels are used like "routes".
6
- The DSL provided is similar to Sinatra.
6
+ Gilmour started off simply as a non-http alternative to Sinatra, but has grown into a feature rich microservices communication library and framework.
7
+
8
+ ## Patterns
9
+
10
+ Gilmour enables two patterns of communication:
11
+
12
+ ### The request-response pattern
13
+ This is the most common pattern found in microservice architectures. An entity
14
+ sends a `request`, and a subscriber processes it and sends a `reply`. See
15
+ `examples/fibonacci.rb` as an example. Gilmour also allows horizontal scaling
16
+ by using __exclusion groups__. When there are multiple instances of a subscriber running, only one subscriber from an exclusion group processes that request.
17
+ Eg, if you run `container.rb` from the examples multiple times simultaneously, running `echoclient.rb` or `forkechoclient.rb` will produce the same result, irrespective of how many instances of the container are running. (More of containers later).
18
+
19
+ ### The signal-slot pattern
20
+ A less common, but very powerful pattern is event driven service design, or the signal-slot pattern (a term borrowed from the Qt library). One service emits or signals events, which are picked up by one or more subscribers and processed. None of the subscribers send a reply, because a reply really has no meaning in this context. See `examples/signal_slot.rb`.
21
+
22
+ Which pattern to chose, is primarily dependent on the ownership of failure. If the subscribers own the failures they encounter, then you should consider the signal-slot pattern.
23
+
24
+ It is also possible to have slots as well as reply subscribers for a given message. Gilmour provides a utility `broadcast` which internally does a signal and a request.
25
+
26
+ ## Composition
27
+
28
+ Microservices increase the granularity of our services oriented architectures. Borrowing from unix philosohy, they should do one thing and do it well. However, for this to be really useful, there should be a facility such as is provided by unix shells. Unix shells allow the composition of small commands using the following methods
29
+
30
+ * Composition: `cmd1 | cmd2 | cmd2`
31
+ * AndAnd: `cmd1 && cmd2 && cmd3`
32
+ * Batch: `cmd1; cmd2; cmd3 > out` or `(cmd1; cmd2; cmd3) > out`
33
+
34
+ Also, you should be able to use these methods of composition in any combination and also in a nexted manner - `(cmd1 | cmd2) && cmd3`. Gilmour enables you to do just that. See `examples/composition.rb`.
7
35
 
8
36
  ## Protocol
9
37
 
@@ -13,17 +41,44 @@ The structure of the payload is a simple JSON as shown below:
13
41
  {
14
42
  data: The actual payload,
15
43
  sender: The origin of the request (unique for each request),
16
- code: The respoonse code if this is a response
44
+ code: The response code if this is a response. This uses HTTP error codes
17
45
  }
18
46
 
47
+ Gilmour has builtin support for error publishing. The error channel is separate from the response channels and everything that is put on the error channel is prefixed by the per-request unique sender uuid, which makes debugging easier. The error packet protocol is
48
+
49
+ {
50
+ code: response code (non-200)
51
+ sender: the "sender" from the request
52
+ topic: the topic to which the request was sent
53
+ request_data: the request payload
54
+ userdata: implementation dependent debug infomation
55
+ backtrace: the backtrace of the error
56
+ timestamp: the timestamp of the error
57
+ }
58
+
59
+ ## Redis backend conventions
60
+
19
61
  The `sender` field actually represents a unique sender key. Any reponse that
20
62
  is to be sent to the request is sent on the "reservered" topic
21
63
  `response.<sender>` on the same exchange.
22
64
 
23
- ## Usage Examples
65
+ For the request-reply pattern, the topic is prefixed with `gilmour.request`.
66
+ For the signal-slot pettern, the topic is prefixed with `gilmour.slot`.
67
+
68
+ The topic on which errors are published is `gilmour.error`.
69
+
70
+ ## Auto-loading
71
+
72
+ Gilmour code can be structured such that the subscribers are auto-loaded from the filesystem. This enables creation of containers which house multiple microservices, with a choice of which ones to enable. See `examples/container.rb`.
73
+
74
+ ## Health monitoring
75
+
76
+ Gilmour supports health monitoring heartbeats. Every gilmour process, which has health monitoring enabled, listens for hearbeat pings and responds accordingly. External monitors can use this to monitor the health. [The health bulletin](https://github.com/gilmour-libs/health-bulletin) and [the health-tools]( https://github.com/gilmour-libs/health-tools) are examples of such tools.
24
77
 
25
- See the `examples` directory for examples of usage
78
+ ## More Usage Examples
26
79
 
80
+ See the `examples` directory
81
+
27
82
  ## Specs
28
83
 
29
84
  To run the specs, set `REDIS_HOST` (default is localhost) and `REDIS_PORT` (default is 6379)
@@ -0,0 +1,98 @@
1
+ require 'gilmour'
2
+
3
+ class Server
4
+ include Gilmour::Base
5
+ end
6
+ server = Server.new
7
+ gilmour = server.enable_backend('redis')
8
+
9
+ gilmour.reply_to 'one' do
10
+ respond request.body.merge({'one' => 'foo'})
11
+ end
12
+
13
+ gilmour.reply_to 'two' do
14
+ respond request.body.merge({'two' => 'foo'})
15
+ end
16
+
17
+ gilmour.reply_to 'badtwo' do
18
+ respond request.body.merge({'badtwo' => 'foo'}), 500
19
+ end
20
+
21
+ gilmour.reply_to 'three' do
22
+ respond request.body.merge({'three' => 'foo'})
23
+ end
24
+
25
+ composed = gilmour.compose([{topic: 'one'}, {topic: 'two'},
26
+ {topic: 'three', message: {'anotherthree' => 'anotherthree'}}])
27
+ compose_waiter = Gilmour::Waiter.new
28
+ composed.execute({zero: 'foo'}) do |data, code|
29
+ puts "composition:"
30
+ puts data # data will have the keys 'one', 'two', 'three' and 'anotherthree'
31
+ compose_waiter.signal
32
+ end
33
+ compose_waiter.wait
34
+
35
+ andand = gilmour.andand([{topic: 'one'}, {topic: 'two'},
36
+ {topic: 'three', message: {'anotherthree' => 'anotherthree'}}])
37
+ andand_waiter = Gilmour::Waiter.new
38
+ andand.execute do |data, code|
39
+ puts "\nandand:"
40
+ puts data # data will have the keys 'three' and 'anotherthree'
41
+ andand_waiter.signal
42
+ end
43
+ andand_waiter.wait
44
+
45
+
46
+ error_andand = gilmour.andand([{topic: 'one'}, {topic: 'badtwo'},
47
+ {topic: 'three', message: {'anotherthree' => 'anotherthree'}}])
48
+ error_andand_waiter = Gilmour::Waiter.new
49
+ continuation = nil
50
+ error_andand.execute do |data, code, c|
51
+ continuation = c # the part of the pipeline that was not executed
52
+ puts "\nerror_andand:\n code - #{code}" # should be 500 fron badtwo
53
+ puts "error_andand:\n data - #{data}" # this will have keys one and badtwo
54
+ error_andand_waiter.signal
55
+ end
56
+ error_andand_waiter.wait
57
+
58
+ continuation_waiter = Gilmour::Waiter.new
59
+ continuation.execute do |data, code|
60
+ puts "\ncontinuation:"
61
+ puts data # This will have keys three and anotherthree
62
+ continuation_waiter.signal
63
+ end
64
+ continuation_waiter.wait
65
+
66
+
67
+ batch = gilmour.batch([{topic: 'one'}, {topic: 'badtwo'},
68
+ {topic: 'three', message: {'anotherthree' => 'anotherthree'}}])
69
+ batch_waiter = Gilmour::Waiter.new
70
+ batch.execute do |data, code|
71
+ puts "\nbatch:"
72
+ puts data # this will have keys three and anotherthree (wont abort on badtwo)
73
+ batch_waiter.signal
74
+ end
75
+ batch_waiter.wait
76
+
77
+ batch_record = gilmour.batch([{topic: 'one'}, {topic: 'badtwo'},
78
+ {topic: 'three', message: {'anotherthree' => 'anotherthree'}}],
79
+ true) # true will record all responses in an array
80
+ batch_record_waiter = Gilmour::Waiter.new
81
+ batch_record.execute do |data, code|
82
+ puts "\nbatch with record:"
83
+ puts data # This is an array
84
+ batch_record_waiter.signal
85
+ end
86
+ batch_record_waiter.wait
87
+
88
+ t_andand = gilmour.andand([{topic: 'one'}, {topic: 'two'}])
89
+ # Use the above composition inside another
90
+ compose = gilmour.compose([t_andand, {topic: 'three'}])
91
+ combination_waiter = Gilmour::Waiter.new
92
+ compose.execute do |data, code|
93
+ puts "\nnested composition"
94
+ puts data # will have keys two and three
95
+ combination_waiter.signal
96
+ end
97
+ combination_waiter.wait
98
+
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ # Example of running as a container with subscribers
3
+ # Run ruby server.rb first, followed by echoclient.rb
4
+ require 'gilmour'
5
+
6
+ class EventServer
7
+ include Gilmour::Base
8
+
9
+ def initialize
10
+ enable_backend('redis')
11
+ registered_subscribers.each do |sub|
12
+ sub.backend = 'redis'
13
+ end
14
+ start(true)
15
+ end
16
+ load_all './subscribers'
17
+ end
18
+
19
+ EventServer.new
@@ -1,19 +1,23 @@
1
1
  require 'securerandom'
2
- require 'gilmour/backends/redis'
2
+ require 'gilmour'
3
+
4
+ class Server
5
+ include Gilmour::Base
6
+ end
7
+
3
8
 
4
9
  def redis_send_and_recv(message, key)
5
- redis = Gilmour::RedisBackend.new({})
6
- redis.setup_subscribers({})
10
+ server = Server.new
11
+ gilmour = server.enable_backend('redis')
7
12
  count = 0
8
13
  loop do
9
- waiter = Thread.new { loop { sleep 1 } }
10
- newkey = "#{key}.#{SecureRandom.hex(2)}"
11
- redis.publish(count, newkey) do |data, code|
14
+ waiter = Gilmour::Waiter.new
15
+ gilmour.request!(count, key) do |data, code|
12
16
  puts "Client got response: #{code}: #{data}"
13
- waiter.kill
17
+ waiter.signal
14
18
  end
15
19
  count = count + 1
16
- waiter.join
20
+ waiter.wait
17
21
  sleep 1
18
22
  end
19
23
  end
@@ -0,0 +1,32 @@
1
+ require 'gilmour'
2
+
3
+ class Server
4
+ include Gilmour::Base
5
+ end
6
+ server = Server.new
7
+ gilmour = server.enable_backend('redis')
8
+
9
+ gilmour.reply_to "fib", excl_group: 'fib', timeout: 1 do
10
+ first = request.body['first']
11
+ second = request.body['second']
12
+ respond('next' => first + second)
13
+ end
14
+
15
+ handler = proc do |first, second|
16
+ first ||= 1
17
+ second ||= 1
18
+ gilmour.request!({ 'first' => first, 'second' => second }, 'fib',
19
+ timeout: 5) do |resp, code|
20
+ if code != 200
21
+ $stderr.puts "Something went wrong in the response! Aborting"
22
+ exit
23
+ end
24
+ next_val = resp['next']
25
+ puts next_val
26
+ EM.add_timer(1) { handler.call(second, resp['next']) }
27
+ end
28
+ end
29
+
30
+ EM.next_tick { handler.call }
31
+ server.start true
32
+
@@ -0,0 +1,23 @@
1
+ require 'securerandom'
2
+ require 'gilmour'
3
+
4
+ class Server
5
+ include Gilmour::Base
6
+ end
7
+
8
+ def redis_send_and_recv(message, key)
9
+ server = Server.new
10
+ gilmour = server.enable_backend('redis')
11
+ loop do
12
+ waiter = Gilmour::Waiter.new
13
+ puts "Process: Sending: #{key}"
14
+ gilmour.request!(message, key) do |data, code|
15
+ puts "Process: Received: #{data}"
16
+ waiter.signal
17
+ end
18
+ waiter.wait
19
+ sleep 1
20
+ end
21
+ end
22
+
23
+ redis_send_and_recv('Ping', 'forkecho')
@@ -0,0 +1,35 @@
1
+ # Run this as LOG_LEVEL=info ruby relay.rb
2
+ require 'gilmour'
3
+
4
+ class Server
5
+ include Gilmour::Base
6
+ end
7
+
8
+ server = Server.new
9
+ gilmour = server.enable_backend('redis')
10
+
11
+ waiter = Gilmour::Waiter.new
12
+
13
+ waiter.add
14
+ gilmour.slot "user.added" do
15
+ # This one will send out email notifications
16
+ logger.info "Sent email notification for -"
17
+ logger.info request.body
18
+ waiter.done
19
+ end
20
+
21
+ waiter.add
22
+ gilmour.slot "user.added" do
23
+ # This one will send out push notifications
24
+ logger.info "Sent push notification for -"
25
+ logger.info request.body
26
+ waiter.done
27
+ end
28
+
29
+ EM.next_tick {
30
+ gilmour.signal!({ username: 'foo', email: 'foo@bar.com' }, 'user.added')
31
+ }
32
+
33
+ waiter.wait
34
+
35
+
@@ -0,0 +1,22 @@
1
+ class EchoSubscriber < EventServer
2
+ reply_to 'echo', excl_group: 'echo' do
3
+ if request.body == 'quit'
4
+ respond nil
5
+ else
6
+ $stderr.puts request.body
7
+ respond "#{request.topic}"
8
+ end
9
+ end
10
+
11
+ reply_to 'forkecho', fork:true, excl_group: 'forkecho' do
12
+ if request.body == 'quit'
13
+ respond nil
14
+ else
15
+ $stderr.puts "Forked response. pid: #{Process.pid}"
16
+ respond "#{request.topic}"
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+
@@ -3,6 +3,7 @@ require 'socket'
3
3
  require 'securerandom'
4
4
 
5
5
  require_relative '../protocol'
6
+ require_relative '../composers'
6
7
 
7
8
  module Gilmour
8
9
  ErrorChannel = "gilmour.error"
@@ -12,55 +13,59 @@ module Gilmour
12
13
  SUPPORTED_BACKENDS = %w(redis)
13
14
  @@registry = {}
14
15
 
15
- def report_errors?
16
+ include Gilmour::Composers
17
+ attr_accessor :broadcast_errors
18
+
19
+ def report_errors? #:nodoc:
16
20
  #Override this method to adjust if you want errors to be reported.
17
- return true
21
+ return false
18
22
  end
19
23
 
20
- def register_health_check
24
+ def register_health_check #:nodoc:
21
25
  raise NotImplementedError.new
22
26
  end
23
27
 
24
- def unregister_health_check
28
+ def unregister_health_check #:nodoc:
25
29
  raise NotImplementedError.new
26
30
  end
27
31
 
28
- def self.implements(backend_name)
32
+ def self.implements(backend_name) #:nodoc:
29
33
  @@registry[backend_name] = self
30
34
  end
31
35
 
32
- def self.get(backend_name)
36
+ def self.get(backend_name) #:nodoc:
33
37
  @@registry[backend_name]
34
38
  end
35
39
 
36
- # :nodoc:
37
40
  # This should be implemented by the derived class
38
41
  # subscriptions is a hash in the format -
39
- # { topic => [handler1, handler2, ...],
40
- # topic2 => [handler3, handler4, ...],
41
- # ...
42
- # }
42
+ # { topic => [handler1, handler2, ...],
43
+ # topic2 => [handler3, handler4, ...],
44
+ # ...
45
+ # }
43
46
  # where handler is a hash
44
- # { :handler => handler_proc,
45
- # :subscriber => subscriber_derived_class
46
- # }
47
- def setup_subscribers(subscriptions)
47
+ # { :handler => handler_proc,
48
+ # :subscriber => subscriber_derived_class
49
+ # }
50
+ def setup_subscribers(subscriptions) #:nodoc:
48
51
  end
49
52
 
53
+ # This is the underlying method for all publishes. Use only if you know
54
+ # what you are doing. Use the request! and signal! methods instead.
50
55
  # Sends a message
51
56
  # If optional block is given, it will be executed when a response is received
52
57
  # or if timeout occurs
53
58
  # +message+:: The body of the message (any object that is serialisable)
54
59
  # +destination+:: The channel to post to
55
- # +opts+::
56
- # ++timeout+:: Sender side timeout
60
+ # +opts+:: Options
61
+ # timeout:: Sender side timeout
57
62
  #
58
63
  def publish(message, destination, opts = {}, code = 0, &blk)
59
64
  payload, sender = Gilmour::Protocol.create_request(message, code)
60
65
 
61
66
  EM.defer do # Because publish can be called from outside the event loop
62
67
  begin
63
- send(sender, destination, payload, opts, &blk)
68
+ send_message(sender, destination, payload, opts, &blk)
64
69
  rescue Exception => e
65
70
  GLogger.debug e.message
66
71
  GLogger.debug e.backtrace
@@ -69,32 +74,149 @@ module Gilmour
69
74
  sender
70
75
  end
71
76
 
77
+ def request_destination(dest) #:nodoc:
78
+ 'gilmour.request.' + dest
79
+ end
80
+
81
+ def slot_destination(dest) #:nodoc:
82
+ 'gilmour.slot.' + dest
83
+ end
84
+
85
+ # Sends a request
86
+ # If optional block is given, it will be executed when a response is received
87
+ # or if timeout occurs. You usually want to provide this (otherwise consider
88
+ # using slots)
89
+ # Params:
90
+ # +message+:: The body of the message (any object that is serialisable)
91
+ # +destination+:: The channel to post to
92
+ # +opts+:: Options
93
+ # timeout:: Sender side timeout
94
+ # confirm_subscriber:: Confirms that active subscriber exists,
95
+ # else calls blk with 404 error code.
96
+ def request!(message, dest, opts = {}, &blk)
97
+ opts[:confirm_subscriber] = true
98
+ publish(message, request_destination(dest), opts, 0, &blk)
99
+ end
100
+
101
+ # Emits a signal
102
+ # Params:
103
+ # +message+:: The body of the message (any object that is serialisable)
104
+ # +destination+:: The channel to post to
105
+ def signal!(message, dest, opts = {})
106
+ GLogger.error('Signal cannot have a callback. Ignoring!') if block_given?
107
+ publish(message, slot_destination(dest), opts)
108
+ end
109
+
110
+ # Emits a signal and sends a request
111
+ def broadcast(message, destination, opts = {}, code = 0, &blk)
112
+ request(message, destination, opts, code, &blk)
113
+ signal(message, destination, opts)
114
+ end
115
+
72
116
  def emit_error(message)
73
117
  raise NotImplementedError.new
74
118
  end
75
119
 
76
120
  # Adds a new handler for the given _topic_
77
- def add_listener(topic, &handler)
121
+ def add_listener(topic, opts={}, &blk)
78
122
  raise "Not implemented by child class"
79
123
  end
80
124
 
125
+ def listeners(topic) #:nodoc:
126
+ raise NotImplementedError.new
127
+ end
128
+
129
+ def excl_dups?(topic, opts) #:nodoc:
130
+ group = exclusive_group(opts)
131
+ existing = listeners(topic).select { |l| exclusive_group(l) == group }
132
+ !existing.empty?
133
+ end
134
+
135
+ # Sets up a reply listener
136
+ # Params:
137
+ # +topic+:: The topic to listen on
138
+ # +options+:: Options Hash
139
+ # timeout:: a 504 error code is sent after this time and the
140
+ # execution for the request is terminated
141
+ # fork:: The request will be processed in a forked process.
142
+ # useful for long running executions
143
+ # excl_group:: The exclustion group for this handler (see README for explanation)
144
+ # +blk+:: The block to the executed for the request
145
+ def reply_to(topic, options={}, &blk)
146
+ opts = options.dup
147
+ group = exclusive_group(opts)
148
+ if group.empty?
149
+ group = "_default"
150
+ opts[:excl_group] = group
151
+ GLogger.warn("Using default exclusion group for #{topic}")
152
+ end
153
+ req_topic = request_destination(topic)
154
+ if excl_dups?(req_topic, opts)
155
+ raise RuntimeError.new("Duplicate reply handler for #{topic}:#{group}")
156
+ end
157
+ opts[:type] = :reply
158
+ opts[:excl] = true
159
+ add_listener(req_topic, opts, &blk)
160
+ end
161
+
162
+ # Sets up a slot listener
163
+ # Params:
164
+ # +topic+:: The topic to listen on
165
+ # +options+:: Options hash
166
+ # timeout:: a 504 error code is sent after this time and the
167
+ # execution for the request is terminated
168
+ # fork:: The request will be processed in a forked process.
169
+ # useful for long running executions
170
+ # +blk+:: The block to the executed for the request
171
+ def slot(topic, options={}, &blk)
172
+ opts = options.dup
173
+ stopic = slot_destination(topic)
174
+ if opts[:excl] && excl_dups?(stopic, opts)
175
+ raise RuntimeError.new("Duplicate reply handler for #{topic}:#{group}")
176
+ end
177
+ opts[:type] = :slot
178
+ #TODO: Check whether topic has a registered subscriber class?
179
+ # or leave it to a linter??
180
+ add_listener(stopic, opts, &blk)
181
+ end
182
+
81
183
  # Removes existing _handler_ for the _topic_
82
- def remove_listener(topic, &handler)
184
+ # Use only if you have registered the handler with add_listener
185
+ # or listen_to
186
+ def remove_listener(topic, handler)
83
187
  raise "Not implemented by child class"
84
188
  end
85
189
 
86
- def acquire_ex_lock(sender)
190
+ # Removes existing slot handler for the _topic_
191
+ def remove_slot(topic, handler)
192
+ remove_listener(slot_destination(topic), handler)
193
+ end
194
+
195
+ # Removes existing reply handler for the _topic_
196
+ def remove_reply(topic, handler)
197
+ remove_listener(request_destination(topic), handler)
198
+ end
199
+
200
+ def acquire_ex_lock(sender) #:nodoc:
87
201
  raise "Not implemented by child class"
88
202
  end
89
203
 
90
- def send_response(sender, body, code)
204
+ def send_response(sender, body, code) #:nodoc:
91
205
  raise "Not implemented by child class"
92
206
  end
93
207
 
94
- def execute_handler(topic, payload, sub)
208
+ def exclusive_group(sub) #:nodoc:
209
+ (sub[:excl_group] || sub[:subscriber]).to_s
210
+ end
211
+
212
+ def execute_handler(topic, payload, sub) #:nodoc:
95
213
  data, sender = Gilmour::Protocol.parse_request(payload)
96
214
  if sub[:exclusive]
97
- lock_key = sender + sub[:subscriber].to_s
215
+ group = exclusive_group(sub)
216
+ if group.empty?
217
+ raise RuntimeError.new("Exclusive flag without group encountered!")
218
+ end
219
+ lock_key = sender + group
98
220
  acquire_ex_lock(lock_key) { _execute_handler(topic, data, sender, sub) }
99
221
  else
100
222
  _execute_handler(topic, data, sender, sub)
@@ -104,29 +226,32 @@ module Gilmour
104
226
  GLogger.debug e.backtrace
105
227
  end
106
228
 
107
- def _execute_handler(topic, data, sender, sub)
229
+ def _execute_handler(topic, data, sender, sub) #:nodoc:
230
+ respond = (sub[:type] != :slot)
108
231
  Gilmour::Responder.new(
109
- sender, topic, data, self, sub[:timeout], sub[:fork]
232
+ sender, topic, data, self, timeout: sub[:timeout],
233
+ fork: sub[:fork], respond: respond
110
234
  ).execute(sub[:handler])
111
235
  rescue Exception => e
112
236
  GLogger.debug e.message
113
237
  GLogger.debug e.backtrace
114
238
  end
115
239
 
116
- def send
240
+ def send_message #:nodoc:
117
241
  raise "Not implemented by child class"
118
242
  end
119
243
 
120
- def self.load_backend(name)
244
+ def self.load_backend(name) #:nodoc:
121
245
  require_relative name
122
246
  end
123
247
 
124
- def self.load_all_backends
248
+ def self.load_all_backends #:nodoc:
125
249
  SUPPORTED_BACKENDS.each do |f|
126
250
  load_backend f
127
251
  end
128
252
  end
129
253
 
254
+ # stop the backend
130
255
  def stop(sender, body, code)
131
256
  raise "Not implemented by child class"
132
257
  end