gilmour 0.2.4
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +16 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +32 -0
- data/examples/echoclient.rb +22 -0
- data/examples/server.rb +47 -0
- data/examples/thread_example.rb +40 -0
- data/gilmour.gemspec +26 -0
- data/lib/gilmour/backends/backend.rb +148 -0
- data/lib/gilmour/backends/redis.rb +254 -0
- data/lib/gilmour/base.rb +178 -0
- data/lib/gilmour/protocol.rb +48 -0
- data/lib/gilmour/responder.rb +224 -0
- data/lib/gilmour/waiter.rb +20 -0
- data/lib/gilmour.rb +11 -0
- data/test/spec/helpers/common.rb +25 -0
- data/test/spec/helpers/connection.rb +58 -0
- data/test/spec/helpers/data.yml +12 -0
- data/test/spec/test_service_base.rb +48 -0
- data/test/spec/test_subscriber_redis.rb +339 -0
- data/test/spec/test_subscriber_redis_forked.rb +370 -0
- data/test/testservice/subscribers/test_subscriber.rb +103 -0
- data/test/testservice/test_service_base.rb +10 -0
- data/version.rb +3 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed168f421b891494b1f49ad0a572009604d4fe1c
|
4
|
+
data.tar.gz: 7a9fad0b5dfc987d2fd08cb3f19b3d611fd3938a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: acf59c9b55719ae57545085728705a1d300485e6a7c9d539655807eb1055a7cb826a2255f4617da775cb1853b9d61a0185bd5cddbdcb272f3fd011b5d2e147f1
|
7
|
+
data.tar.gz: 0ca69b252a271f54da89da5fdd6749ab9f655262f65c9593e2c7253e29605cb98695c6233def5568e464a48ab823bd0832b7daac7553cf8e15752064dd03afba
|
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 2.1.5
|
5
|
+
|
6
|
+
services:
|
7
|
+
- redis-server
|
8
|
+
|
9
|
+
install:
|
10
|
+
- bundle install
|
11
|
+
|
12
|
+
script:
|
13
|
+
- cd test/
|
14
|
+
- bundle exec rspec spec/test_service_base.rb -b --format documentation
|
15
|
+
- bundle exec rspec spec/test_subscriber_redis.rb -b --format documentation
|
16
|
+
- bundle exec rspec spec/test_subscriber_redis_forked.rb -b --format documentation
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2013 Aditya Godbole
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Gilmour
|
2
|
+
|
3
|
+
Gilmour is a framework for writing micro-services that exchange data over
|
4
|
+
non-http transports. Currently the supported backend is Redis PubSub.
|
5
|
+
Redis pubsub channels are used like "routes".
|
6
|
+
The DSL provided is similar to Sinatra.
|
7
|
+
|
8
|
+
## Protocol
|
9
|
+
|
10
|
+
Gilmour uses it's own simple protocol to send request and response "headers".
|
11
|
+
The structure of the payload is a simple JSON as shown below:
|
12
|
+
|
13
|
+
{
|
14
|
+
data: The actual payload,
|
15
|
+
sender: The origin of the request (unique for each request),
|
16
|
+
code: The respoonse code if this is a response
|
17
|
+
}
|
18
|
+
|
19
|
+
The `sender` field actually represents a unique sender key. Any reponse that
|
20
|
+
is to be sent to the request is sent on the "reservered" topic
|
21
|
+
`response.<sender>` on the same exchange.
|
22
|
+
|
23
|
+
## Usage Examples
|
24
|
+
|
25
|
+
See the `examples` directory for examples of usage
|
26
|
+
|
27
|
+
## Specs
|
28
|
+
|
29
|
+
To run the specs, set `REDIS_HOST` (default is localhost) and `REDIS_PORT` (default is 6379)
|
30
|
+
to point to your rabbitmq or redis server.
|
31
|
+
Then, from within the `test` directory, run `rspec spec/*`
|
32
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'gilmour/backends/redis'
|
3
|
+
|
4
|
+
def redis_send_and_recv(message, key)
|
5
|
+
redis = Gilmour::RedisBackend.new({})
|
6
|
+
redis.setup_subscribers({})
|
7
|
+
count = 0
|
8
|
+
loop do
|
9
|
+
waiter = Thread.new { loop { sleep 1 } }
|
10
|
+
newkey = "#{key}.#{SecureRandom.hex(2)}"
|
11
|
+
redis.publish(count, newkey) do |data, code|
|
12
|
+
puts "Client got response: #{code}: #{data}"
|
13
|
+
waiter.kill
|
14
|
+
end
|
15
|
+
count = count + 1
|
16
|
+
waiter.join
|
17
|
+
sleep 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
redis_send_and_recv('Ping', 'echo')
|
22
|
+
|
data/examples/server.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'gilmour'
|
3
|
+
|
4
|
+
class EventServer
|
5
|
+
include Gilmour::Base
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
backend = 'redis'
|
9
|
+
enable_backend(backend, { })
|
10
|
+
registered_subscribers.each do |sub|
|
11
|
+
sub.backend = backend
|
12
|
+
end
|
13
|
+
$stderr.puts "Starting server. To see messaging in action run clients."
|
14
|
+
start(true)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class EchoSubscriber < EventServer
|
19
|
+
# Passing second parameter as true makes only one instance of this handler handle a request
|
20
|
+
listen_to 'echo.*', {"exclusive" => true} do
|
21
|
+
if request.body == 'Palmolive'
|
22
|
+
respond nil
|
23
|
+
else
|
24
|
+
$stderr.puts request.body
|
25
|
+
respond "#{request.topic}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class FibonacciSubscriber < EventServer
|
31
|
+
class << self
|
32
|
+
attr_accessor :last
|
33
|
+
end
|
34
|
+
|
35
|
+
listen_to 'fib.next' do
|
36
|
+
old = FibonacciSubscriber.last
|
37
|
+
FibonacciSubscriber.last = new = request.body
|
38
|
+
respond(old + new)
|
39
|
+
end
|
40
|
+
|
41
|
+
listen_to 'fib.init' do
|
42
|
+
FibonacciSubscriber.last = request.body
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
EventServer.new
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'gilmour/backends/redis'
|
3
|
+
|
4
|
+
def redis_send_and_recv(message, key, ident)
|
5
|
+
redis = Gilmour::RedisBackend.new({})
|
6
|
+
redis.setup_subscribers({})
|
7
|
+
#loop do
|
8
|
+
waiter = Thread.new { loop { sleep 1 } }
|
9
|
+
newkey = "#{key}.#{SecureRandom.hex(2)}"
|
10
|
+
puts "Process: #{ident} Sending: #{newkey}"
|
11
|
+
redis.publish(ident, newkey) do |data, code|
|
12
|
+
puts "Process: #{ident} Received: #{data}"
|
13
|
+
waiter.kill
|
14
|
+
end
|
15
|
+
waiter.join
|
16
|
+
#end
|
17
|
+
end
|
18
|
+
|
19
|
+
def fork_and_run(num)
|
20
|
+
pid_array = []
|
21
|
+
|
22
|
+
num.times do |i|
|
23
|
+
pid = Process.fork do
|
24
|
+
puts "Process #{i}"
|
25
|
+
yield i
|
26
|
+
end
|
27
|
+
|
28
|
+
pid_array.push(pid)
|
29
|
+
end
|
30
|
+
|
31
|
+
pid_array.each do |pid|
|
32
|
+
Process.waitpid(pid)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
fork_and_run(5) do |i|
|
38
|
+
# Start echo server first
|
39
|
+
redis_send_and_recv('Ping', 'echo', i)
|
40
|
+
end
|
data/gilmour.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "./version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gilmour"
|
7
|
+
s.version = Gilmour::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Aditya Godbole"]
|
10
|
+
s.email = ["code.aa@gdbl.me"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{A Sinatra like DSL for implementing AMQP services}
|
13
|
+
s.description = %q{This gem provides a Sinatra like DSL and a simple protocol to enable writing services that communicate over AMQP}
|
14
|
+
|
15
|
+
s.add_development_dependency "rspec"
|
16
|
+
s.add_development_dependency "rspec-given"
|
17
|
+
s.add_dependency "mash"
|
18
|
+
s.add_dependency "redis"
|
19
|
+
s.add_dependency "gilmour-em-hiredis"
|
20
|
+
s.add_dependency "amqp"
|
21
|
+
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
+
s.require_paths = ["lib"]
|
26
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'socket'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
require_relative '../protocol'
|
6
|
+
|
7
|
+
module Gilmour
|
8
|
+
ErrorChannel = "gilmour.error"
|
9
|
+
|
10
|
+
# Base class for loading backends
|
11
|
+
class Backend
|
12
|
+
SUPPORTED_BACKENDS = %w(redis)
|
13
|
+
@@registry = {}
|
14
|
+
|
15
|
+
def ident
|
16
|
+
@ident
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_ident
|
20
|
+
"#{Socket.gethostname}-pid-#{Process.pid}-uuid-#{SecureRandom.uuid}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def report_errors?
|
24
|
+
#Override this method to adjust if you want errors to be reported.
|
25
|
+
return true
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(opts={})
|
29
|
+
@ident = generate_ident
|
30
|
+
end
|
31
|
+
|
32
|
+
def register_health_check
|
33
|
+
raise NotImplementedError.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def unregister_health_check
|
37
|
+
raise NotImplementedError.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.implements(backend_name)
|
41
|
+
@@registry[backend_name] = self
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.get(backend_name)
|
45
|
+
@@registry[backend_name]
|
46
|
+
end
|
47
|
+
|
48
|
+
# :nodoc:
|
49
|
+
# This should be implemented by the derived class
|
50
|
+
# subscriptions is a hash in the format -
|
51
|
+
# { topic => [handler1, handler2, ...],
|
52
|
+
# topic2 => [handler3, handler4, ...],
|
53
|
+
# ...
|
54
|
+
# }
|
55
|
+
# where handler is a hash
|
56
|
+
# { :handler => handler_proc,
|
57
|
+
# :subscriber => subscriber_derived_class
|
58
|
+
# }
|
59
|
+
def setup_subscribers(subscriptions)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sends a message
|
63
|
+
# If optional block is given, it will be executed when a response is received
|
64
|
+
# or if timeout occurs
|
65
|
+
# +message+:: The body of the message (any object that is serialisable)
|
66
|
+
# +destination+:: The channel to post to
|
67
|
+
# +opts+::
|
68
|
+
# ++timeout+:: Sender side timeout
|
69
|
+
#
|
70
|
+
def publish(message, destination, opts = {}, code = 0, &blk)
|
71
|
+
payload, sender = Gilmour::Protocol.create_request(message, code)
|
72
|
+
|
73
|
+
EM.defer do # Because publish can be called from outside the event loop
|
74
|
+
begin
|
75
|
+
send(sender, destination, payload, opts, &blk)
|
76
|
+
rescue Exception => e
|
77
|
+
GLogger.debug e.message
|
78
|
+
GLogger.debug e.backtrace
|
79
|
+
end
|
80
|
+
end
|
81
|
+
sender
|
82
|
+
end
|
83
|
+
|
84
|
+
def emit_error(message)
|
85
|
+
raise NotImplementedError.new
|
86
|
+
end
|
87
|
+
|
88
|
+
# Adds a new handler for the given _topic_
|
89
|
+
def add_listener(topic, &handler)
|
90
|
+
raise "Not implemented by child class"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Removes existing _handler_ for the _topic_
|
94
|
+
def remove_listener(topic, &handler)
|
95
|
+
raise "Not implemented by child class"
|
96
|
+
end
|
97
|
+
|
98
|
+
def acquire_ex_lock(sender)
|
99
|
+
raise "Not implemented by child class"
|
100
|
+
end
|
101
|
+
|
102
|
+
def send_response(sender, body, code)
|
103
|
+
raise "Not implemented by child class"
|
104
|
+
end
|
105
|
+
|
106
|
+
def execute_handler(topic, payload, sub)
|
107
|
+
data, sender = Gilmour::Protocol.parse_request(payload)
|
108
|
+
if sub[:exclusive]
|
109
|
+
lock_key = sender + sub[:subscriber].to_s
|
110
|
+
acquire_ex_lock(lock_key) { _execute_handler(topic, data, sender, sub) }
|
111
|
+
else
|
112
|
+
_execute_handler(topic, data, sender, sub)
|
113
|
+
end
|
114
|
+
rescue Exception => e
|
115
|
+
GLogger.debug e.message
|
116
|
+
GLogger.debug e.backtrace
|
117
|
+
end
|
118
|
+
|
119
|
+
def _execute_handler(topic, data, sender, sub)
|
120
|
+
Gilmour::Responder.new(
|
121
|
+
sender, topic, data, self, sub[:timeout], sub[:fork]
|
122
|
+
).execute(sub[:handler])
|
123
|
+
rescue Exception => e
|
124
|
+
GLogger.debug e.message
|
125
|
+
GLogger.debug e.backtrace
|
126
|
+
end
|
127
|
+
|
128
|
+
def send
|
129
|
+
raise "Not implemented by child class"
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.load_backend(name)
|
133
|
+
require_relative name
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.load_all_backends
|
137
|
+
SUPPORTED_BACKENDS.each do |f|
|
138
|
+
load_backend f
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def stop(sender, body, code)
|
143
|
+
raise "Not implemented by child class"
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
@@ -0,0 +1,254 @@
|
|
1
|
+
require 'em-hiredis'
|
2
|
+
require_relative 'backend'
|
3
|
+
require_relative '../waiter'
|
4
|
+
|
5
|
+
module Gilmour
|
6
|
+
# Redis backend implementation
|
7
|
+
class RedisBackend < Backend
|
8
|
+
GilmourHealthKey = "gilmour.known_host.health"
|
9
|
+
GilmourErrorBufferLen = 9999
|
10
|
+
|
11
|
+
implements 'redis'
|
12
|
+
|
13
|
+
attr_writer :report_errors
|
14
|
+
attr_reader :subscriber
|
15
|
+
attr_reader :publisher
|
16
|
+
|
17
|
+
def redis_host(opts)
|
18
|
+
host = opts[:host] || '127.0.0.1'
|
19
|
+
port = opts[:port] || 6379
|
20
|
+
db = opts[:db] || 0
|
21
|
+
"redis://#{host}:#{port}/#{db}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(opts)
|
25
|
+
@response_handlers = {}
|
26
|
+
@subscriptions = {}
|
27
|
+
|
28
|
+
waiter = Waiter.new
|
29
|
+
|
30
|
+
Thread.new do
|
31
|
+
EM.run do
|
32
|
+
setup_pubsub(opts)
|
33
|
+
waiter.signal
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
waiter.wait
|
38
|
+
|
39
|
+
@report_health = opts["health_check"] || opts[:health_check]
|
40
|
+
@report_health = false if @report_health != true
|
41
|
+
|
42
|
+
@report_errors = opts["broadcast_errors"] || opts[:broadcast_errors]
|
43
|
+
@report_errors = true if @report_errors != false
|
44
|
+
end
|
45
|
+
|
46
|
+
def report_health?
|
47
|
+
@report_health
|
48
|
+
end
|
49
|
+
|
50
|
+
def report_errors?
|
51
|
+
@report_errors
|
52
|
+
end
|
53
|
+
|
54
|
+
def emit_error(message)
|
55
|
+
report = self.report_errors?
|
56
|
+
|
57
|
+
if report == false
|
58
|
+
Glogger.debug "Skipping because report_errors is false"
|
59
|
+
elsif report == true
|
60
|
+
publish_error message
|
61
|
+
elsif report.is_a? String and !report.empty?
|
62
|
+
queue_error report, message
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def setup_pubsub(opts)
|
67
|
+
@publisher = EM::Hiredis.connect(redis_host(opts))
|
68
|
+
@subscriber = @publisher.pubsub_client
|
69
|
+
register_handlers
|
70
|
+
rescue Exception => e
|
71
|
+
GLogger.debug e.message
|
72
|
+
GLogger.debug e.backtrace
|
73
|
+
end
|
74
|
+
|
75
|
+
def register_handlers
|
76
|
+
@subscriber.on(:pmessage) do |key, topic, payload|
|
77
|
+
pmessage_handler(key, topic, payload)
|
78
|
+
end
|
79
|
+
@subscriber.on(:message) do |topic, payload|
|
80
|
+
begin
|
81
|
+
if topic.start_with? 'gilmour.response.'
|
82
|
+
response_handler(topic, payload)
|
83
|
+
else
|
84
|
+
pmessage_handler(topic, topic, payload)
|
85
|
+
end
|
86
|
+
rescue Exception => e
|
87
|
+
GLogger.debug e.message
|
88
|
+
GLogger.debug e.backtrace
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def subscribe_topic(topic)
|
94
|
+
method = topic.index('*') ? :psubscribe : :subscribe
|
95
|
+
@subscriber.method(method).call(topic)
|
96
|
+
end
|
97
|
+
|
98
|
+
def pmessage_handler(key, matched_topic, payload)
|
99
|
+
@subscriptions[key].each do |subscription|
|
100
|
+
EM.defer(->{execute_handler(matched_topic, payload, subscription)})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def register_response(sender, handler, timeout = 600)
|
105
|
+
topic = "gilmour.response.#{sender}"
|
106
|
+
timer = EM::Timer.new(timeout) do # Simulate error response
|
107
|
+
GLogger.info "Timeout: Killing handler for #{sender}"
|
108
|
+
payload, _ = Gilmour::Protocol.create_request({}, 499)
|
109
|
+
response_handler(topic, payload)
|
110
|
+
end
|
111
|
+
@response_handlers[topic] = {handler: handler, timer: timer}
|
112
|
+
subscribe_topic(topic)
|
113
|
+
rescue Exception => e
|
114
|
+
GLogger.debug e.message
|
115
|
+
GLogger.debug e.backtrace
|
116
|
+
end
|
117
|
+
|
118
|
+
def publish_error(messsage)
|
119
|
+
@publisher.publish(Gilmour::ErrorChannel, messsage)
|
120
|
+
end
|
121
|
+
|
122
|
+
def queue_error(key, message)
|
123
|
+
@publisher.lpush(key, message) do
|
124
|
+
@publisher.ltrim(key, 0, GilmourErrorBufferLen) do
|
125
|
+
Glogger.debug "Error queued"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def acquire_ex_lock(sender)
|
131
|
+
@publisher.set(sender, sender, 'EX', 600, 'NX') do |val|
|
132
|
+
EM.defer do
|
133
|
+
yield val if val && block_given?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def response_handler(sender, payload)
|
139
|
+
data, code, _ = Gilmour::Protocol.parse_response(payload)
|
140
|
+
handler = @response_handlers.delete(sender)
|
141
|
+
@subscriber.unsubscribe(sender)
|
142
|
+
if handler
|
143
|
+
handler[:timer].cancel
|
144
|
+
handler[:handler].call(data, code)
|
145
|
+
end
|
146
|
+
rescue Exception => e
|
147
|
+
GLogger.debug e.message
|
148
|
+
GLogger.debug e.backtrace
|
149
|
+
end
|
150
|
+
|
151
|
+
def send_response(sender, body, code)
|
152
|
+
publish(body, "gilmour.response.#{sender}", {}, code)
|
153
|
+
end
|
154
|
+
|
155
|
+
def setup_subscribers(subs = {})
|
156
|
+
@subscriptions.merge!(subs)
|
157
|
+
EM.defer do
|
158
|
+
subs.keys.each { |topic| subscribe_topic(topic) }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def add_listener(topic, &handler)
|
163
|
+
@subscriptions[topic] ||= []
|
164
|
+
@subscriptions[topic] << { handler: handler }
|
165
|
+
subscribe_topic(topic)
|
166
|
+
end
|
167
|
+
|
168
|
+
def remove_listener(topic, handler = nil)
|
169
|
+
if handler
|
170
|
+
subs = @subscriptions[topic]
|
171
|
+
subs.delete_if { |e| e[:handler] == handler }
|
172
|
+
else
|
173
|
+
@subscriptions[topic] = []
|
174
|
+
end
|
175
|
+
@subscriber.unsubscribe(topic) if @subscriptions[topic].empty?
|
176
|
+
end
|
177
|
+
|
178
|
+
def send(sender, destination, payload, opts = {}, &blk)
|
179
|
+
timeout = opts[:timeout] || 600
|
180
|
+
if opts[:confirm_subscriber]
|
181
|
+
confirm_subscriber(destination) do |present|
|
182
|
+
if !present
|
183
|
+
blk.call(nil, 404) if blk
|
184
|
+
else
|
185
|
+
_send(sender, destination, payload, timeout, &blk)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
else
|
189
|
+
_send(sender, destination, payload, timeout, &blk)
|
190
|
+
end
|
191
|
+
rescue Exception => e
|
192
|
+
GLogger.debug e.message
|
193
|
+
GLogger.debug e.backtrace
|
194
|
+
end
|
195
|
+
|
196
|
+
def _send(sender, destination, payload, timeout, &blk)
|
197
|
+
register_response(sender, blk, timeout) if block_given?
|
198
|
+
@publisher.publish(destination, payload)
|
199
|
+
sender
|
200
|
+
end
|
201
|
+
|
202
|
+
def confirm_subscriber(dest, &blk)
|
203
|
+
@publisher.pubsub('numsub', dest) do |_, num|
|
204
|
+
blk.call(num.to_i > 0)
|
205
|
+
end
|
206
|
+
rescue Exception => e
|
207
|
+
GLogger.debug e.message
|
208
|
+
GLogger.debug e.backtrace
|
209
|
+
end
|
210
|
+
|
211
|
+
def stop
|
212
|
+
@subscriber.close_connection
|
213
|
+
end
|
214
|
+
|
215
|
+
# TODO: Health checks currently use Redis to keep keys in a data structure.
|
216
|
+
# An alternate approach would be that monitor subscribes to a topic
|
217
|
+
# and records nodenames that request to be monitored. The publish method
|
218
|
+
# should fail if there is no definite health monitor listening. However,
|
219
|
+
# that would require the health node to be running at all points of time
|
220
|
+
# before a Gilmour server starts up. To circumvent this dependency, till
|
221
|
+
# monitor is stable enough, use Redis to save/share these data structures.
|
222
|
+
#
|
223
|
+
def register_health_check
|
224
|
+
@publisher.hset GilmourHealthKey, self.ident, 'active'
|
225
|
+
|
226
|
+
# - Start listening on a dyanmic topic that Health Monitor can publish
|
227
|
+
# on.
|
228
|
+
#
|
229
|
+
# NOTE: Health checks are not run as forks, to ensure that event-machine's
|
230
|
+
# ThreadPool has sufficient resources to handle new requests.
|
231
|
+
#
|
232
|
+
topic = "gilmour.health.#{self.ident}"
|
233
|
+
add_listener(topic) do
|
234
|
+
respond @subscriptions.keys
|
235
|
+
end
|
236
|
+
|
237
|
+
# TODO: Need to do these manually. Alternate is to return the handler
|
238
|
+
# hash from add_listener.
|
239
|
+
@subscriptions[topic][0][:exclusive] = true
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
def unregister_health_check
|
244
|
+
waiter = Waiter.new
|
245
|
+
|
246
|
+
@publisher.hdel(GilmourHealthKey, self.ident) do
|
247
|
+
waiter.signal
|
248
|
+
end
|
249
|
+
|
250
|
+
waiter.wait(5)
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
end
|