pulsar_sdk 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +51 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE +201 -0
  5. data/README.md +107 -0
  6. data/Rakefile +2 -0
  7. data/bin/console +15 -0
  8. data/bin/setup +8 -0
  9. data/lib/protobuf/pulsar_api.pb.rb +710 -0
  10. data/lib/protobuf/pulsar_api.proto +934 -0
  11. data/lib/protobuf/validate.rb +41 -0
  12. data/lib/pulsar_admin.rb +14 -0
  13. data/lib/pulsar_admin/api.rb +215 -0
  14. data/lib/pulsar_sdk.rb +55 -0
  15. data/lib/pulsar_sdk/client.rb +13 -0
  16. data/lib/pulsar_sdk/client/connection.rb +371 -0
  17. data/lib/pulsar_sdk/client/connection_pool.rb +79 -0
  18. data/lib/pulsar_sdk/client/rpc.rb +67 -0
  19. data/lib/pulsar_sdk/consumer.rb +13 -0
  20. data/lib/pulsar_sdk/consumer/base.rb +148 -0
  21. data/lib/pulsar_sdk/consumer/manager.rb +127 -0
  22. data/lib/pulsar_sdk/consumer/message_tracker.rb +86 -0
  23. data/lib/pulsar_sdk/options.rb +6 -0
  24. data/lib/pulsar_sdk/options/base.rb +10 -0
  25. data/lib/pulsar_sdk/options/connection.rb +51 -0
  26. data/lib/pulsar_sdk/options/consumer.rb +34 -0
  27. data/lib/pulsar_sdk/options/producer.rb +14 -0
  28. data/lib/pulsar_sdk/options/reader.rb +7 -0
  29. data/lib/pulsar_sdk/options/tls.rb +8 -0
  30. data/lib/pulsar_sdk/producer.rb +14 -0
  31. data/lib/pulsar_sdk/producer/base.rb +154 -0
  32. data/lib/pulsar_sdk/producer/manager.rb +67 -0
  33. data/lib/pulsar_sdk/producer/message.rb +47 -0
  34. data/lib/pulsar_sdk/producer/router.rb +100 -0
  35. data/lib/pulsar_sdk/protocol.rb +8 -0
  36. data/lib/pulsar_sdk/protocol/frame.rb +53 -0
  37. data/lib/pulsar_sdk/protocol/lookup.rb +55 -0
  38. data/lib/pulsar_sdk/protocol/message.rb +55 -0
  39. data/lib/pulsar_sdk/protocol/namespace.rb +22 -0
  40. data/lib/pulsar_sdk/protocol/partitioned.rb +54 -0
  41. data/lib/pulsar_sdk/protocol/reader.rb +67 -0
  42. data/lib/pulsar_sdk/protocol/structure.rb +93 -0
  43. data/lib/pulsar_sdk/protocol/topic.rb +74 -0
  44. data/lib/pulsar_sdk/tweaks.rb +10 -0
  45. data/lib/pulsar_sdk/tweaks/assign_attributes.rb +30 -0
  46. data/lib/pulsar_sdk/tweaks/base_command.rb +66 -0
  47. data/lib/pulsar_sdk/tweaks/binary_heap.rb +133 -0
  48. data/lib/pulsar_sdk/tweaks/clean_inspect.rb +15 -0
  49. data/lib/pulsar_sdk/tweaks/time_at_microsecond.rb +27 -0
  50. data/lib/pulsar_sdk/tweaks/timeout_queue.rb +52 -0
  51. data/lib/pulsar_sdk/tweaks/wait_map.rb +81 -0
  52. data/lib/pulsar_sdk/version.rb +3 -0
  53. data/pulsar_sdk.gemspec +31 -0
  54. 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
@@ -0,0 +1,14 @@
1
+ require 'net/http'
2
+ require 'pulsar_admin/api'
3
+
4
+ module PulsarAdmin
5
+ extend self
6
+
7
+ # options
8
+ # endpoint
9
+ # tenant
10
+ # persistent
11
+ def create_client(options)
12
+ PulsarAdmin::Api.new(options)
13
+ end
14
+ end
@@ -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
@@ -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,13 @@
1
+ require 'pulsar_sdk/client/rpc'
2
+ require 'pulsar_sdk/client/connection'
3
+ require 'pulsar_sdk/client/connection_pool'
4
+
5
+ module PulsarSdk
6
+ module Client
7
+ extend self
8
+
9
+ def create(opts)
10
+ PulsarSdk::Client::Rpc.new(opts)
11
+ end
12
+ end
13
+ 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