nats-pure 2.1.2 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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