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.
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