pulsar_sdk 0.8.8
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 +51 -0
- data/Gemfile +6 -0
- data/LICENSE +201 -0
- data/README.md +107 -0
- data/Rakefile +2 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/protobuf/pulsar_api.pb.rb +710 -0
- data/lib/protobuf/pulsar_api.proto +934 -0
- data/lib/protobuf/validate.rb +41 -0
- data/lib/pulsar_admin.rb +14 -0
- data/lib/pulsar_admin/api.rb +215 -0
- data/lib/pulsar_sdk.rb +55 -0
- data/lib/pulsar_sdk/client.rb +13 -0
- data/lib/pulsar_sdk/client/connection.rb +371 -0
- data/lib/pulsar_sdk/client/connection_pool.rb +79 -0
- data/lib/pulsar_sdk/client/rpc.rb +67 -0
- data/lib/pulsar_sdk/consumer.rb +13 -0
- data/lib/pulsar_sdk/consumer/base.rb +148 -0
- data/lib/pulsar_sdk/consumer/manager.rb +127 -0
- data/lib/pulsar_sdk/consumer/message_tracker.rb +86 -0
- data/lib/pulsar_sdk/options.rb +6 -0
- data/lib/pulsar_sdk/options/base.rb +10 -0
- data/lib/pulsar_sdk/options/connection.rb +51 -0
- data/lib/pulsar_sdk/options/consumer.rb +34 -0
- data/lib/pulsar_sdk/options/producer.rb +14 -0
- data/lib/pulsar_sdk/options/reader.rb +7 -0
- data/lib/pulsar_sdk/options/tls.rb +8 -0
- data/lib/pulsar_sdk/producer.rb +14 -0
- data/lib/pulsar_sdk/producer/base.rb +154 -0
- data/lib/pulsar_sdk/producer/manager.rb +67 -0
- data/lib/pulsar_sdk/producer/message.rb +47 -0
- data/lib/pulsar_sdk/producer/router.rb +100 -0
- data/lib/pulsar_sdk/protocol.rb +8 -0
- data/lib/pulsar_sdk/protocol/frame.rb +53 -0
- data/lib/pulsar_sdk/protocol/lookup.rb +55 -0
- data/lib/pulsar_sdk/protocol/message.rb +55 -0
- data/lib/pulsar_sdk/protocol/namespace.rb +22 -0
- data/lib/pulsar_sdk/protocol/partitioned.rb +54 -0
- data/lib/pulsar_sdk/protocol/reader.rb +67 -0
- data/lib/pulsar_sdk/protocol/structure.rb +93 -0
- data/lib/pulsar_sdk/protocol/topic.rb +74 -0
- data/lib/pulsar_sdk/tweaks.rb +10 -0
- data/lib/pulsar_sdk/tweaks/assign_attributes.rb +30 -0
- data/lib/pulsar_sdk/tweaks/base_command.rb +66 -0
- data/lib/pulsar_sdk/tweaks/binary_heap.rb +133 -0
- data/lib/pulsar_sdk/tweaks/clean_inspect.rb +15 -0
- data/lib/pulsar_sdk/tweaks/time_at_microsecond.rb +27 -0
- data/lib/pulsar_sdk/tweaks/timeout_queue.rb +52 -0
- data/lib/pulsar_sdk/tweaks/wait_map.rb +81 -0
- data/lib/pulsar_sdk/version.rb +3 -0
- data/pulsar_sdk.gemspec +31 -0
- metadata +151 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'google/protobuf'
|
2
|
+
|
3
|
+
module Google
|
4
|
+
module Protobuf
|
5
|
+
module MessageExts
|
6
|
+
alias_method :orig_to_json, :to_json
|
7
|
+
alias_method :orig_to_proto, :to_proto
|
8
|
+
|
9
|
+
def to_json(options = {})
|
10
|
+
validate_presence!
|
11
|
+
orig_to_json(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_proto
|
15
|
+
validate_presence!
|
16
|
+
orig_to_proto
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate_presence!
|
20
|
+
self.class.descriptor.entries.each do |entry|
|
21
|
+
next if entry.label != :required
|
22
|
+
|
23
|
+
v = self[entry.name]
|
24
|
+
|
25
|
+
validate_fail = case entry.type
|
26
|
+
when :int64, :uint64, :int32, :uint32, :double, :float
|
27
|
+
v.nil? || v == 0
|
28
|
+
when :string
|
29
|
+
v.nil? || v.empty?
|
30
|
+
when :enum
|
31
|
+
v.nil? || !entry.subtype.entries.map(&:first).include?(v)
|
32
|
+
else
|
33
|
+
v.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
raise "#{self.class.name}::#{entry.name} was required, but got 「#{v.inspect}」" if validate_fail
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/pulsar_admin.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
module PulsarAdmin
|
2
|
+
class Api
|
3
|
+
PublishTimeHeader = /^X-Pulsar-Publish-Time$/i
|
4
|
+
BatchHeader = /^X-Pulsar-Num-Batch-Message$/i
|
5
|
+
PropertyPrefix = /^X-Pulsar-PROPERTY-/i
|
6
|
+
|
7
|
+
# opts
|
8
|
+
# endpoint
|
9
|
+
# tenant
|
10
|
+
# persistent
|
11
|
+
def initialize(opts)
|
12
|
+
@endpoint = URI.parse(opts[:endpoint])
|
13
|
+
@tenant = opts[:tenant]
|
14
|
+
@persistent = opts[:persistent] == false ? 'non-persistent' : 'persistent'
|
15
|
+
end
|
16
|
+
|
17
|
+
def list_namespaces
|
18
|
+
get('/admin/v2/namespaces/:tenant').map do |ns|
|
19
|
+
ns.sub("#{@tenant}/", '')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_namespace(name)
|
24
|
+
put('/admin/v2/namespaces/:tenant/:namespace', namespace: name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_namespace(name)
|
28
|
+
delete('/admin/v2/namespaces/:tenant/:namespace', namespace: name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def namespace_topics(namespace)
|
32
|
+
result = {}
|
33
|
+
['', '/partitioned'].flat_map do |pd|
|
34
|
+
resp = get("/admin/v2/:persistent/:tenant/:namespace#{pd}", namespace: namespace)
|
35
|
+
result[pd.empty? ? 'non-partitioned' : 'partitioned'] = resp
|
36
|
+
end
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_topic(namespace, topic, partitions = 0)
|
41
|
+
put("/admin/v2/:persistent/:tenant/:namespace/:topic#{partitions.zero? ? '' : '/partitions'}",
|
42
|
+
{namespace: namespace, topic: topic}, partitions
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_topic(namespace, topic)
|
47
|
+
res1 = delete('/admin/v2/:persistent/:tenant/:namespace/:topic',
|
48
|
+
namespace: namespace, topic: topic
|
49
|
+
)
|
50
|
+
|
51
|
+
res2 = delete('/admin/v2/:persistent/:tenant/:namespace/:topic/partitions',
|
52
|
+
namespace: namespace, topic: topic
|
53
|
+
)
|
54
|
+
|
55
|
+
res1 || res2
|
56
|
+
end
|
57
|
+
|
58
|
+
# options
|
59
|
+
# namespace
|
60
|
+
# topic
|
61
|
+
# sub_name
|
62
|
+
# message_position
|
63
|
+
# count
|
64
|
+
def peek_messages(options)
|
65
|
+
(options[:count] || 1).times.map do |x|
|
66
|
+
opts = options.dup
|
67
|
+
opts[:message_position] = (opts[:message_position].to_i + x + 1).to_s
|
68
|
+
peek_message(opts)
|
69
|
+
end.compact
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def put(path, payload = {}, body = nil)
|
74
|
+
uri = @endpoint.dup
|
75
|
+
uri.path, payload = handle_restful_path(path, payload)
|
76
|
+
|
77
|
+
req = Net::HTTP::Put.new(uri)
|
78
|
+
|
79
|
+
if payload.empty?
|
80
|
+
req.body = body.to_s
|
81
|
+
req.content_type = body.nil? ? 'application/json' : 'text/plain'
|
82
|
+
else
|
83
|
+
req.body = payload.to_json
|
84
|
+
req.content_type = 'application/json'
|
85
|
+
end
|
86
|
+
|
87
|
+
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
|
88
|
+
http.request(req)
|
89
|
+
end
|
90
|
+
|
91
|
+
case res
|
92
|
+
when Net::HTTPSuccess, Net::HTTPNoContent
|
93
|
+
return true
|
94
|
+
else
|
95
|
+
puts "status: #{res.code} - body: #{res.body} - #{res.inspect}"
|
96
|
+
return false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def raw_get(path, params = {})
|
101
|
+
uri = @endpoint.dup
|
102
|
+
uri.path, params = handle_restful_path(path, params)
|
103
|
+
|
104
|
+
req = Net::HTTP::Get.new(uri)
|
105
|
+
req.set_form_data(params) unless params.empty?
|
106
|
+
|
107
|
+
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
108
|
+
http.request(req)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def get(path, params = {})
|
113
|
+
resp = raw_get(path, params)
|
114
|
+
try_decode_body(resp)
|
115
|
+
end
|
116
|
+
|
117
|
+
def delete(path, payload = {})
|
118
|
+
uri = @endpoint.dup
|
119
|
+
uri.path, payload = handle_restful_path(path, payload)
|
120
|
+
|
121
|
+
req = Net::HTTP::Delete.new(uri)
|
122
|
+
req.body = payload.to_json
|
123
|
+
req.content_type = 'application/json'
|
124
|
+
|
125
|
+
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
|
126
|
+
http.request(req)
|
127
|
+
end
|
128
|
+
|
129
|
+
case res
|
130
|
+
when Net::HTTPSuccess, Net::HTTPNoContent
|
131
|
+
return true
|
132
|
+
else
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# options
|
138
|
+
# namespace
|
139
|
+
# topic
|
140
|
+
# sub_name
|
141
|
+
# message_position
|
142
|
+
def peek_message(options)
|
143
|
+
options[:message_position] = options[:message_position].to_s
|
144
|
+
resp = raw_get('/admin/v2/:persistent/:tenant/:namespace/:topic/subscription/:sub_name/position/:message_position', options)
|
145
|
+
unless request_ok?(resp)
|
146
|
+
puts resp.body
|
147
|
+
return
|
148
|
+
end
|
149
|
+
|
150
|
+
payload = resp.body
|
151
|
+
|
152
|
+
msg_id = nil
|
153
|
+
properties = {}
|
154
|
+
resp.each_header do |header|
|
155
|
+
case header
|
156
|
+
when PublishTimeHeader
|
157
|
+
properties['publish-time'] = resp.header[header]
|
158
|
+
when BatchHeader
|
159
|
+
properties['pulsar-num-batch-message'] = resp.header[header]
|
160
|
+
when PropertyPrefix
|
161
|
+
properties[header] = resp.header[header]
|
162
|
+
when /^X-Pulsar-Message-ID$/i
|
163
|
+
msg_id = resp.header[header]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
[
|
167
|
+
msg_id,
|
168
|
+
properties,
|
169
|
+
payload
|
170
|
+
]
|
171
|
+
end
|
172
|
+
|
173
|
+
def handle_restful_path(path, options)
|
174
|
+
return path unless path.include?(':')
|
175
|
+
|
176
|
+
opts = combine_default_value(options || {})
|
177
|
+
opts.keys.sort.reverse.each do |k|
|
178
|
+
remark = ":#{k}"
|
179
|
+
next unless path.include?(remark)
|
180
|
+
path.gsub!(remark, opts[k])
|
181
|
+
options.delete(k)
|
182
|
+
end
|
183
|
+
|
184
|
+
return [path, options]
|
185
|
+
end
|
186
|
+
|
187
|
+
def combine_default_value(opts)
|
188
|
+
opts.merge(
|
189
|
+
tenant: @tenant,
|
190
|
+
persistent: @persistent
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
def request_ok?(resp)
|
195
|
+
case resp
|
196
|
+
when Net::HTTPSuccess
|
197
|
+
true
|
198
|
+
when Net::HTTPRedirection
|
199
|
+
false
|
200
|
+
else
|
201
|
+
false
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def try_decode_body(resp)
|
206
|
+
unless request_ok?(resp)
|
207
|
+
puts resp.body
|
208
|
+
return
|
209
|
+
end
|
210
|
+
return resp.body unless resp.content_type =~ /application\/json/
|
211
|
+
|
212
|
+
JSON.parse(resp.body) rescue resp.body
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
data/lib/pulsar_sdk.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'protobuf/validate'
|
5
|
+
require 'protobuf/pulsar_api.pb'
|
6
|
+
require "pulsar_sdk/version"
|
7
|
+
require 'pulsar_sdk/tweaks'
|
8
|
+
require 'pulsar_sdk/protocol'
|
9
|
+
require 'pulsar_sdk/options'
|
10
|
+
require 'pulsar_sdk/consumer'
|
11
|
+
require 'pulsar_sdk/producer'
|
12
|
+
require 'pulsar_sdk/client'
|
13
|
+
|
14
|
+
module PulsarSdk
|
15
|
+
extend self
|
16
|
+
|
17
|
+
# options Hash see PulsarSdk::Options::Connection for detail
|
18
|
+
def create_client(options)
|
19
|
+
opts = ::PulsarSdk::Options::Connection.new(options)
|
20
|
+
::PulsarSdk::Client.create(opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
# options Hash see PulsarSdk::Options::Producer for detail
|
24
|
+
def create_producer(client, options)
|
25
|
+
opts = ::PulsarSdk::Options::Producer.new(options)
|
26
|
+
client.create_producer(opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
# options Hash see PulsarSdk::Options::Consumer for detail
|
30
|
+
def create_consumer(client, options)
|
31
|
+
opts = ::PulsarSdk::Options::Consumer.new(options)
|
32
|
+
client.subscribe(opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
def logger
|
36
|
+
@logger ||= Logger.new(STDOUT).tap do |logger|
|
37
|
+
logger.formatter = Formatter.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def logger=(v)
|
42
|
+
@logger = v
|
43
|
+
end
|
44
|
+
|
45
|
+
class Formatter < ::Logger::Formatter
|
46
|
+
def call(severity, timestamp, progname, msg)
|
47
|
+
case msg
|
48
|
+
when ::StandardError
|
49
|
+
msg = [msg.message, msg&.backtrace].join(":\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,371 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module PulsarSdk
|
4
|
+
module Client
|
5
|
+
class Connection
|
6
|
+
prepend ::PulsarSdk::Tweaks::CleanInspect
|
7
|
+
|
8
|
+
CLIENT_NAME = "pulsar-client-#{PulsarSdk::VERSION}".freeze
|
9
|
+
PROTOCOL_VER = Pulsar::Proto::ProtocolVersion::V13
|
10
|
+
|
11
|
+
attr_reader :consumer_handlers
|
12
|
+
attr_reader :producer_handlers
|
13
|
+
attr_reader :response_container # 用于处理状态回调
|
14
|
+
attr_reader :seq_generator
|
15
|
+
|
16
|
+
# opts PulsarSdk::Options::Connection
|
17
|
+
def initialize(opts)
|
18
|
+
@conn_options = opts
|
19
|
+
|
20
|
+
@socket = nil
|
21
|
+
@state = Status.new
|
22
|
+
|
23
|
+
@seq_generator = SeqGenerator.new
|
24
|
+
|
25
|
+
@consumer_handlers = ConsumerHandler.new
|
26
|
+
@producer_handlers = ProducerHandler.new
|
27
|
+
@response_container = ResponseContainer.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def start
|
31
|
+
unless connect && do_hand_shake && listen
|
32
|
+
@state.closed!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.establish(opts)
|
37
|
+
conn = new(opts).tap do |c|
|
38
|
+
c.start
|
39
|
+
end
|
40
|
+
# TODO check connection ready
|
41
|
+
conn
|
42
|
+
end
|
43
|
+
|
44
|
+
def close
|
45
|
+
@state.closed!
|
46
|
+
consumer_handlers.each{|_k, v| v.call}
|
47
|
+
producer_handlers.each{|_k, v| v.call}
|
48
|
+
Timeout::timeout(2) {@pong&.join} rescue @pong&.kill
|
49
|
+
@pong&.join
|
50
|
+
ensure
|
51
|
+
@socket.close
|
52
|
+
end
|
53
|
+
|
54
|
+
def closed?
|
55
|
+
@state.closed?
|
56
|
+
end
|
57
|
+
|
58
|
+
def ping
|
59
|
+
base_cmd = Pulsar::Proto::BaseCommand.new(
|
60
|
+
type: Pulsar::Proto::BaseCommand::Type::PING,
|
61
|
+
ping: Pulsar::Proto::CommandPing.new
|
62
|
+
)
|
63
|
+
|
64
|
+
request(base_cmd, nil, true)
|
65
|
+
|
66
|
+
@state.ping!
|
67
|
+
end
|
68
|
+
|
69
|
+
def active_status
|
70
|
+
[@state.last_ping_at, @state.last_received_at]
|
71
|
+
end
|
72
|
+
|
73
|
+
def request(cmd, msg = nil, async = false, timeout = nil)
|
74
|
+
raise 'connection was closed!' if closed?
|
75
|
+
|
76
|
+
cmd.seq_generator ||= @seq_generator
|
77
|
+
|
78
|
+
# NOTE try to auto set *_id
|
79
|
+
cmd.handle_ids
|
80
|
+
|
81
|
+
frame = PulsarSdk::Protocol::Frame.encode(cmd, msg)
|
82
|
+
write(frame)
|
83
|
+
return true if async
|
84
|
+
|
85
|
+
if request_id = cmd.get_request_id
|
86
|
+
return @response_container.delete(request_id, timeout)
|
87
|
+
end
|
88
|
+
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def reader
|
94
|
+
@reader ||= PulsarSdk::Protocol::Reader.new(@socket)
|
95
|
+
end
|
96
|
+
|
97
|
+
def write(bytes)
|
98
|
+
begin
|
99
|
+
@socket.write_nonblock(bytes)
|
100
|
+
rescue IO::WaitWritable
|
101
|
+
IO.select(nil, [@socket], nil, @conn_options.operation_timeout)
|
102
|
+
retry
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def listen
|
107
|
+
@pong = Thread.new do
|
108
|
+
loop do
|
109
|
+
break if closed?
|
110
|
+
|
111
|
+
begin
|
112
|
+
@state.ready? ? read_from_connection : @state.wait
|
113
|
+
rescue Errno::ETIMEDOUT
|
114
|
+
# read timeout, do nothing
|
115
|
+
rescue => e
|
116
|
+
PulsarSdk.logger.error("reader error") {e}
|
117
|
+
close
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
@pong.abort_on_exception = false
|
122
|
+
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def connect
|
127
|
+
return true if (@socket && !closed?)
|
128
|
+
|
129
|
+
@socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
130
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
131
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
|
132
|
+
|
133
|
+
host_port = @conn_options.port_and_host_from(:logical_addr)
|
134
|
+
|
135
|
+
sockaddr = Socket.sockaddr_in(*host_port)
|
136
|
+
begin
|
137
|
+
# Initiate the socket connection in the background. If it doesn't fail
|
138
|
+
# immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
|
139
|
+
# indicating the connection is in progress.
|
140
|
+
@socket.connect_nonblock(sockaddr)
|
141
|
+
rescue IO::WaitWritable
|
142
|
+
# IO.select will block until the socket is writable or the timeout
|
143
|
+
# is exceeded, whichever comes first.
|
144
|
+
unless IO.select(nil, [@socket], nil, @conn_options.connection_timeout)
|
145
|
+
# IO.select returns nil when the socket is not ready before timeout
|
146
|
+
# seconds have elapsed
|
147
|
+
@socket.close
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
|
151
|
+
begin
|
152
|
+
# Verify there is now a good connection.
|
153
|
+
@socket.connect_nonblock(sockaddr)
|
154
|
+
rescue Errno::EISCONN
|
155
|
+
# The socket is connected, we're good!
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
@state.tcp_connected!
|
160
|
+
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
def do_hand_shake
|
165
|
+
base_cmd = Pulsar::Proto::BaseCommand.new(
|
166
|
+
type: Pulsar::Proto::BaseCommand::Type::CONNECT,
|
167
|
+
connect: Pulsar::Proto::CommandConnect.new(
|
168
|
+
client_version: CLIENT_NAME,
|
169
|
+
protocol_version: PROTOCOL_VER,
|
170
|
+
proxy_to_broker_url: @conn_options.proxy_to_broker_url
|
171
|
+
)
|
172
|
+
)
|
173
|
+
|
174
|
+
request(base_cmd)
|
175
|
+
|
176
|
+
@state.ready!
|
177
|
+
true
|
178
|
+
end
|
179
|
+
|
180
|
+
def read_from_connection
|
181
|
+
base_cmd, meta_and_payload = reader.read_fully
|
182
|
+
return if base_cmd.nil?
|
183
|
+
|
184
|
+
@state.received!
|
185
|
+
|
186
|
+
handle_base_command(base_cmd, meta_and_payload)
|
187
|
+
end
|
188
|
+
|
189
|
+
def handle_base_command(cmd, payload)
|
190
|
+
PulsarSdk.logger.debug(__method__){cmd.type} unless cmd.typeof_ping?
|
191
|
+
|
192
|
+
case
|
193
|
+
when cmd.typeof_success?
|
194
|
+
handle_response(cmd)
|
195
|
+
|
196
|
+
when cmd.typeof_connected?
|
197
|
+
PulsarSdk.logger.info(__method__){"#{cmd.type}: #{cmd.connected}"}
|
198
|
+
|
199
|
+
when cmd.typeof_producer_success?
|
200
|
+
handle_response(cmd)
|
201
|
+
|
202
|
+
when cmd.typeof_lookup_response?
|
203
|
+
handle_response(cmd)
|
204
|
+
|
205
|
+
when cmd.typeof_get_last_message_id_response?
|
206
|
+
handle_response(cmd)
|
207
|
+
|
208
|
+
when cmd.typeof_consumer_stats_response?
|
209
|
+
handle_response(cmd)
|
210
|
+
|
211
|
+
when cmd.typeof_reached_end_of_topic?
|
212
|
+
# TODO notify consumer no more message
|
213
|
+
|
214
|
+
when cmd.typeof_get_topics_of_namespace_response?
|
215
|
+
handle_response(cmd)
|
216
|
+
|
217
|
+
when cmd.typeof_get_schema_response?
|
218
|
+
when cmd.typeof_partitioned_metadata_response?
|
219
|
+
handle_response(cmd)
|
220
|
+
|
221
|
+
when cmd.typeof_error?
|
222
|
+
PulsarSdk.logger.error(__method__){"#{cmd.error}: #{cmd.message}"}
|
223
|
+
|
224
|
+
when cmd.typeof_close_producer?
|
225
|
+
producer_id = cmd.close_producer.producer_id
|
226
|
+
producer_handlers.find(producer_id)&.call
|
227
|
+
|
228
|
+
when cmd.typeof_close_consumer?
|
229
|
+
consumer_id = cmd.close_consumer.consumer_id
|
230
|
+
consumer_handlers.find(consumer_id)&.call
|
231
|
+
|
232
|
+
when cmd.typeof_active_consumer_change?
|
233
|
+
when cmd.typeof_message?
|
234
|
+
handle_message(cmd, payload)
|
235
|
+
|
236
|
+
when cmd.typeof_send_receipt?
|
237
|
+
handle_send_receipt(cmd)
|
238
|
+
|
239
|
+
when cmd.typeof_ping?
|
240
|
+
handle_ping
|
241
|
+
|
242
|
+
when cmd.typeof_pong?
|
243
|
+
|
244
|
+
else
|
245
|
+
close
|
246
|
+
raise "Received invalid command type: #{cmd.type}"
|
247
|
+
end
|
248
|
+
|
249
|
+
true
|
250
|
+
end
|
251
|
+
|
252
|
+
def handle_response(cmd)
|
253
|
+
request_id = cmd.get_request_id
|
254
|
+
return if request_id.nil?
|
255
|
+
@response_container.add(request_id, cmd)
|
256
|
+
end
|
257
|
+
|
258
|
+
def handle_message(cmd, payload)
|
259
|
+
consumer_id = cmd.get_consumer_id
|
260
|
+
if consumer_id.nil?
|
261
|
+
::PulsarSdk.logger.warn(__method__){"can not get consumer id from cmd: #{cmd.inspect}"}
|
262
|
+
return
|
263
|
+
end
|
264
|
+
handler = consumer_handlers.find(consumer_id)
|
265
|
+
if handler.nil?
|
266
|
+
::PulsarSdk.logger.warn(__method__){"can not get consumer_handler from cmd: #{cmd.inspect}"}
|
267
|
+
return
|
268
|
+
end
|
269
|
+
handler.call(cmd, payload)
|
270
|
+
end
|
271
|
+
|
272
|
+
def handle_send_receipt(cmd)
|
273
|
+
send_receipt = cmd.send_receipt
|
274
|
+
producer_id = send_receipt.producer_id
|
275
|
+
handler = producer_handlers.find(producer_id)
|
276
|
+
return if handler.nil?
|
277
|
+
handler.call(send_receipt)
|
278
|
+
end
|
279
|
+
|
280
|
+
def handle_ping
|
281
|
+
base_cmd = Pulsar::Proto::BaseCommand.new(
|
282
|
+
type: Pulsar::Proto::BaseCommand::Type::PONG,
|
283
|
+
pong: Pulsar::Proto::CommandPong.new
|
284
|
+
)
|
285
|
+
|
286
|
+
request(base_cmd, nil, true)
|
287
|
+
end
|
288
|
+
|
289
|
+
class Status
|
290
|
+
attr_reader :last_received_at, :last_ping_at
|
291
|
+
|
292
|
+
STATUS = %w[
|
293
|
+
init
|
294
|
+
connecting
|
295
|
+
tcp_connected
|
296
|
+
ready
|
297
|
+
closed
|
298
|
+
].freeze
|
299
|
+
|
300
|
+
def initialize
|
301
|
+
@state = 'init'
|
302
|
+
@lock = Mutex.new
|
303
|
+
@signal = ConditionVariable.new
|
304
|
+
@last_received_at = 0
|
305
|
+
@last_ping_at = 0
|
306
|
+
end
|
307
|
+
|
308
|
+
def wait
|
309
|
+
@lock.synchronize do
|
310
|
+
@signal.wait(@lock)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def received!
|
315
|
+
@lock.synchronize do
|
316
|
+
@last_received_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def ping!
|
321
|
+
@lock.synchronize do
|
322
|
+
@last_ping_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
STATUS.each do |x|
|
327
|
+
define_method "#{x.to_s.downcase}?" do
|
328
|
+
@state == x
|
329
|
+
end
|
330
|
+
|
331
|
+
define_method "#{x.to_s.downcase}!" do
|
332
|
+
@lock.synchronize do
|
333
|
+
@state = x
|
334
|
+
@signal.broadcast
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
class SeqGenerator
|
341
|
+
def initialize
|
342
|
+
@mutex = Mutex.new
|
343
|
+
@seq = {}
|
344
|
+
end
|
345
|
+
|
346
|
+
# def new_request_id
|
347
|
+
# def new_producer_id
|
348
|
+
# def new_consumer_id
|
349
|
+
# def new_sequence_id
|
350
|
+
[:request_id, :producer_id, :consumer_id, :sequence_id].each do |k|
|
351
|
+
define_method "new_#{k}" do
|
352
|
+
next!(k)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def next!(key)
|
357
|
+
@mutex.synchronize do
|
358
|
+
@seq[key] ||= 0
|
359
|
+
@seq[key] += 1
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
class ConsumerHandler < ::PulsarSdk::Tweaks::WaitMap; end
|
365
|
+
|
366
|
+
class ProducerHandler < ::PulsarSdk::Tweaks::WaitMap; end
|
367
|
+
|
368
|
+
class ResponseContainer < ::PulsarSdk::Tweaks::WaitMap; end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|