nats-pure 2.1.2 → 2.2.1

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.
data/lib/nats/io/js.rb DELETED
@@ -1,1331 +0,0 @@
1
- # Copyright 2021 The NATS Authors
2
- # Licensed under the Apache License, Version 2.0 (the "License");
3
- # you may not use this file except in compliance with the License.
4
- # You may obtain a copy of the License at
5
- #
6
- # http://www.apache.org/licenses/LICENSE-2.0
7
- #
8
- # Unless required by applicable law or agreed to in writing, software
9
- # distributed under the License is distributed on an "AS IS" BASIS,
10
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
- # See the License for the specific language governing permissions and
12
- # limitations under the License.
13
- #
14
- require_relative 'msg'
15
- require_relative 'client'
16
- require_relative 'errors'
17
- require_relative 'kv'
18
- require 'time'
19
- require 'base64'
20
-
21
- module NATS
22
-
23
- # JetStream returns a context with a similar API as the NATS::Client
24
- # but with enhanced functions to persist and consume messages from
25
- # the NATS JetStream engine.
26
- #
27
- # @example
28
- # nc = NATS.connect("demo.nats.io")
29
- # js = nc.jetstream()
30
- #
31
- class JetStream
32
- # Create a new JetStream context for a NATS connection.
33
- #
34
- # @param conn [NATS::Client]
35
- # @param params [Hash] Options to customize JetStream context.
36
- # @option params [String] :prefix JetStream API prefix to use for the requests.
37
- # @option params [String] :domain JetStream Domain to use for the requests.
38
- # @option params [Float] :timeout Default timeout to use for JS requests.
39
- def initialize(conn, params={})
40
- @nc = conn
41
- @prefix = if params[:prefix]
42
- params[:prefix]
43
- elsif params[:domain]
44
- "$JS.#{params[:domain]}.API"
45
- else
46
- JS::DefaultAPIPrefix
47
- end
48
- @opts = params
49
- @opts[:timeout] ||= 5 # seconds
50
- params[:prefix] = @prefix
51
-
52
- # Include JetStream::Manager
53
- extend Manager
54
- extend KeyValue::Manager
55
- end
56
-
57
- # PubAck is the API response from a successfully published message.
58
- #
59
- # @!attribute [stream] stream
60
- # @return [String] Name of the stream that processed the published message.
61
- # @!attribute [seq] seq
62
- # @return [Fixnum] Sequence of the message in the stream.
63
- # @!attribute [duplicate] duplicate
64
- # @return [Boolean] Indicates whether the published message is a duplicate.
65
- # @!attribute [domain] domain
66
- # @return [String] JetStream Domain that processed the ack response.
67
- PubAck = Struct.new(:stream, :seq, :duplicate, :domain, keyword_init: true)
68
-
69
- # publish produces a message for JetStream.
70
- #
71
- # @param subject [String] The subject from a stream where the message will be sent.
72
- # @param payload [String] The payload of the message.
73
- # @param params [Hash] Options to customize the publish message request.
74
- # @option params [Float] :timeout Time to wait for an PubAck response or an error.
75
- # @option params [Hash] :header NATS Headers to use for the message.
76
- # @option params [String] :stream Expected Stream to which the message is being published.
77
- # @raise [NATS::Timeout] When it takes too long to receive an ack response.
78
- # @return [PubAck] The pub ack response.
79
- def publish(subject, payload="", **params)
80
- params[:timeout] ||= @opts[:timeout]
81
- if params[:stream]
82
- params[:header] ||= {}
83
- params[:header][JS::Header::ExpectedStream] = params[:stream]
84
- end
85
-
86
- # Send message with headers.
87
- msg = NATS::Msg.new(subject: subject,
88
- data: payload,
89
- header: params[:header])
90
-
91
- begin
92
- resp = @nc.request_msg(msg, **params)
93
- result = JSON.parse(resp.data, symbolize_names: true)
94
- rescue ::NATS::IO::NoRespondersError
95
- raise JetStream::Error::NoStreamResponse.new("nats: no response from stream")
96
- end
97
- raise JS.from_error(result[:error]) if result[:error]
98
-
99
- PubAck.new(result)
100
- end
101
-
102
- # subscribe binds or creates a push subscription to a JetStream pull consumer.
103
- #
104
- # @param subject [String] Subject from which the messages will be fetched.
105
- # @param params [Hash] Options to customize the PushSubscription.
106
- # @option params [String] :stream Name of the Stream to which the consumer belongs.
107
- # @option params [String] :consumer Name of the Consumer to which the PushSubscription will be bound.
108
- # @option params [String] :durable Consumer durable name from where the messages will be fetched.
109
- # @option params [Hash] :config Configuration for the consumer.
110
- # @return [NATS::JetStream::PushSubscription]
111
- def subscribe(subject, params={}, &cb)
112
- params[:consumer] ||= params[:durable]
113
- stream = params[:stream].nil? ? find_stream_name_by_subject(subject) : params[:stream]
114
-
115
- queue = params[:queue]
116
- durable = params[:durable]
117
- flow_control = params[:flow_control]
118
- manual_ack = params[:manual_ack]
119
- idle_heartbeat = params[:idle_heartbeat]
120
- flow_control = params[:flow_control]
121
- config = params[:config]
122
-
123
- if queue
124
- if durable and durable != queue
125
- raise NATS::JetStream::Error.new("nats: cannot create queue subscription '#{queue}' to consumer '#{durable}'")
126
- else
127
- durable = queue
128
- end
129
- end
130
-
131
- cinfo = nil
132
- consumer_found = false
133
- should_create = false
134
-
135
- if not durable
136
- should_create = true
137
- else
138
- begin
139
- cinfo = consumer_info(stream, durable)
140
- config = cinfo.config
141
- consumer_found = true
142
- consumer = durable
143
- rescue NATS::JetStream::Error::NotFound
144
- should_create = true
145
- consumer_found = false
146
- end
147
- end
148
-
149
- if consumer_found
150
- if not config.deliver_group
151
- if queue
152
- raise NATS::JetStream::Error.new("nats: cannot create a queue subscription for a consumer without a deliver group")
153
- elsif cinfo.push_bound
154
- raise NATS::JetStream::Error.new("nats: consumer is already bound to a subscription")
155
- end
156
- else
157
- if not queue
158
- raise NATS::JetStream::Error.new("nats: cannot create a subscription for a consumer with a deliver group #{config.deliver_group}")
159
- elsif queue != config.deliver_group
160
- raise NATS::JetStream::Error.new("nats: cannot create a queue subscription #{queue} for a consumer with a deliver group #{config.deliver_group}")
161
- end
162
- end
163
- elsif should_create
164
- # Auto-create consumer if none found.
165
- if config.nil?
166
- # Defaults
167
- config = JetStream::API::ConsumerConfig.new({ack_policy: "explicit"})
168
- elsif config.is_a?(Hash)
169
- config = JetStream::API::ConsumerConfig.new(config)
170
- elsif !config.is_a?(JetStream::API::ConsumerConfig)
171
- raise NATS::JetStream::Error.new("nats: invalid ConsumerConfig")
172
- end
173
-
174
- config.durable_name = durable if not config.durable_name
175
- config.deliver_group = queue if not config.deliver_group
176
-
177
- # Create inbox for push consumer.
178
- deliver = @nc.new_inbox
179
- config.deliver_subject = deliver
180
-
181
- # Auto created consumers use the filter subject.
182
- config.filter_subject = subject
183
-
184
- # Heartbeats / FlowControl
185
- config.flow_control = flow_control
186
- if idle_heartbeat or config.idle_heartbeat
187
- idle_heartbeat = config.idle_heartbeat if config.idle_heartbeat
188
- idle_heartbeat = idle_heartbeat * 1_000_000_000
189
- config.idle_heartbeat = idle_heartbeat
190
- end
191
-
192
- # Auto create the consumer.
193
- cinfo = add_consumer(stream, config)
194
- consumer = cinfo.name
195
- end
196
-
197
- # Enable auto acking for async callbacks unless disabled.
198
- if cb and not manual_ack
199
- ocb = cb
200
- new_cb = proc do |msg|
201
- ocb.call(msg)
202
- msg.ack rescue JetStream::Error::MsgAlreadyAckd
203
- end
204
- cb = new_cb
205
- end
206
- sub = @nc.subscribe(config.deliver_subject, queue: config.deliver_group, &cb)
207
- sub.extend(PushSubscription)
208
- sub.jsi = JS::Sub.new(
209
- js: self,
210
- stream: stream,
211
- consumer: consumer,
212
- )
213
- sub
214
- end
215
-
216
- # pull_subscribe binds or creates a subscription to a JetStream pull consumer.
217
- #
218
- # @param subject [String] Subject from which the messages will be fetched.
219
- # @param durable [String] Consumer durable name from where the messages will be fetched.
220
- # @param params [Hash] Options to customize the PullSubscription.
221
- # @option params [String] :stream Name of the Stream to which the consumer belongs.
222
- # @option params [String] :consumer Name of the Consumer to which the PullSubscription will be bound.
223
- # @option params [Hash] :config Configuration for the consumer.
224
- # @return [NATS::JetStream::PullSubscription]
225
- def pull_subscribe(subject, durable, params={})
226
- raise JetStream::Error::InvalidDurableName.new("nats: invalid durable name") if durable.empty?
227
- params[:consumer] ||= durable
228
- stream = params[:stream].nil? ? find_stream_name_by_subject(subject) : params[:stream]
229
-
230
- begin
231
- consumer_info(stream, params[:consumer])
232
- rescue NATS::JetStream::Error::NotFound => e
233
- # If attempting to bind, then this is a hard error.
234
- raise e if params[:stream]
235
-
236
- config = if not params[:config]
237
- JetStream::API::ConsumerConfig.new
238
- elsif params[:config].is_a?(JetStream::API::ConsumerConfig)
239
- params[:config]
240
- else
241
- JetStream::API::ConsumerConfig.new(params[:config])
242
- end
243
- config[:durable_name] = durable
244
- config[:ack_policy] ||= JS::Config::AckExplicit
245
- add_consumer(stream, config)
246
- end
247
-
248
- deliver = @nc.new_inbox
249
- sub = @nc.subscribe(deliver)
250
- sub.extend(PullSubscription)
251
-
252
- consumer = params[:consumer]
253
- subject = "#{@prefix}.CONSUMER.MSG.NEXT.#{stream}.#{consumer}"
254
- sub.jsi = JS::Sub.new(
255
- js: self,
256
- stream: stream,
257
- consumer: params[:consumer],
258
- nms: subject
259
- )
260
- sub
261
- end
262
-
263
- # A JetStream::Manager can be used to make requests to the JetStream API.
264
- #
265
- # @example
266
- # require 'nats/client'
267
- #
268
- # nc = NATS.connect("demo.nats.io")
269
- #
270
- # config = JetStream::API::StreamConfig.new()
271
- # nc.jsm.add_stream(config)
272
- #
273
- #
274
- module Manager
275
- # add_stream creates a stream with a given config.
276
- # @param config [JetStream::API::StreamConfig] Configuration of the stream to create.
277
- # @param params [Hash] Options to customize API request.
278
- # @option params [Float] :timeout Time to wait for response.
279
- # @return [JetStream::API::StreamCreateResponse] The result of creating a Stream.
280
- def add_stream(config, params={})
281
- config = if not config.is_a?(JetStream::API::StreamConfig)
282
- JetStream::API::StreamConfig.new(config)
283
- else
284
- config
285
- end
286
- stream = config[:name]
287
- raise ArgumentError.new(":name is required to create streams") unless stream
288
- raise ArgumentError.new("Spaces, tabs, period (.), greater than (>) or asterisk (*) are prohibited in stream names") if stream =~ /(\s|\.|\>|\*)/
289
- req_subject = "#{@prefix}.STREAM.CREATE.#{stream}"
290
- result = api_request(req_subject, config.to_json, params)
291
- JetStream::API::StreamCreateResponse.new(result)
292
- end
293
-
294
- # stream_info retrieves the current status of a stream.
295
- # @param stream [String] Name of the stream.
296
- # @param params [Hash] Options to customize API request.
297
- # @option params [Float] :timeout Time to wait for response.
298
- # @return [JetStream::API::StreamInfo] The latest StreamInfo of the stream.
299
- def stream_info(stream, params={})
300
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
301
-
302
- req_subject = "#{@prefix}.STREAM.INFO.#{stream}"
303
- result = api_request(req_subject, '', params)
304
- JetStream::API::StreamInfo.new(result)
305
- end
306
-
307
- # delete_stream deletes a stream.
308
- # @param stream [String] Name of the stream.
309
- # @param params [Hash] Options to customize API request.
310
- # @option params [Float] :timeout Time to wait for response.
311
- # @return [Boolean]
312
- def delete_stream(stream, params={})
313
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
314
-
315
- req_subject = "#{@prefix}.STREAM.DELETE.#{stream}"
316
- result = api_request(req_subject, '', params)
317
- result[:success]
318
- end
319
-
320
- # add_consumer creates a consumer with a given config.
321
- # @param stream [String] Name of the stream.
322
- # @param config [JetStream::API::ConsumerConfig] Configuration of the consumer to create.
323
- # @param params [Hash] Options to customize API request.
324
- # @option params [Float] :timeout Time to wait for response.
325
- # @return [JetStream::API::ConsumerInfo] The result of creating a Consumer.
326
- def add_consumer(stream, config, params={})
327
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
328
- config = if not config.is_a?(JetStream::API::ConsumerConfig)
329
- JetStream::API::ConsumerConfig.new(config)
330
- else
331
- config
332
- end
333
- req_subject = if config[:durable_name]
334
- "#{@prefix}.CONSUMER.DURABLE.CREATE.#{stream}.#{config[:durable_name]}"
335
- else
336
- "#{@prefix}.CONSUMER.CREATE.#{stream}"
337
- end
338
-
339
- config[:ack_policy] ||= JS::Config::AckExplicit
340
- # Check if have to normalize ack wait so that it is in nanoseconds for Go compat.
341
- if config[:ack_wait]
342
- raise ArgumentError.new("nats: invalid ack wait") unless config[:ack_wait].is_a?(Integer)
343
- config[:ack_wait] = config[:ack_wait] * 1_000_000_000
344
- end
345
-
346
- req = {
347
- stream_name: stream,
348
- config: config
349
- }
350
-
351
- result = api_request(req_subject, req.to_json, params)
352
- JetStream::API::ConsumerInfo.new(result).freeze
353
- end
354
-
355
- # consumer_info retrieves the current status of a consumer.
356
- # @param stream [String] Name of the stream.
357
- # @param consumer [String] Name of the consumer.
358
- # @param params [Hash] Options to customize API request.
359
- # @option params [Float] :timeout Time to wait for response.
360
- # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
361
- def consumer_info(stream, consumer, params={})
362
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
363
- raise JetStream::Error::InvalidConsumerName.new("nats: invalid consumer name") if consumer.nil? or consumer.empty?
364
-
365
- req_subject = "#{@prefix}.CONSUMER.INFO.#{stream}.#{consumer}"
366
- result = api_request(req_subject, '', params)
367
- JetStream::API::ConsumerInfo.new(result)
368
- end
369
-
370
- # delete_consumer deletes a consumer.
371
- # @param stream [String] Name of the stream.
372
- # @param consumer [String] Name of the consumer.
373
- # @param params [Hash] Options to customize API request.
374
- # @option params [Float] :timeout Time to wait for response.
375
- # @return [Boolean]
376
- def delete_consumer(stream, consumer, params={})
377
- raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty?
378
- raise JetStream::Error::InvalidConsumerName.new("nats: invalid consumer name") if consumer.nil? or consumer.empty?
379
-
380
- req_subject = "#{@prefix}.CONSUMER.DELETE.#{stream}.#{consumer}"
381
- result = api_request(req_subject, '', params)
382
- result[:success]
383
- end
384
-
385
- # find_stream_name_by_subject does a lookup for the stream to which
386
- # the subject belongs.
387
- # @param subject [String] The subject that belongs to a stream.
388
- # @param params [Hash] Options to customize API request.
389
- # @option params [Float] :timeout Time to wait for response.
390
- # @return [String] The name of the JetStream stream for the subject.
391
- def find_stream_name_by_subject(subject, params={})
392
- req_subject = "#{@prefix}.STREAM.NAMES"
393
- req = { subject: subject }
394
- result = api_request(req_subject, req.to_json, params)
395
- raise JetStream::Error::NotFound unless result[:streams]
396
-
397
- result[:streams].first
398
- end
399
-
400
- def get_last_msg(stream_name, subject)
401
- req_subject = "#{@prefix}.STREAM.MSG.GET.#{stream_name}"
402
- req = {'last_by_subj': subject}
403
- data = req.to_json
404
- resp = api_request(req_subject, data)
405
- JetStream::API::RawStreamMsg.new(resp[:message])
406
- end
407
-
408
- private
409
-
410
- def api_request(req_subject, req="", params={})
411
- params[:timeout] ||= @opts[:timeout]
412
- result = begin
413
- msg = @nc.request(req_subject, req, **params)
414
- JSON.parse(msg.data, symbolize_names: true)
415
- rescue NATS::IO::NoRespondersError
416
- raise JetStream::Error::ServiceUnavailable
417
- end
418
- raise JS.from_error(result[:error]) if result[:error]
419
-
420
- result
421
- end
422
- end
423
-
424
- # PushSubscription is included into NATS::Subscription so that it
425
- #
426
- # @example Create a push subscription using JetStream context.
427
- #
428
- # require 'nats/client'
429
- #
430
- # nc = NATS.connect
431
- # js = nc.jetstream
432
- # sub = js.subscribe("foo", "bar")
433
- # msg = sub.next_msg
434
- # msg.ack
435
- # sub.unsubscribe
436
- #
437
- # @!visibility public
438
- module PushSubscription
439
- # consumer_info retrieves the current status of the pull subscription consumer.
440
- # @param params [Hash] Options to customize API request.
441
- # @option params [Float] :timeout Time to wait for response.
442
- # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
443
- def consumer_info(params={})
444
- @jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
445
- end
446
- end
447
- private_constant :PushSubscription
448
-
449
- # PullSubscription is included into NATS::Subscription so that it
450
- # can be used to fetch messages from a pull based consumer from
451
- # JetStream.
452
- #
453
- # @example Create a pull subscription using JetStream context.
454
- #
455
- # require 'nats/client'
456
- #
457
- # nc = NATS.connect
458
- # js = nc.jetstream
459
- # psub = js.pull_subscribe("foo", "bar")
460
- #
461
- # loop do
462
- # msgs = psub.fetch(5)
463
- # msgs.each do |msg|
464
- # msg.ack
465
- # end
466
- # end
467
- #
468
- # @!visibility public
469
- module PullSubscription
470
- # next_msg is not available for pull based subscriptions.
471
- # @raise [NATS::JetStream::Error]
472
- def next_msg(params={})
473
- raise ::NATS::JetStream::Error.new("nats: pull subscription cannot use next_msg")
474
- end
475
-
476
- # fetch makes a request to be delivered more messages from a pull consumer.
477
- #
478
- # @param batch [Fixnum] Number of messages to pull from the stream.
479
- # @param params [Hash] Options to customize the fetch request.
480
- # @option params [Float] :timeout Duration of the fetch request before it expires.
481
- # @return [Array<NATS::Msg>]
482
- def fetch(batch=1, params={})
483
- if batch < 1
484
- raise ::NATS::JetStream::Error.new("nats: invalid batch size")
485
- end
486
-
487
- t = MonotonicTime.now
488
- timeout = params[:timeout] ||= 5
489
- expires = (timeout * 1_000_000_000) - 100_000
490
- next_req = {
491
- batch: batch
492
- }
493
-
494
- msgs = []
495
- case
496
- when batch < 1
497
- raise ::NATS::JetStream::Error.new("nats: invalid batch size")
498
- when batch == 1
499
- ####################################################
500
- # Fetch (1) #
501
- ####################################################
502
-
503
- # Check if there is any pending message in the queue that is
504
- # ready to be consumed.
505
- synchronize do
506
- unless @pending_queue.empty?
507
- msg = @pending_queue.pop
508
- @pending_size -= msg.data.size
509
- # Check for a no msgs response status.
510
- if JS.is_status_msg(msg)
511
- case msg.header["Status"]
512
- when JS::Status::NoMsgs
513
- msg = nil
514
- when JS::Status::RequestTimeout
515
- # Skip
516
- else
517
- raise JS.from_msg(msg)
518
- end
519
- else
520
- msgs << msg
521
- end
522
- end
523
- end
524
-
525
- # Make lingering request with expiration.
526
- next_req[:expires] = expires
527
- if msgs.empty?
528
- # Make publish request and wait for response.
529
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
530
-
531
- # Wait for result of fetch or timeout.
532
- synchronize { wait_for_msgs_cond.wait(timeout) }
533
-
534
- unless @pending_queue.empty?
535
- msg = @pending_queue.pop
536
- @pending_size -= msg.data.size
537
-
538
- msgs << msg
539
- end
540
-
541
- duration = MonotonicTime.since(t)
542
- if duration > timeout
543
- raise ::NATS::Timeout.new("nats: fetch timeout")
544
- end
545
-
546
- # Should have received at least a message at this point,
547
- # if that is not the case then error already.
548
- if JS.is_status_msg(msgs.first)
549
- msg = msgs.first
550
- case msg.header[JS::Header::Status]
551
- when JS::Status::RequestTimeout
552
- raise NATS::Timeout.new("nats: fetch request timeout")
553
- else
554
- raise JS.from_msg(msgs.first)
555
- end
556
- end
557
- end
558
- when batch > 1
559
- ####################################################
560
- # Fetch (n) #
561
- ####################################################
562
-
563
- # Check if there already enough in the pending buffer.
564
- synchronize do
565
- if batch <= @pending_queue.size
566
- batch.times do
567
- msg = @pending_queue.pop
568
- @pending_size -= msg.data.size
569
-
570
- # Check for a no msgs response status.
571
- if JS.is_status_msg(msg)
572
- case msg.header[JS::Header::Status]
573
- when JS::Status::NoMsgs, JS::Status::RequestTimeout
574
- # Skip these
575
- next
576
- else
577
- raise JS.from_msg(msg)
578
- end
579
- else
580
- msgs << msg
581
- end
582
- end
583
-
584
- return msgs
585
- end
586
- end
587
-
588
- # Make publish request and wait any response.
589
- next_req[:no_wait] = true
590
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
591
-
592
- # Not receiving even one is a timeout.
593
- start_time = MonotonicTime.now
594
- msg = nil
595
-
596
- synchronize do
597
- wait_for_msgs_cond.wait(timeout)
598
-
599
- unless @pending_queue.empty?
600
- msg = @pending_queue.pop
601
- @pending_size -= msg.data.size
602
- end
603
- end
604
-
605
- # Check if the first message was a response saying that
606
- # there are no messages.
607
- if !msg.nil? && JS.is_status_msg(msg)
608
- case msg.header[JS::Header::Status]
609
- when JS::Status::NoMsgs
610
- # Make another request that does wait.
611
- next_req[:expires] = expires
612
- next_req.delete(:no_wait)
613
-
614
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
615
- when JS::Status::RequestTimeout
616
- raise NATS::Timeout.new("nats: fetch request timeout")
617
- else
618
- raise JS.from_msg(msg)
619
- end
620
- else
621
- msgs << msg unless msg.nil?
622
- end
623
-
624
- # Check if have not received yet a single message.
625
- duration = MonotonicTime.since(start_time)
626
- if msgs.empty? and duration > timeout
627
- raise NATS::Timeout.new("nats: fetch timeout")
628
- end
629
-
630
- needed = batch - msgs.count
631
- while needed > 0 and MonotonicTime.since(start_time) < timeout
632
- duration = MonotonicTime.since(start_time)
633
-
634
- # Wait for the rest of the messages.
635
- synchronize do
636
-
637
- # Wait until there is a message delivered.
638
- if @pending_queue.empty?
639
- deadline = timeout - duration
640
- wait_for_msgs_cond.wait(deadline) if deadline > 0
641
-
642
- duration = MonotonicTime.since(start_time)
643
- if msgs.empty? && @pending_queue.empty? and duration > timeout
644
- raise NATS::Timeout.new("nats: fetch timeout")
645
- end
646
- else
647
- msg = @pending_queue.pop
648
- @pending_size -= msg.data.size
649
-
650
- if JS.is_status_msg(msg)
651
- case msg.header[JS::Header::Status]
652
- when JS::Status::NoMsgs, JS::Status::RequestTimeout
653
- duration = MonotonicTime.since(start_time)
654
-
655
- if duration > timeout
656
- # Only received a subset of the messages.
657
- if !msgs.empty?
658
- return msgs
659
- else
660
- raise NATS::Timeout.new("nats: fetch timeout")
661
- end
662
- end
663
- else
664
- raise JS.from_msg(msg)
665
- end
666
-
667
- else
668
- # Add to the set of messages that will be returned.
669
- msgs << msg
670
- needed -= 1
671
- end
672
- end
673
- end # :end: synchronize
674
- end
675
- end
676
-
677
- msgs
678
- end
679
-
680
- # consumer_info retrieves the current status of the pull subscription consumer.
681
- # @param params [Hash] Options to customize API request.
682
- # @option params [Float] :timeout Time to wait for response.
683
- # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
684
- def consumer_info(params={})
685
- @jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
686
- end
687
- end
688
- private_constant :PullSubscription
689
-
690
- #######################################
691
- # #
692
- # JetStream Message and Ack Methods #
693
- # #
694
- #######################################
695
-
696
- # JetStream::Msg module includes the methods so that a regular NATS::Msg
697
- # can be enhanced with JetStream features like acking and metadata.
698
- module Msg
699
- module Ack
700
- # Ack types
701
- Ack = ("+ACK".freeze)
702
- Nak = ("-NAK".freeze)
703
- Progress = ("+WPI".freeze)
704
- Term = ("+TERM".freeze)
705
-
706
- Empty = (''.freeze)
707
- DotSep = ('.'.freeze)
708
- NoDomainName = ('_'.freeze)
709
-
710
- # Position
711
- Prefix0 = ('$JS'.freeze)
712
- Prefix1 = ('ACK'.freeze)
713
- Domain = 2
714
- AccHash = 3
715
- Stream = 4
716
- Consumer = 5
717
- NumDelivered = 6
718
- StreamSeq = 7
719
- ConsumerSeq = 8
720
- Timestamp = 9
721
- NumPending = 10
722
-
723
- # Subject without domain:
724
- # $JS.ACK.<stream>.<consumer>.<delivered>.<sseq>.<cseq>.<tm>.<pending>
725
- #
726
- V1TokenCounts = 9
727
-
728
- # Subject with domain:
729
- # $JS.ACK.<domain>.<account hash>.<stream>.<consumer>.<delivered>.<sseq>.<cseq>.<tm>.<pending>.<a token with a random value>
730
- #
731
- V2TokenCounts = 12
732
-
733
- SequencePair = Struct.new(:stream, :consumer)
734
- end
735
- private_constant :Ack
736
-
737
- class Metadata
738
- attr_reader :sequence, :num_delivered, :num_pending, :timestamp, :stream, :consumer, :domain
739
-
740
- def initialize(opts)
741
- @sequence = Ack::SequencePair.new(opts[Ack::StreamSeq].to_i, opts[Ack::ConsumerSeq].to_i)
742
- @domain = opts[Ack::Domain]
743
- @num_delivered = opts[Ack::NumDelivered].to_i
744
- @num_pending = opts[Ack::NumPending].to_i
745
- @timestamp = Time.at((opts[Ack::Timestamp].to_i / 1_000_000_000.0))
746
- @stream = opts[Ack::Stream]
747
- @consumer = opts[Ack::Consumer]
748
- # TODO: Not exposed in Go client either right now.
749
- # account = opts[Ack::AccHash]
750
- end
751
- end
752
-
753
- module AckMethods
754
- def ack(**params)
755
- ensure_is_acked_once!
756
-
757
- resp = if params[:timeout]
758
- @nc.request(@reply, Ack::Ack, **params)
759
- else
760
- @nc.publish(@reply, Ack::Ack)
761
- end
762
- @sub.synchronize { @ackd = true }
763
-
764
- resp
765
- end
766
-
767
- def ack_sync(**params)
768
- ensure_is_acked_once!
769
-
770
- params[:timeout] ||= 0.5
771
- resp = @nc.request(@reply, Ack::Ack, **params)
772
- @sub.synchronize { @ackd = true }
773
-
774
- resp
775
- end
776
-
777
- def nak(**params)
778
- ensure_is_acked_once!
779
-
780
- resp = if params[:timeout]
781
- @nc.request(@reply, Ack::Nak, **params)
782
- else
783
- @nc.publish(@reply, Ack::Nak)
784
- end
785
- @sub.synchronize { @ackd = true }
786
-
787
- resp
788
- end
789
-
790
- def term(**params)
791
- ensure_is_acked_once!
792
-
793
- resp = if params[:timeout]
794
- @nc.request(@reply, Ack::Term, **params)
795
- else
796
- @nc.publish(@reply, Ack::Term)
797
- end
798
- @sub.synchronize { @ackd = true }
799
-
800
- resp
801
- end
802
-
803
- def in_progress(**params)
804
- params[:timeout] ? @nc.request(@reply, Ack::Progress, **params) : @nc.publish(@reply, Ack::Progress)
805
- end
806
-
807
- def metadata
808
- @meta ||= parse_metadata(reply)
809
- end
810
-
811
- private
812
-
813
- def ensure_is_acked_once!
814
- @sub.synchronize do
815
- if @ackd
816
- raise JetStream::Error::MsgAlreadyAckd.new("nats: message was already acknowledged: #{self}")
817
- end
818
- end
819
- end
820
-
821
- def parse_metadata(reply)
822
- tokens = reply.split(Ack::DotSep)
823
- n = tokens.count
824
-
825
- case
826
- when n < Ack::V1TokenCounts || (n > Ack::V1TokenCounts and n < Ack::V2TokenCounts)
827
- raise NotJSMessage.new("nats: not a jetstream message")
828
- when tokens[0] != Ack::Prefix0 || tokens[1] != Ack::Prefix1
829
- raise NotJSMessage.new("nats: not a jetstream message")
830
- when n == Ack::V1TokenCounts
831
- tokens.insert(Ack::Domain, Ack::Empty)
832
- tokens.insert(Ack::AccHash, Ack::Empty)
833
- when tokens[Ack::Domain] == Ack::NoDomainName
834
- tokens[Ack::Domain] = Ack::Empty
835
- end
836
-
837
- Metadata.new(tokens)
838
- end
839
- end
840
- end
841
-
842
- ####################################
843
- # #
844
- # JetStream Configuration Options #
845
- # #
846
- ####################################
847
-
848
- # Misc internal functions to support JS API.
849
- # @private
850
- module JS
851
- DefaultAPIPrefix = ("$JS.API".freeze)
852
-
853
- module Status
854
- CtrlMsg = ("100".freeze)
855
- NoMsgs = ("404".freeze)
856
- NotFound = ("404".freeze)
857
- RequestTimeout = ("408".freeze)
858
- ServiceUnavailable = ("503".freeze)
859
- end
860
-
861
- module Header
862
- Status = ("Status".freeze)
863
- Desc = ("Description".freeze)
864
- MsgID = ("Nats-Msg-Id".freeze)
865
- ExpectedStream = ("Nats-Expected-Stream".freeze)
866
- ExpectedLastSeq = ("Nats-Expected-Last-Sequence".freeze)
867
- ExpectedLastSubjSeq = ("Nats-Expected-Last-Subject-Sequence".freeze)
868
- ExpectedLastMsgID = ("Nats-Expected-Last-Msg-Id".freeze)
869
- LastConsumerSeq = ("Nats-Last-Consumer".freeze)
870
- LastStreamSeq = ("Nats-Last-Stream".freeze)
871
- end
872
-
873
- module Config
874
- # AckPolicy
875
- AckExplicit = ("explicit".freeze)
876
- AckAll = ("all".freeze)
877
- AckNone = ("none".freeze)
878
- end
879
-
880
- class Sub
881
- attr_reader :js, :stream, :consumer, :nms
882
-
883
- def initialize(opts={})
884
- @js = opts[:js]
885
- @stream = opts[:stream]
886
- @consumer = opts[:consumer]
887
- @nms = opts[:nms]
888
- end
889
- end
890
-
891
- class << self
892
- def next_req_to_json(next_req)
893
- req = {}
894
- req[:batch] = next_req[:batch]
895
- req[:expires] = next_req[:expires].to_i if next_req[:expires]
896
- req[:no_wait] = next_req[:no_wait] if next_req[:no_wait]
897
- req.to_json
898
- end
899
-
900
- def is_status_msg(msg)
901
- return (!msg.nil? and (!msg.header.nil? and msg.header[Header::Status]))
902
- end
903
-
904
- # check_503_error raises exception when a NATS::Msg has a 503 status header.
905
- # @param msg [NATS::Msg] The message with status headers.
906
- # @raise [NATS::JetStream::Error::ServiceUnavailable]
907
- def check_503_error(msg)
908
- return if msg.nil? or msg.header.nil?
909
- if msg.header[Header::Status] == Status::ServiceUnavailable
910
- raise ::NATS::JetStream::Error::ServiceUnavailable
911
- end
912
- end
913
-
914
- # from_msg takes a plain NATS::Msg and checks its headers to confirm
915
- # if it was an error:
916
- #
917
- # msg.header={"Status"=>"503"})
918
- # msg.header={"Status"=>"408", "Description"=>"Request Timeout"})
919
- #
920
- # @param msg [NATS::Msg] The message with status headers.
921
- # @return [NATS::JetStream::API::Error]
922
- def from_msg(msg)
923
- check_503_error(msg)
924
- code = msg.header[JS::Header::Status]
925
- desc = msg.header[JS::Header::Desc]
926
- return ::NATS::JetStream::API::Error.new({code: code, description: desc})
927
- end
928
-
929
- # from_error takes an API response that errored and maps the error
930
- # into a JetStream error type based on the status and error code.
931
- def from_error(err)
932
- return unless err
933
- case err[:code]
934
- when 503
935
- ::NATS::JetStream::Error::ServiceUnavailable.new(err)
936
- when 500
937
- ::NATS::JetStream::Error::ServerError.new(err)
938
- when 404
939
- case err[:err_code]
940
- when 10059
941
- ::NATS::JetStream::Error::StreamNotFound.new(err)
942
- when 10014
943
- ::NATS::JetStream::Error::ConsumerNotFound.new(err)
944
- else
945
- ::NATS::JetStream::Error::NotFound.new(err)
946
- end
947
- when 400
948
- ::NATS::JetStream::Error::BadRequest.new(err)
949
- else
950
- ::NATS::JetStream::API::Error.new(err)
951
- end
952
- end
953
- end
954
- end
955
- private_constant :JS
956
-
957
- #####################
958
- # #
959
- # JetStream Errors #
960
- # #
961
- #####################
962
-
963
- # Error is any error that may arise when interacting with JetStream.
964
- class Error < Error
965
-
966
- # When there is a NATS::IO::NoResponders error after making a publish request.
967
- class NoStreamResponse < Error; end
968
-
969
- # When an invalid durable or consumer name was attempted to be used.
970
- class InvalidDurableName < Error; end
971
-
972
- # When an ack not longer valid.
973
- class InvalidJSAck < Error; end
974
-
975
- # When an ack has already been acked.
976
- class MsgAlreadyAckd < Error; end
977
-
978
- # When the delivered message does not behave as a message delivered by JetStream,
979
- # for example when the ack reply has unrecognizable fields.
980
- class NotJSMessage < Error; end
981
-
982
- # When the stream name is invalid.
983
- class InvalidStreamName < Error; end
984
-
985
- # When the consumer name is invalid.
986
- class InvalidConsumerName < Error; end
987
-
988
- # When the server responds with an error from the JetStream API.
989
- class APIError < Error
990
- attr_reader :code, :err_code, :description, :stream, :seq
991
-
992
- def initialize(params={})
993
- @code = params[:code]
994
- @err_code = params[:err_code]
995
- @description = params[:description]
996
- @stream = params[:stream]
997
- @seq = params[:seq]
998
- end
999
-
1000
- def to_s
1001
- "#{@description} (status_code=#{@code}, err_code=#{@err_code})"
1002
- end
1003
- end
1004
-
1005
- # When JetStream is not currently available, this could be due to JetStream
1006
- # not being enabled or temporarily unavailable due to a leader election when
1007
- # running in cluster mode.
1008
- # This condition is represented with a message that has 503 status code header.
1009
- class ServiceUnavailable < APIError
1010
- def initialize(params={})
1011
- super(params)
1012
- @code ||= 503
1013
- end
1014
- end
1015
-
1016
- # When there is a hard failure in the JetStream.
1017
- # This condition is represented with a message that has 500 status code header.
1018
- class ServerError < APIError
1019
- def initialize(params={})
1020
- super(params)
1021
- @code ||= 500
1022
- end
1023
- end
1024
-
1025
- # When a JetStream object was not found.
1026
- # This condition is represented with a message that has 404 status code header.
1027
- class NotFound < APIError
1028
- def initialize(params={})
1029
- super(params)
1030
- @code ||= 404
1031
- end
1032
- end
1033
-
1034
- # When the stream is not found.
1035
- class StreamNotFound < NotFound; end
1036
-
1037
- # When the consumer or durable is not found by name.
1038
- class ConsumerNotFound < NotFound; end
1039
-
1040
- # When the JetStream client makes an invalid request.
1041
- # This condition is represented with a message that has 400 status code header.
1042
- class BadRequest < APIError
1043
- def initialize(params={})
1044
- super(params)
1045
- @code ||= 400
1046
- end
1047
- end
1048
- end
1049
-
1050
- #######################
1051
- # #
1052
- # JetStream API Types #
1053
- # #
1054
- #######################
1055
-
1056
- # JetStream::API are the types used to interact with the JetStream API.
1057
- module API
1058
- # When the server responds with an error from the JetStream API.
1059
- Error = ::NATS::JetStream::Error::APIError
1060
-
1061
- # SequenceInfo is a pair of consumer and stream sequence and last activity.
1062
- # @!attribute consumer_seq
1063
- # @return [Integer] The consumer sequence.
1064
- # @!attribute stream_seq
1065
- # @return [Integer] The stream sequence.
1066
- SequenceInfo = Struct.new(:consumer_seq, :stream_seq, :last_active,
1067
- keyword_init: true) do
1068
- def initialize(opts={})
1069
- # Filter unrecognized fields and freeze.
1070
- rem = opts.keys - members
1071
- opts.delete_if { |k| rem.include?(k) }
1072
- super(opts)
1073
- freeze
1074
- end
1075
- end
1076
-
1077
- # ConsumerInfo is the current status of a JetStream consumer.
1078
- #
1079
- # @!attribute stream_name
1080
- # @return [String] name of the stream to which the consumer belongs.
1081
- # @!attribute name
1082
- # @return [String] name of the consumer.
1083
- # @!attribute created
1084
- # @return [String] time when the consumer was created.
1085
- # @!attribute config
1086
- # @return [ConsumerConfig] consumer configuration.
1087
- # @!attribute delivered
1088
- # @return [SequenceInfo]
1089
- # @!attribute ack_floor
1090
- # @return [SequenceInfo]
1091
- # @!attribute num_ack_pending
1092
- # @return [Integer]
1093
- # @!attribute num_redelivered
1094
- # @return [Integer]
1095
- # @!attribute num_waiting
1096
- # @return [Integer]
1097
- # @!attribute num_pending
1098
- # @return [Integer]
1099
- # @!attribute cluster
1100
- # @return [Hash]
1101
- ConsumerInfo = Struct.new(:type, :stream_name, :name, :created,
1102
- :config, :delivered, :ack_floor,
1103
- :num_ack_pending, :num_redelivered, :num_waiting,
1104
- :num_pending, :cluster, :push_bound,
1105
- keyword_init: true) do
1106
- def initialize(opts={})
1107
- opts[:created] = Time.parse(opts[:created])
1108
- opts[:ack_floor] = SequenceInfo.new(opts[:ack_floor])
1109
- opts[:delivered] = SequenceInfo.new(opts[:delivered])
1110
- opts[:config][:ack_wait] = opts[:config][:ack_wait] / 1_000_000_000
1111
- opts[:config] = ConsumerConfig.new(opts[:config])
1112
- opts.delete(:cluster)
1113
- # Filter unrecognized fields just in case.
1114
- rem = opts.keys - members
1115
- opts.delete_if { |k| rem.include?(k) }
1116
- super(opts)
1117
- freeze
1118
- end
1119
- end
1120
-
1121
- # ConsumerConfig is the consumer configuration.
1122
- #
1123
- # @!attribute durable_name
1124
- # @return [String]
1125
- # @!attribute deliver_policy
1126
- # @return [String]
1127
- # @!attribute ack_policy
1128
- # @return [String]
1129
- # @!attribute ack_wait
1130
- # @return [Integer]
1131
- # @!attribute max_deliver
1132
- # @return [Integer]
1133
- # @!attribute replay_policy
1134
- # @return [String]
1135
- # @!attribute max_waiting
1136
- # @return [Integer]
1137
- # @!attribute max_ack_pending
1138
- # @return [Integer]
1139
- ConsumerConfig = Struct.new(:durable_name, :description,
1140
- :deliver_policy, :opt_start_seq, :opt_start_time,
1141
- :ack_policy, :ack_wait, :max_deliver, :backoff,
1142
- :filter_subject, :replay_policy, :rate_limit_bps,
1143
- :sample_freq, :max_waiting, :max_ack_pending,
1144
- :flow_control, :idle_heartbeat, :headers_only,
1145
-
1146
- # Pull based options
1147
- :max_batch, :max_expires,
1148
- # Push based consumers
1149
- :deliver_subject, :deliver_group,
1150
- # Ephemeral inactivity threshold
1151
- :inactive_threshold,
1152
- # Generally inherited by parent stream and other markers,
1153
- # now can be configured directly.
1154
- :num_replicas,
1155
- # Force memory storage
1156
- :memory_storage,
1157
- keyword_init: true) do
1158
- def initialize(opts={})
1159
- # Filter unrecognized fields just in case.
1160
- rem = opts.keys - members
1161
- opts.delete_if { |k| rem.include?(k) }
1162
- super(opts)
1163
- end
1164
-
1165
- def to_json(*args)
1166
- config = self.to_h
1167
- config.delete_if { |_k, v| v.nil? }
1168
- config.to_json(*args)
1169
- end
1170
- end
1171
-
1172
- # StreamConfig represents the configuration of a stream from JetStream.
1173
- #
1174
- # @!attribute type
1175
- # @return [String]
1176
- # @!attribute config
1177
- # @return [Hash]
1178
- # @!attribute created
1179
- # @return [String]
1180
- # @!attribute state
1181
- # @return [StreamState]
1182
- # @!attribute did_create
1183
- # @return [Boolean]
1184
- # @!attribute name
1185
- # @return [String]
1186
- # @!attribute subjects
1187
- # @return [Array]
1188
- # @!attribute retention
1189
- # @return [String]
1190
- # @!attribute max_consumers
1191
- # @return [Integer]
1192
- # @!attribute max_msgs
1193
- # @return [Integer]
1194
- # @!attribute max_bytes
1195
- # @return [Integer]
1196
- # @!attribute max_age
1197
- # @return [Integer]
1198
- # @!attribute max_msgs_per_subject
1199
- # @return [Integer]
1200
- # @!attribute max_msg_size
1201
- # @return [Integer]
1202
- # @!attribute discard
1203
- # @return [String]
1204
- # @!attribute storage
1205
- # @return [String]
1206
- # @!attribute num_replicas
1207
- # @return [Integer]
1208
- # @!attribute duplicate_window
1209
- # @return [Integer]
1210
- StreamConfig = Struct.new(:name, :description, :subjects, :retention, :max_consumers,
1211
- :max_msgs, :max_bytes, :discard, :max_age,
1212
- :max_msgs_per_subject, :max_msg_size,
1213
- :storage, :num_replicas, :no_ack, :duplicate_window,
1214
- :placement, :allow_direct,
1215
- keyword_init: true) do
1216
- def initialize(opts={})
1217
- # Filter unrecognized fields just in case.
1218
- rem = opts.keys - members
1219
- opts.delete_if { |k| rem.include?(k) }
1220
- super(opts)
1221
- end
1222
-
1223
- def to_json(*args)
1224
- config = self.to_h
1225
- config.delete_if { |_k, v| v.nil? }
1226
- config.to_json(*args)
1227
- end
1228
- end
1229
-
1230
- # StreamInfo is the info about a stream from JetStream.
1231
- #
1232
- # @!attribute type
1233
- # @return [String]
1234
- # @!attribute config
1235
- # @return [Hash]
1236
- # @!attribute created
1237
- # @return [String]
1238
- # @!attribute state
1239
- # @return [Hash]
1240
- # @!attribute domain
1241
- # @return [String]
1242
- StreamInfo = Struct.new(:type, :config, :created, :state, :domain,
1243
- keyword_init: true) do
1244
- def initialize(opts={})
1245
- opts[:config] = StreamConfig.new(opts[:config])
1246
- opts[:state] = StreamState.new(opts[:state])
1247
- opts[:created] = Time.parse(opts[:created])
1248
-
1249
- # Filter fields and freeze.
1250
- rem = opts.keys - members
1251
- opts.delete_if { |k| rem.include?(k) }
1252
- super(opts)
1253
- freeze
1254
- end
1255
- end
1256
-
1257
- # StreamState is the state of a stream.
1258
- #
1259
- # @!attribute messages
1260
- # @return [Integer]
1261
- # @!attribute bytes
1262
- # @return [Integer]
1263
- # @!attribute first_seq
1264
- # @return [Integer]
1265
- # @!attribute last_seq
1266
- # @return [Integer]
1267
- # @!attribute consumer_count
1268
- # @return [Integer]
1269
- StreamState = Struct.new(:messages, :bytes, :first_seq, :first_ts,
1270
- :last_seq, :last_ts, :consumer_count,
1271
- keyword_init: true) do
1272
- def initialize(opts={})
1273
- rem = opts.keys - members
1274
- opts.delete_if { |k| rem.include?(k) }
1275
- super(opts)
1276
- end
1277
- end
1278
-
1279
- # StreamCreateResponse is the response from the JetStream $JS.API.STREAM.CREATE API.
1280
- #
1281
- # @!attribute type
1282
- # @return [String]
1283
- # @!attribute config
1284
- # @return [StreamConfig]
1285
- # @!attribute created
1286
- # @return [String]
1287
- # @!attribute state
1288
- # @return [StreamState]
1289
- # @!attribute did_create
1290
- # @return [Boolean]
1291
- StreamCreateResponse = Struct.new(:type, :config, :created, :state, :did_create,
1292
- keyword_init: true) do
1293
- def initialize(opts={})
1294
- rem = opts.keys - members
1295
- opts.delete_if { |k| rem.include?(k) }
1296
- opts[:config] = StreamConfig.new(opts[:config])
1297
- opts[:state] = StreamState.new(opts[:state])
1298
- super(opts)
1299
- freeze
1300
- end
1301
- end
1302
-
1303
- RawStreamMsg = Struct.new(:subject, :seq, :data, :headers, keyword_init: true) do
1304
- def initialize(opts)
1305
- opts[:data] = Base64.decode64(opts[:data]) if opts[:data]
1306
- if opts[:hdrs]
1307
- header = Base64.decode64(opts[:hdrs])
1308
- hdr = {}
1309
- lines = header.lines
1310
- lines.slice(1, header.size).each do |line|
1311
- line.rstrip!
1312
- next if line.empty?
1313
- key, value = line.strip.split(/\s*:\s*/, 2)
1314
- hdr[key] = value
1315
- end
1316
- opts[:headers] = hdr
1317
- end
1318
-
1319
- # Filter out members not present.
1320
- rem = opts.keys - members
1321
- opts.delete_if { |k| rem.include?(k) }
1322
- super(opts)
1323
- end
1324
-
1325
- def sequence
1326
- self.seq
1327
- end
1328
- end
1329
- end
1330
- end
1331
- end