nsq-krakow 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +84 -0
- data/CONTRIBUTING.md +25 -0
- data/LICENSE +13 -0
- data/README.md +249 -0
- data/krakow.gemspec +22 -0
- data/lib/krakow.rb +25 -0
- data/lib/krakow/command.rb +89 -0
- data/lib/krakow/command/auth.rb +36 -0
- data/lib/krakow/command/cls.rb +24 -0
- data/lib/krakow/command/fin.rb +31 -0
- data/lib/krakow/command/identify.rb +55 -0
- data/lib/krakow/command/mpub.rb +39 -0
- data/lib/krakow/command/nop.rb +14 -0
- data/lib/krakow/command/pub.rb +37 -0
- data/lib/krakow/command/rdy.rb +31 -0
- data/lib/krakow/command/req.rb +32 -0
- data/lib/krakow/command/sub.rb +36 -0
- data/lib/krakow/command/touch.rb +31 -0
- data/lib/krakow/connection.rb +417 -0
- data/lib/krakow/connection_features.rb +10 -0
- data/lib/krakow/connection_features/deflate.rb +82 -0
- data/lib/krakow/connection_features/snappy_frames.rb +129 -0
- data/lib/krakow/connection_features/ssl.rb +75 -0
- data/lib/krakow/consumer.rb +355 -0
- data/lib/krakow/consumer/queue.rb +151 -0
- data/lib/krakow/discovery.rb +57 -0
- data/lib/krakow/distribution.rb +229 -0
- data/lib/krakow/distribution/default.rb +159 -0
- data/lib/krakow/exceptions.rb +30 -0
- data/lib/krakow/frame_type.rb +66 -0
- data/lib/krakow/frame_type/error.rb +26 -0
- data/lib/krakow/frame_type/message.rb +74 -0
- data/lib/krakow/frame_type/response.rb +26 -0
- data/lib/krakow/ksocket.rb +102 -0
- data/lib/krakow/producer.rb +162 -0
- data/lib/krakow/producer/http.rb +224 -0
- data/lib/krakow/utils.rb +9 -0
- data/lib/krakow/utils/lazy.rb +125 -0
- data/lib/krakow/utils/logging.rb +43 -0
- data/lib/krakow/version.rb +4 -0
- metadata +184 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
# Features that wrap the connection
|
5
|
+
module ConnectionFeatures
|
6
|
+
autoload :SnappyFrames, 'krakow/connection_features/snappy_frames'
|
7
|
+
autoload :Deflate, 'krakow/connection_features/deflate'
|
8
|
+
autoload :Ssl, 'krakow/connection_features/ssl'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'krakow'
|
3
|
+
|
4
|
+
module Krakow
|
5
|
+
module ConnectionFeatures
|
6
|
+
# Deflate functionality
|
7
|
+
module Deflate
|
8
|
+
# Deflatable IO
|
9
|
+
class Io
|
10
|
+
|
11
|
+
attr_reader :io, :buffer, :headers, :inflator, :deflator
|
12
|
+
|
13
|
+
# Create new deflatable IO
|
14
|
+
#
|
15
|
+
# @param io [IO] IO to wrap
|
16
|
+
# @return [Io]
|
17
|
+
def initialize(io, args={})
|
18
|
+
@io = io
|
19
|
+
@buffer = ''
|
20
|
+
@inflator = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
21
|
+
@deflator = Zlib::Deflate.new(nil, -Zlib::MAX_WBITS)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Proxy to underlying socket
|
25
|
+
#
|
26
|
+
# @param args [Object]
|
27
|
+
# @return [Object]
|
28
|
+
def method_missing(*args)
|
29
|
+
io.__send__(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Receive bytes from the IO
|
33
|
+
#
|
34
|
+
# @param n [Integer] nuber of bytes
|
35
|
+
# @return [String]
|
36
|
+
def recv(n)
|
37
|
+
until(buffer.length >= n)
|
38
|
+
read_stream
|
39
|
+
sleep(0.1) unless buffer.length >= n
|
40
|
+
end
|
41
|
+
buffer.slice!(0, n)
|
42
|
+
end
|
43
|
+
alias_method :read, :recv
|
44
|
+
|
45
|
+
# Read contents from stream
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
def read_stream
|
49
|
+
str = io.read
|
50
|
+
unless(str.empty?)
|
51
|
+
buffer << inflator.inflate(str)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Write string to IO
|
56
|
+
#
|
57
|
+
# @param string [String]
|
58
|
+
# @return [Integer] number of bytes written
|
59
|
+
def write(string)
|
60
|
+
unless(string.empty?)
|
61
|
+
output = deflator.deflate(string)
|
62
|
+
output << deflator.flush
|
63
|
+
io.write(output)
|
64
|
+
else
|
65
|
+
0
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Close the IO
|
70
|
+
#
|
71
|
+
# @return [TrueClass]
|
72
|
+
def close(*args)
|
73
|
+
super
|
74
|
+
deflator.deflate(nil, Zlib::FINISH)
|
75
|
+
deflator.close
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
begin
|
2
|
+
require 'snappy'
|
3
|
+
rescue LoadError
|
4
|
+
$stderr.puts 'ERROR: Failed to locate `snappy` gem. Install `snappy` gem into system or bundle.'
|
5
|
+
raise
|
6
|
+
end
|
7
|
+
require 'digest/crc'
|
8
|
+
require 'krakow'
|
9
|
+
|
10
|
+
module Krakow
|
11
|
+
module ConnectionFeatures
|
12
|
+
# Snappy functionality
|
13
|
+
# @todo Add support for max size + chunks
|
14
|
+
# @todo Include support for remaining types
|
15
|
+
module SnappyFrames
|
16
|
+
# Snappy-able IO
|
17
|
+
class Io
|
18
|
+
|
19
|
+
# Header identifier
|
20
|
+
IDENTIFIER = "\x73\x4e\x61\x50\x70\x59".force_encoding('ASCII-8BIT')
|
21
|
+
ident_size = [IDENTIFIER.size].pack('L<')
|
22
|
+
ident_size.slice!(-1,1)
|
23
|
+
# Size of identifier
|
24
|
+
IDENTIFIER_SIZE = ident_size
|
25
|
+
|
26
|
+
# Mapping of types
|
27
|
+
CHUNK_TYPE = {
|
28
|
+
"\xff".force_encoding('ASCII-8BIT') => :identifier,
|
29
|
+
"\x00".force_encoding('ASCII-8BIT') => :compressed,
|
30
|
+
"\x01".force_encoding('ASCII-8BIT') => :uncompressed
|
31
|
+
}
|
32
|
+
|
33
|
+
attr_reader :io, :buffer
|
34
|
+
|
35
|
+
# Create new snappy-able IO
|
36
|
+
#
|
37
|
+
# @param io [IO] IO to wrap
|
38
|
+
# @return [Io]
|
39
|
+
def initialize(io, args={})
|
40
|
+
@io = io
|
41
|
+
@snappy_write_ident = false
|
42
|
+
@buffer = ''
|
43
|
+
end
|
44
|
+
|
45
|
+
# Proxy to underlying socket
|
46
|
+
#
|
47
|
+
# @param args [Object]
|
48
|
+
# @return [Object]
|
49
|
+
def method_missing(*args)
|
50
|
+
io.__send__(*args)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Mask the checksum
|
54
|
+
#
|
55
|
+
# @param checksum [String]
|
56
|
+
# @return [String]
|
57
|
+
def checksum_mask(checksum)
|
58
|
+
(((checksum >> 15) | (checksum << 17)) + 0xa282ead8) & 0xffffffff
|
59
|
+
end
|
60
|
+
|
61
|
+
# Receive bytes from the IO
|
62
|
+
#
|
63
|
+
# @param n [Integer] nuber of bytes
|
64
|
+
# @return [String]
|
65
|
+
def recv(n)
|
66
|
+
read_stream unless buffer.size >= n
|
67
|
+
result = buffer.slice!(0,n)
|
68
|
+
result.empty? ? nil : result
|
69
|
+
end
|
70
|
+
alias_method :read, :recv
|
71
|
+
|
72
|
+
# Read contents from stream
|
73
|
+
#
|
74
|
+
# @return [String]
|
75
|
+
def read_stream
|
76
|
+
header = io.recv(4)
|
77
|
+
ident = CHUNK_TYPE[header.slice!(0)]
|
78
|
+
size = (header << CHUNK_TYPE.key(:compressed)).unpack('L<').first
|
79
|
+
content = io.recv(size)
|
80
|
+
case ident
|
81
|
+
when :identifier
|
82
|
+
unless(content == IDENTIFIER)
|
83
|
+
raise "Invalid stream identification encountered (content: #{content.inspect})"
|
84
|
+
end
|
85
|
+
read_stream
|
86
|
+
when :compressed
|
87
|
+
checksum = content.slice!(0, 4).unpack('L<').first
|
88
|
+
deflated = Snappy.inflate(content)
|
89
|
+
digest = Digest::CRC32c.new
|
90
|
+
digest << deflated
|
91
|
+
unless(checksum == checksum_mask(digest.checksum))
|
92
|
+
raise 'Checksum mismatch!'
|
93
|
+
end
|
94
|
+
buffer << deflated
|
95
|
+
when :uncompressed
|
96
|
+
buffer << content
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Write string to IO
|
101
|
+
#
|
102
|
+
# @param string [String]
|
103
|
+
# @return [Integer] number of bytes written
|
104
|
+
def write(string)
|
105
|
+
unless(@snappy_writer_ident)
|
106
|
+
send_snappy_identifier
|
107
|
+
end
|
108
|
+
digest = Digest::CRC32c.new
|
109
|
+
digest << string
|
110
|
+
content = Snappy.deflate(string)
|
111
|
+
size = content.length + 4
|
112
|
+
size = [size].pack('L<')
|
113
|
+
size.slice!(-1,1)
|
114
|
+
checksum = [checksum_mask(digest.checksum)].pack('L<')
|
115
|
+
output = [CHUNK_TYPE.key(:compressed), size, checksum, content].pack('a*a*a*a*')
|
116
|
+
io.write output
|
117
|
+
end
|
118
|
+
|
119
|
+
# Send the identifier for snappy content
|
120
|
+
#
|
121
|
+
# @return [Integer] bytes written
|
122
|
+
def send_snappy_identifier
|
123
|
+
io.write [CHUNK_TYPE.key(:identifier), IDENTIFIER_SIZE, IDENTIFIER].pack('a*a*a*')
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'krakow'
|
3
|
+
|
4
|
+
module Krakow
|
5
|
+
module ConnectionFeatures
|
6
|
+
# SSL functionality
|
7
|
+
module Ssl
|
8
|
+
# SSL-able IO
|
9
|
+
class Io
|
10
|
+
|
11
|
+
attr_reader :_socket
|
12
|
+
|
13
|
+
# Create new SSL-able IO
|
14
|
+
#
|
15
|
+
# @param io [IO] IO to wrap
|
16
|
+
# @param args [Hash]
|
17
|
+
# @option args [Hash] :ssl_context
|
18
|
+
# @return [Io]
|
19
|
+
def initialize(io, args={})
|
20
|
+
ssl_socket_arguments = [io]
|
21
|
+
if(args[:ssl_context])
|
22
|
+
validate_ssl_args!(args[:ssl_context])
|
23
|
+
context = OpenSSL::SSL::SSLContext.new
|
24
|
+
context.cert = OpenSSL::X509::Certificate.new(File.open(args[:ssl_context][:certificate]))
|
25
|
+
context.key = OpenSSL::PKey::RSA.new(File.open(args[:ssl_context][:key]))
|
26
|
+
ssl_socket_arguments << context
|
27
|
+
end
|
28
|
+
@_socket = Celluloid::IO::SSLSocket.new(*ssl_socket_arguments)
|
29
|
+
_socket.sync = true
|
30
|
+
_socket.connect
|
31
|
+
end
|
32
|
+
|
33
|
+
# Proxy to underlying socket
|
34
|
+
#
|
35
|
+
# @param args [Object]
|
36
|
+
# @return [Object]
|
37
|
+
def method_missing(*args)
|
38
|
+
_socket.send(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Receive bytes from the IO
|
42
|
+
#
|
43
|
+
# @param len [Integer] nuber of bytes
|
44
|
+
# @return [String]
|
45
|
+
def recv(len)
|
46
|
+
str = readpartial(len)
|
47
|
+
if(len > str.length)
|
48
|
+
str << sysread(len - str.length)
|
49
|
+
end
|
50
|
+
str
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Validate the SSL configuration provided
|
56
|
+
#
|
57
|
+
# @param args [Hash]
|
58
|
+
# @option args [String] :certificate path to certificate
|
59
|
+
# @option args [String] :key path to key
|
60
|
+
# @raise [ArgumentError, LoadError]
|
61
|
+
def validate_ssl_args!(args)
|
62
|
+
[:key, :certificate].each do |arg_key|
|
63
|
+
unless(args.has_key?(arg_key))
|
64
|
+
raise ArgumentError.new "The `:ssl_context` option requires `#{arg_key.inspect}` to be set"
|
65
|
+
end
|
66
|
+
unless(File.readable?(args[arg_key]))
|
67
|
+
raise LoadError.new "Unable to read the `#{arg_key.inspect}` file from the `:ssl_context` arguments"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,355 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
# Consume messages from a server
|
5
|
+
class Consumer
|
6
|
+
|
7
|
+
autoload :Queue, 'krakow/consumer/queue'
|
8
|
+
|
9
|
+
include Utils::Lazy
|
10
|
+
# @!parse include Krakow::Utils::Lazy::InstanceMethods
|
11
|
+
# @!parse extend Krakow::Utils::Lazy::ClassMethods
|
12
|
+
|
13
|
+
include Celluloid
|
14
|
+
|
15
|
+
trap_exit :connection_failure
|
16
|
+
finalizer :consumer_cleanup
|
17
|
+
|
18
|
+
attr_reader :connections, :discovery, :distribution, :queue
|
19
|
+
|
20
|
+
# @!group Attributes
|
21
|
+
|
22
|
+
# @!macro [attach] attribute
|
23
|
+
# @!method $1
|
24
|
+
# @return [$2] the $1 $0
|
25
|
+
# @!method $1?
|
26
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
27
|
+
attribute :topic, String, :required => true
|
28
|
+
attribute :channel, String, :required => true
|
29
|
+
attribute :host, String
|
30
|
+
attribute :port, [String, Integer]
|
31
|
+
attribute :nsqlookupd, [Array, String]
|
32
|
+
attribute :max_in_flight, Integer, :default => 1
|
33
|
+
attribute :backoff_interval, Numeric
|
34
|
+
attribute :discovery_interval, Numeric, :default => 30
|
35
|
+
attribute :discovery_jitter, Numeric, :default => 10.0
|
36
|
+
attribute :notifier, [Celluloid::Signals, Celluloid::Condition, Celluloid::Actor]
|
37
|
+
attribute :connection_options, Hash, :default => ->{ Hash.new }
|
38
|
+
|
39
|
+
# @!endgroup
|
40
|
+
|
41
|
+
def initialize(args={})
|
42
|
+
super
|
43
|
+
arguments[:connection_options] = {:features => {}, :config => {}}.merge(
|
44
|
+
arguments[:connection_options] || {}
|
45
|
+
)
|
46
|
+
@connections = {}
|
47
|
+
@queue = Queue.new(
|
48
|
+
current_actor,
|
49
|
+
:removal_callback => :remove_message
|
50
|
+
)
|
51
|
+
@distribution = Distribution::Default.new(
|
52
|
+
:max_in_flight => max_in_flight,
|
53
|
+
:backoff_interval => backoff_interval,
|
54
|
+
:consumer => current_actor
|
55
|
+
)
|
56
|
+
if(nsqlookupd)
|
57
|
+
debug "Connections will be established via lookup #{nsqlookupd.inspect}"
|
58
|
+
@discovery = Discovery.new(:nsqlookupd => nsqlookupd)
|
59
|
+
discover
|
60
|
+
elsif(host && port)
|
61
|
+
direct_connect
|
62
|
+
else
|
63
|
+
abort Error::ConfigurationError.new('No connection information provided!')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [TrueClass, FalseClass] currently connected to at least
|
68
|
+
# one nsqd
|
69
|
+
def connected?
|
70
|
+
!!connections.values.any? do |con|
|
71
|
+
begin
|
72
|
+
con.connected?
|
73
|
+
rescue Celluloid::DeadActorError
|
74
|
+
false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Connect to nsqd instance directly
|
80
|
+
#
|
81
|
+
# @return [Connection]
|
82
|
+
def direct_connect
|
83
|
+
debug "Connection will be established via direct connection #{host}:#{port}"
|
84
|
+
connection = build_connection(host, port, queue)
|
85
|
+
if(register(connection))
|
86
|
+
info "Registered new connection #{connection}"
|
87
|
+
distribution.redistribute!
|
88
|
+
else
|
89
|
+
abort Error::ConnectionFailure.new("Failed to establish subscription at provided end point (#{host}:#{port}")
|
90
|
+
end
|
91
|
+
connection
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns [Krakow::Connection] associated to key
|
95
|
+
#
|
96
|
+
# @param key [Object] identifier
|
97
|
+
# @return [Krakow::Connection] associated connection
|
98
|
+
def connection(key)
|
99
|
+
@connections[key]
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [String] stringify object
|
103
|
+
def to_s
|
104
|
+
"<#{self.class.name}:#{object_id} T:#{topic} C:#{channel}>"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Instance destructor
|
108
|
+
#
|
109
|
+
# @return [nil]
|
110
|
+
def consumer_cleanup
|
111
|
+
debug 'Tearing down consumer'
|
112
|
+
if(distribution && distribution.alive?)
|
113
|
+
distribution.terminate
|
114
|
+
end
|
115
|
+
if(queue && queue.alive?)
|
116
|
+
queue.terminate
|
117
|
+
end
|
118
|
+
connections.values.each do |con|
|
119
|
+
con.terminate if con.alive?
|
120
|
+
end
|
121
|
+
info 'Consumer torn down'
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
# Build a new [Krakow::Connection]
|
126
|
+
#
|
127
|
+
# @param host [String] remote host
|
128
|
+
# @param port [String, Integer] remote port
|
129
|
+
# @param queue [Queue] queue for messages
|
130
|
+
# @return [Krakow::Connection, nil] new connection or nil
|
131
|
+
def build_connection(host, port, queue)
|
132
|
+
begin
|
133
|
+
connection = Connection.new(
|
134
|
+
:host => host,
|
135
|
+
:port => port,
|
136
|
+
:queue => queue,
|
137
|
+
:topic => topic,
|
138
|
+
:channel => channel,
|
139
|
+
:notifier => notifier,
|
140
|
+
:features => connection_options[:features],
|
141
|
+
:features_args => connection_options[:config],
|
142
|
+
:callbacks => {
|
143
|
+
:handle => {
|
144
|
+
:actor => current_actor,
|
145
|
+
:method => :process_message
|
146
|
+
}
|
147
|
+
}
|
148
|
+
)
|
149
|
+
queue.register_connection(connection)
|
150
|
+
connection
|
151
|
+
rescue => e
|
152
|
+
error "Failed to build connection (host: #{host} port: #{port} queue: #{queue}) - #{e.class}: #{e}"
|
153
|
+
debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Process a given message if required
|
159
|
+
#
|
160
|
+
# @param message [Krakow::FrameType]
|
161
|
+
# @param connection [Krakow::Connection]
|
162
|
+
# @return [Krakow::FrameType]
|
163
|
+
# @note If we receive a message that is already in flight, attempt
|
164
|
+
# to scrub message from wait queue. If message is found, retry
|
165
|
+
# distribution registration. If message is not found, assume it
|
166
|
+
# is currently being processed and do not allow new message to
|
167
|
+
# be queued
|
168
|
+
def process_message(message, connection)
|
169
|
+
discard = false
|
170
|
+
if(message.is_a?(FrameType::Message))
|
171
|
+
message.origin = current_actor
|
172
|
+
message.connection = connection
|
173
|
+
retried = false
|
174
|
+
begin
|
175
|
+
distribution.register_message(message, connection.identifier)
|
176
|
+
rescue KeyError => e
|
177
|
+
if(!retried && queue.scrub_duplicate_message(message))
|
178
|
+
retried = true
|
179
|
+
retry
|
180
|
+
else
|
181
|
+
error "Received message is currently in flight and not in wait queue. Discarding! (#{message})"
|
182
|
+
discard = true
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
discard ? nil : message
|
187
|
+
end
|
188
|
+
|
189
|
+
# Send RDY for connection based on distribution rules
|
190
|
+
#
|
191
|
+
# @param connection [Krakow::Connection]
|
192
|
+
# @return [nil]
|
193
|
+
def update_ready!(connection)
|
194
|
+
distribution.set_ready_for(connection)
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
|
198
|
+
# Initialize the consumer by starting lookup and adding connections
|
199
|
+
#
|
200
|
+
# @return [nil]
|
201
|
+
def init!
|
202
|
+
debug 'Running consumer `init!` connection builds'
|
203
|
+
found = discovery.lookup(topic)
|
204
|
+
debug "Discovery results: #{found.inspect}"
|
205
|
+
connection = nil
|
206
|
+
found.each do |node|
|
207
|
+
debug "Processing discovery result: #{node.inspect}"
|
208
|
+
key = Connection.identifier(node[:broadcast_address], node[:tcp_port], topic, channel)
|
209
|
+
unless(connections[key])
|
210
|
+
connection = build_connection(node[:broadcast_address], node[:tcp_port], queue)
|
211
|
+
info "Registered new connection #{connection}" if register(connection)
|
212
|
+
else
|
213
|
+
debug "Discovery result already registered: #{node.inspect}"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
distribution.redistribute! if connection
|
217
|
+
nil
|
218
|
+
end
|
219
|
+
|
220
|
+
# Start the discovery interval lookup
|
221
|
+
#
|
222
|
+
# @return [nil]
|
223
|
+
def discover
|
224
|
+
init!
|
225
|
+
after(discovery_interval + (discovery_jitter * rand)){ discover }
|
226
|
+
end
|
227
|
+
|
228
|
+
# Register connection with distribution
|
229
|
+
#
|
230
|
+
# @param connection [Krakow::Connection]
|
231
|
+
# @return [TrueClass, FalseClass] true if subscription was successful
|
232
|
+
def register(connection)
|
233
|
+
begin
|
234
|
+
connection.init!
|
235
|
+
connection.transmit(Command::Sub.new(:topic_name => topic, :channel_name => channel))
|
236
|
+
self.link connection
|
237
|
+
connections[connection.identifier] = connection
|
238
|
+
distribution.add_connection(connection)
|
239
|
+
true
|
240
|
+
rescue Error::BadResponse => e
|
241
|
+
debug "Failed to establish connection: #{e.result ? e.result.error : '<No Response!>'}"
|
242
|
+
connection.terminate
|
243
|
+
false
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Remove connection references when connection is terminated
|
248
|
+
#
|
249
|
+
# @param actor [Object] terminated actor
|
250
|
+
# @param reason [Exception] reason for termination
|
251
|
+
# @return [nil]
|
252
|
+
def connection_failure(actor, reason)
|
253
|
+
if(reason && key = connections.key(actor))
|
254
|
+
warn "Connection failure detected. Removing connection: #{key} - #{reason}"
|
255
|
+
connections.delete(key)
|
256
|
+
begin
|
257
|
+
distribution.remove_connection(key)
|
258
|
+
rescue Error::ConnectionUnavailable, Error::ConnectionFailure
|
259
|
+
warn 'Caught connection unavailability'
|
260
|
+
end
|
261
|
+
queue.deregister_connection(key)
|
262
|
+
distribution.redistribute!
|
263
|
+
direct_connect unless discovery
|
264
|
+
end
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
|
268
|
+
# Remove message
|
269
|
+
#
|
270
|
+
# @param messages [Array<FrameType::Message>]
|
271
|
+
# @return [NilClass]
|
272
|
+
# @note used mainly for queue callback
|
273
|
+
def remove_message(messages)
|
274
|
+
[messages].flatten.compact.each do |msg|
|
275
|
+
distribution.unregister_message(msg.message_id)
|
276
|
+
update_ready!(msg.connection)
|
277
|
+
end
|
278
|
+
nil
|
279
|
+
end
|
280
|
+
|
281
|
+
# Confirm message has been processed
|
282
|
+
#
|
283
|
+
# @param message_id [String, Krakow::FrameType::Message]
|
284
|
+
# @return [TrueClass]
|
285
|
+
# @raise [KeyError] connection not found
|
286
|
+
def confirm(message_id)
|
287
|
+
message_id = message_id.message_id if message_id.respond_to?(:message_id)
|
288
|
+
begin
|
289
|
+
begin
|
290
|
+
connection = distribution.in_flight_lookup(message_id)
|
291
|
+
connection.transmit(Command::Fin.new(:message_id => message_id))
|
292
|
+
distribution.success(connection.identifier)
|
293
|
+
rescue => e
|
294
|
+
abort e
|
295
|
+
end
|
296
|
+
true
|
297
|
+
rescue KeyError => e
|
298
|
+
error "Message confirmation failed: #{e}"
|
299
|
+
abort e
|
300
|
+
rescue Error::LookupFailed => e
|
301
|
+
error "Lookup of message for confirmation failed! <Message ID: #{message_id} - Error: #{e}>"
|
302
|
+
abort e
|
303
|
+
rescue Error::ConnectionUnavailable => e
|
304
|
+
abort e
|
305
|
+
rescue Celluloid::DeadActorError
|
306
|
+
abort Error::ConnectionUnavailable.new
|
307
|
+
ensure
|
308
|
+
con = distribution.unregister_message(message_id)
|
309
|
+
update_ready!(con) if con
|
310
|
+
end
|
311
|
+
end
|
312
|
+
alias_method :finish, :confirm
|
313
|
+
|
314
|
+
# Requeue message (generally due to processing failure)
|
315
|
+
#
|
316
|
+
# @param message_id [String, Krakow::FrameType::Message]
|
317
|
+
# @param timeout [Numeric]
|
318
|
+
# @return [TrueClass]
|
319
|
+
def requeue(message_id, timeout=0)
|
320
|
+
message_id = message_id.message_id if message_id.respond_to?(:message_id)
|
321
|
+
distribution.in_flight_lookup(message_id) do |connection|
|
322
|
+
distribution.unregister_message(message_id)
|
323
|
+
connection.transmit(
|
324
|
+
Command::Req.new(
|
325
|
+
:message_id => message_id,
|
326
|
+
:timeout => timeout
|
327
|
+
)
|
328
|
+
)
|
329
|
+
distribution.failure(connection.identifier)
|
330
|
+
update_ready!(connection)
|
331
|
+
end
|
332
|
+
true
|
333
|
+
end
|
334
|
+
|
335
|
+
# Touch message (to extend timeout)
|
336
|
+
#
|
337
|
+
# @param message_id [String, Krakow::FrameType::Message]
|
338
|
+
# @return [TrueClass]
|
339
|
+
def touch(message_id)
|
340
|
+
message_id = message_id.message_id if message_id.respond_to?(:message_id)
|
341
|
+
begin
|
342
|
+
distribution.in_flight_lookup(message_id) do |connection|
|
343
|
+
connection.transmit(
|
344
|
+
Command::Touch.new(:message_id => message_id)
|
345
|
+
)
|
346
|
+
end
|
347
|
+
true
|
348
|
+
rescue Error::LookupFailed => e
|
349
|
+
error "Lookup of message for touch failed! <Message ID: #{message_id} - Error: #{e}>"
|
350
|
+
abort e
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
end
|