nats-pure 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +90 -0
- data/README.md +83 -0
- data/lib/nats/io/client.rb +173 -60
- data/lib/nats/io/jetstream/api.rb +213 -5
- data/lib/nats/io/jetstream/errors.rb +25 -0
- data/lib/nats/io/jetstream/header.rb +115 -0
- data/lib/nats/io/jetstream/js/header.rb +1 -0
- data/lib/nats/io/jetstream/js/status.rb +1 -0
- data/lib/nats/io/jetstream/js.rb +30 -2
- data/lib/nats/io/jetstream/manager.rb +247 -67
- data/lib/nats/io/jetstream/msg/ack.rb +7 -3
- data/lib/nats/io/jetstream/msg/ack_methods.rb +14 -4
- data/lib/nats/io/jetstream/msg/metadata.rb +2 -1
- data/lib/nats/io/jetstream/pull_subscription.rb +288 -190
- data/lib/nats/io/jetstream.rb +98 -9
- data/lib/nats/io/kv/api.rb +4 -0
- data/lib/nats/io/kv/bucket_status.rb +12 -0
- data/lib/nats/io/kv/manager.rb +7 -1
- data/lib/nats/io/kv.rb +21 -15
- data/lib/nats/io/parser.rb +3 -3
- data/lib/nats/io/subscription.rb +18 -10
- data/lib/nats/io/version.rb +2 -2
- data/lib/nats/io/websocket.rb +32 -12
- data/lib/nats/service/validator.rb +2 -2
- data/sig/nats/io/jetstream/api.rbs +12 -3
- data/sig/nats/io/jetstream/errors.rbs +12 -0
- data/sig/nats/io/jetstream/header.rbs +33 -0
- data/sig/nats/io/jetstream/js/header.rbs +1 -0
- data/sig/nats/io/jetstream/js/status.rbs +1 -0
- data/sig/nats/io/jetstream/js.rbs +3 -1
- data/sig/nats/io/jetstream/manager.rbs +21 -1
- data/sig/nats/io/jetstream/pull_subscription.rbs +40 -2
- data/sig/nats/io/jetstream.rbs +9 -1
- data/sig/nats/io/kv/bucket_status.rbs +4 -0
- metadata +5 -6
data/lib/nats/io/jetstream.rb
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
require_relative "kv"
|
|
17
17
|
require_relative "jetstream/api"
|
|
18
18
|
require_relative "jetstream/errors"
|
|
19
|
+
require_relative "jetstream/header"
|
|
19
20
|
require_relative "jetstream/js"
|
|
20
21
|
require_relative "jetstream/manager"
|
|
21
22
|
require_relative "jetstream/msg"
|
|
@@ -69,7 +70,19 @@ module NATS
|
|
|
69
70
|
# @return [Boolean] Indicates whether the published message is a duplicate.
|
|
70
71
|
# @!attribute [domain] domain
|
|
71
72
|
# @return [String] JetStream Domain that processed the ack response.
|
|
72
|
-
|
|
73
|
+
# @!attribute [val] val
|
|
74
|
+
# @return [String] Value of the counter after the increment, set when publishing to a stream with counters enabled, unless the message was a duplicate.
|
|
75
|
+
# @!attribute [batch] batch
|
|
76
|
+
# @return [String] ID of the batch, set on the ack of an atomic batch commit.
|
|
77
|
+
# @!attribute [count] count
|
|
78
|
+
# @return [Integer] Number of messages the batch stored, set on the ack of an atomic batch commit.
|
|
79
|
+
PubAck = Struct.new(:stream, :seq, :duplicate, :domain, :val, :batch, :count,
|
|
80
|
+
keyword_init: true) do
|
|
81
|
+
# Fields added by newer servers are ignored, so that they cannot break publish.
|
|
82
|
+
def initialize(opts = {})
|
|
83
|
+
super(**opts.slice(*members))
|
|
84
|
+
end
|
|
85
|
+
end
|
|
73
86
|
|
|
74
87
|
# publish produces a message for JetStream.
|
|
75
88
|
#
|
|
@@ -77,21 +90,51 @@ module NATS
|
|
|
77
90
|
# @param payload [String] The payload of the message.
|
|
78
91
|
# @param params [Hash] Options to customize the publish message request.
|
|
79
92
|
# @option params [Float] :timeout Time to wait for an PubAck response or an error.
|
|
80
|
-
# @option params [Hash] :header NATS Headers to use for the message
|
|
93
|
+
# @option params [Hash] :header NATS Headers to use for the message; the
|
|
94
|
+
# options below replace those that they set.
|
|
81
95
|
# @option params [String] :stream Expected Stream to which the message is being published.
|
|
96
|
+
# @option params [Integer, Symbol] :ttl Seconds after which the stream
|
|
97
|
+
# removes the message, from 1 to 2**32, or :never to keep it past the max_age of
|
|
98
|
+
# the stream. The stream needs allow_msg_ttl (requires nats-server v2.11.0).
|
|
99
|
+
# @option params [Hash] :schedule Makes the message a schedule, which
|
|
100
|
+
# publishes it to the subject given as :target: once at the Time given
|
|
101
|
+
# as :at; every so many whole seconds, given as :every; or on a :cron
|
|
102
|
+
# expression with seconds, such as "0 30 * * * *", or "@hourly",
|
|
103
|
+
# "@daily", "@weekly", "@monthly" or "@yearly", in UTC or the IANA
|
|
104
|
+
# :time_zone, which only cron schedules take. :at is sent to the
|
|
105
|
+
# nanosecond. :source publishes the last message of that subject
|
|
106
|
+
# instead, :ttl gives the published messages a TTL, like the ttl
|
|
107
|
+
# option, and rollup: true rolls up the target with each. The target
|
|
108
|
+
# and source have to be subjects of the stream, which needs
|
|
109
|
+
# allow_msg_schedules, and allow_msg_ttl for :ttl. Another schedule
|
|
110
|
+
# on the subject replaces the schedule, and deleting its message stops
|
|
111
|
+
# it. Requires nats-server v2.12.0 for :at, :target and :ttl, and
|
|
112
|
+
# v2.14.0 for the others; v2.12 ignores :source and :rollup.
|
|
82
113
|
# @raise [NATS::Timeout] When it takes too long to receive an ack response.
|
|
114
|
+
# @raise [ArgumentError] When an option is invalid, before the message is sent.
|
|
115
|
+
# @raise [NATS::JetStream::Error::APIError] When the stream refuses the
|
|
116
|
+
# message, as a stream that does not allow TTLs refuses one with a TTL.
|
|
117
|
+
# @raise [NATS::JetStream::Error::NoStreamResponse] When no stream takes
|
|
118
|
+
# the subject.
|
|
83
119
|
# @return [PubAck] The pub ack response.
|
|
84
120
|
def publish(subject, payload = "", **params)
|
|
85
121
|
params[:timeout] ||= @opts[:timeout]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
params[:
|
|
122
|
+
# The options add to a copy of the header, which the caller may reuse.
|
|
123
|
+
options = {
|
|
124
|
+
Header::EXPECTED_STREAM => (params[:stream] if params[:stream]),
|
|
125
|
+
Header::MSG_TTL => (msg_ttl(params[:ttl]) if params[:ttl])
|
|
126
|
+
}.compact
|
|
127
|
+
if (schedule = params[:schedule])
|
|
128
|
+
raise ArgumentError.new("nats: invalid schedule #{schedule.inspect}, expected a Hash") unless schedule.is_a?(Hash)
|
|
129
|
+
|
|
130
|
+
options.merge!(schedule_header(**schedule))
|
|
89
131
|
end
|
|
132
|
+
header = options.empty? ? params[:header] : params[:header].to_h.merge(options)
|
|
90
133
|
|
|
91
134
|
# Send message with headers.
|
|
92
135
|
msg = NATS::Msg.new(subject: subject,
|
|
93
136
|
data: payload,
|
|
94
|
-
header:
|
|
137
|
+
header: header)
|
|
95
138
|
|
|
96
139
|
begin
|
|
97
140
|
resp = @nc.request_msg(msg, **params)
|
|
@@ -305,7 +348,7 @@ module NATS
|
|
|
305
348
|
# If attempting to bind, then this is a hard error.
|
|
306
349
|
raise e if params[:stream] && !multi_filter
|
|
307
350
|
|
|
308
|
-
config = if !
|
|
351
|
+
config = if !params[:config]
|
|
309
352
|
JetStream::API::ConsumerConfig.new
|
|
310
353
|
elsif params[:config].is_a?(JetStream::API::ConsumerConfig)
|
|
311
354
|
params[:config]
|
|
@@ -322,8 +365,8 @@ module NATS
|
|
|
322
365
|
add_consumer(stream, config)
|
|
323
366
|
end
|
|
324
367
|
|
|
325
|
-
|
|
326
|
-
sub = @nc.subscribe(
|
|
368
|
+
# Each pull gets a reply of its own under the subscription.
|
|
369
|
+
sub = @nc.subscribe("#{@nc.new_inbox}.*")
|
|
327
370
|
sub.extend(PullSubscription)
|
|
328
371
|
|
|
329
372
|
consumer = params[:consumer]
|
|
@@ -336,5 +379,51 @@ module NATS
|
|
|
336
379
|
)
|
|
337
380
|
sub
|
|
338
381
|
end
|
|
382
|
+
|
|
383
|
+
private
|
|
384
|
+
|
|
385
|
+
# msg_ttl formats a message TTL as the server takes it. Longer TTLs than
|
|
386
|
+
# 2**32 seconds, some 136 years, overflow in the server, which then
|
|
387
|
+
# removes the message at once.
|
|
388
|
+
def msg_ttl(ttl)
|
|
389
|
+
return "never" if ttl == :never
|
|
390
|
+
unless ttl.is_a?(Integer) && ttl.between?(1, 2**32)
|
|
391
|
+
raise ArgumentError.new("nats: invalid ttl #{ttl.inspect}, expected whole seconds from 1 to 2**32, or :never")
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
ttl.to_s
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# schedule_header makes the headers of a message schedule.
|
|
398
|
+
def schedule_header(target:, at: nil, every: nil, cron: nil, source: nil, ttl: nil, time_zone: nil, rollup: nil)
|
|
399
|
+
pattern = if at.is_a?(Time)
|
|
400
|
+
"@at #{at.getutc.iso8601(9)}"
|
|
401
|
+
elsif every.is_a?(Integer) && every >= 1
|
|
402
|
+
"@every #{every}s"
|
|
403
|
+
elsif nonempty_string?(cron)
|
|
404
|
+
cron
|
|
405
|
+
end
|
|
406
|
+
unless pattern && [at, every, cron].compact.size == 1
|
|
407
|
+
raise ArgumentError.new("nats: a schedule needs one of at: a Time, every: whole seconds from 1, or cron: an expression")
|
|
408
|
+
end
|
|
409
|
+
raise ArgumentError.new("nats: a schedule needs a target subject") unless nonempty_string?(target)
|
|
410
|
+
raise ArgumentError.new("nats: invalid schedule source #{source.inspect}") unless source.nil? || nonempty_string?(source)
|
|
411
|
+
raise ArgumentError.new("nats: invalid schedule time zone #{time_zone.inspect}") unless time_zone.nil? || nonempty_string?(time_zone)
|
|
412
|
+
raise ArgumentError.new("nats: only cron: schedules take a time zone") if time_zone && !cron
|
|
413
|
+
raise ArgumentError.new("nats: invalid schedule rollup #{rollup.inspect}") unless [nil, true, false].include?(rollup)
|
|
414
|
+
|
|
415
|
+
{
|
|
416
|
+
Header::SCHEDULE => pattern,
|
|
417
|
+
Header::SCHEDULE_TARGET => target,
|
|
418
|
+
Header::SCHEDULE_SOURCE => source,
|
|
419
|
+
Header::SCHEDULE_TTL => (msg_ttl(ttl) if ttl),
|
|
420
|
+
Header::SCHEDULE_TIME_ZONE => time_zone,
|
|
421
|
+
Header::SCHEDULE_ROLLUP => ("sub" if rollup)
|
|
422
|
+
}.compact
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def nonempty_string?(value)
|
|
426
|
+
value.is_a?(String) && !value.empty?
|
|
427
|
+
end
|
|
339
428
|
end
|
|
340
429
|
end
|
data/lib/nats/io/kv/api.rb
CHANGED
|
@@ -30,6 +30,10 @@ module NATS
|
|
|
30
30
|
:republish,
|
|
31
31
|
:direct,
|
|
32
32
|
:validate_keys,
|
|
33
|
+
# Compress the bucket's stream with S2 (requires nats-server v2.10.0).
|
|
34
|
+
:compression,
|
|
35
|
+
# Freeform metadata of the bucket (requires nats-server v2.10.0).
|
|
36
|
+
:metadata,
|
|
33
37
|
keyword_init: true
|
|
34
38
|
) do
|
|
35
39
|
def initialize(opts = {})
|
|
@@ -35,6 +35,18 @@ module NATS
|
|
|
35
35
|
def ttl
|
|
36
36
|
@stream_info.config.max_age / ::NATS::NANOSECONDS
|
|
37
37
|
end
|
|
38
|
+
|
|
39
|
+
# Whether the bucket's stream is compressed (requires nats-server v2.10.0).
|
|
40
|
+
def compressed?
|
|
41
|
+
compression = @stream_info.config.compression
|
|
42
|
+
!compression.nil? && compression != "none"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The bucket's metadata, including the metadata that the server sets
|
|
46
|
+
# (requires nats-server v2.10.0).
|
|
47
|
+
def metadata
|
|
48
|
+
@stream_info.config.metadata
|
|
49
|
+
end
|
|
38
50
|
end
|
|
39
51
|
end
|
|
40
52
|
end
|
data/lib/nats/io/kv/manager.rb
CHANGED
|
@@ -59,6 +59,10 @@ module NATS
|
|
|
59
59
|
raise NATS::KeyValue::KeyHistoryTooLargeError
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
unless [true, false, nil].include?(config.compression)
|
|
63
|
+
raise ArgumentError.new("nats: compression must be true or false")
|
|
64
|
+
end
|
|
65
|
+
|
|
62
66
|
stream = JetStream::API::StreamConfig.new(
|
|
63
67
|
name: "KV_#{config.bucket}",
|
|
64
68
|
description: config.description,
|
|
@@ -76,7 +80,9 @@ module NATS
|
|
|
76
80
|
max_msgs_per_subject: config.history,
|
|
77
81
|
num_replicas: config.replicas,
|
|
78
82
|
storage: config.storage,
|
|
79
|
-
republish: config.republish
|
|
83
|
+
republish: config.republish,
|
|
84
|
+
compression: config.compression ? "s2" : nil,
|
|
85
|
+
metadata: config.metadata
|
|
80
86
|
)
|
|
81
87
|
|
|
82
88
|
si = add_stream(stream)
|
data/lib/nats/io/kv.rb
CHANGED
|
@@ -31,7 +31,7 @@ module NATS
|
|
|
31
31
|
ROLLUP = "Nats-Rollup"
|
|
32
32
|
|
|
33
33
|
VALID_BUCKET_RE = /\A[a-zA-Z0-9_-]+$/
|
|
34
|
-
VALID_KEY_RE = /\A[-\/_
|
|
34
|
+
VALID_KEY_RE = /\A[-\/_=.a-zA-Z0-9]+$/
|
|
35
35
|
|
|
36
36
|
class << self
|
|
37
37
|
def is_valid_key(key)
|
|
@@ -258,7 +258,8 @@ module NATS
|
|
|
258
258
|
CONSUMER_STALLED_HDR = "Nats-Consumer-Stalled"
|
|
259
259
|
|
|
260
260
|
# watch will be signaled when a key that matches the keys
|
|
261
|
-
# pattern is updated.
|
|
261
|
+
# pattern is updated. keys can also be an Array of patterns
|
|
262
|
+
# (requires nats-server v2.10.0); an empty Array watches all keys.
|
|
262
263
|
# The first update after starting the watch is nil in case
|
|
263
264
|
# there are no pending updates.
|
|
264
265
|
def watch(keys, params = {})
|
|
@@ -267,13 +268,18 @@ module NATS
|
|
|
267
268
|
params[:ignore_deletes] ||= false
|
|
268
269
|
params[:idle_heartbeat] ||= 5 # seconds
|
|
269
270
|
params[:inactive_threshold] ||= 5 * 60 # 5 minutes
|
|
270
|
-
subject =
|
|
271
|
+
subject = if keys.is_a?(Array)
|
|
272
|
+
keys = [">"] if keys.empty?
|
|
273
|
+
keys.map { |key| "#{@pre}#{key}" }
|
|
274
|
+
else
|
|
275
|
+
"#{@pre}#{keys}"
|
|
276
|
+
end
|
|
271
277
|
init_setup = new_cond
|
|
272
278
|
init_setup_done = false
|
|
273
279
|
nc = @js.nc
|
|
274
280
|
watcher = KeyWatcher.new(@js)
|
|
275
281
|
|
|
276
|
-
deliver_policy = if !
|
|
282
|
+
deliver_policy = if !params[:include_history]
|
|
277
283
|
"last_per_subject"
|
|
278
284
|
end
|
|
279
285
|
|
|
@@ -294,7 +300,7 @@ module NATS
|
|
|
294
300
|
}
|
|
295
301
|
|
|
296
302
|
# watch_updates callback.
|
|
297
|
-
sub = @js.subscribe(subject, config: ordered) do |msg|
|
|
303
|
+
sub = @js.subscribe(subject, stream: @stream, config: ordered) do |msg|
|
|
298
304
|
synchronize do
|
|
299
305
|
if !init_setup_done
|
|
300
306
|
init_setup.wait(@js.opts[:timeout])
|
|
@@ -327,15 +333,9 @@ module NATS
|
|
|
327
333
|
meta = msg.metadata
|
|
328
334
|
watcher.synchronize { watcher._active = true }
|
|
329
335
|
# Track the sequences
|
|
330
|
-
#
|
|
331
|
-
# $JS.ACK.KV_TEST.CKRGrWpf.1.10.10.1739859923871837000.0
|
|
332
|
-
#
|
|
333
|
-
tokens = msg.reply.split(".")
|
|
334
|
-
sseq = tokens[5]
|
|
335
|
-
dseq = tokens[6]
|
|
336
336
|
watcher.synchronize do
|
|
337
|
-
watcher._dseq =
|
|
338
|
-
watcher._sseq =
|
|
337
|
+
watcher._dseq = meta.sequence.consumer + 1
|
|
338
|
+
watcher._sseq = meta.sequence.stream
|
|
339
339
|
end
|
|
340
340
|
|
|
341
341
|
# Keys() handling
|
|
@@ -385,6 +385,9 @@ module NATS
|
|
|
385
385
|
begin
|
|
386
386
|
cinfo = sub.consumer_info
|
|
387
387
|
stream_name = cinfo.stream_name
|
|
388
|
+
# Snapshot the keys filter too: subscribe set it on the consumer,
|
|
389
|
+
# not on ordered, and a recreated consumer needs it.
|
|
390
|
+
filter = cinfo.config.to_h.slice(:filter_subject, :filter_subjects)
|
|
388
391
|
|
|
389
392
|
synchronize do
|
|
390
393
|
init_setup_done = true
|
|
@@ -423,9 +426,11 @@ module NATS
|
|
|
423
426
|
current
|
|
424
427
|
}
|
|
425
428
|
if !active
|
|
426
|
-
ccreq = ordered.
|
|
429
|
+
ccreq = ordered.merge(filter)
|
|
427
430
|
ccreq[:deliver_policy] = "by_start_sequence"
|
|
428
|
-
|
|
431
|
+
# Resume after the last entry. With none yet, that is the start of
|
|
432
|
+
# the stream, so earlier revisions of the keys are replayed too.
|
|
433
|
+
ccreq[:opt_start_seq] = watcher._sseq + 1
|
|
429
434
|
ccreq[:deliver_subject] = deliver_subject
|
|
430
435
|
ccreq[:idle_heartbeat] = ordered[:idle_heartbeat]
|
|
431
436
|
ccreq[:inactive_threshold] = ordered[:inactive_threshold]
|
|
@@ -465,6 +470,7 @@ module NATS
|
|
|
465
470
|
class KeyWatcher
|
|
466
471
|
include MonitorMixin
|
|
467
472
|
include Enumerable
|
|
473
|
+
|
|
468
474
|
attr_accessor :received, :pending, :_sub, :_updates, :_init_done, :_watcher_cond
|
|
469
475
|
attr_accessor :_sseq, :_dseq, :_active, :_hb_task
|
|
470
476
|
|
data/lib/nats/io/parser.rb
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
module NATS
|
|
18
18
|
module Protocol
|
|
19
19
|
MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i
|
|
20
|
-
HMSG = /\AHMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(
|
|
20
|
+
HMSG = /\AHMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\s+(\d+)\r\n/i
|
|
21
21
|
OK = /\A\+OK\s*\r\n/i
|
|
22
22
|
ERR = /\A-ERR\s+('.+')?\r\n/i
|
|
23
23
|
PING = /\APING\s*\r\n/i
|
|
@@ -98,12 +98,12 @@ module NATS
|
|
|
98
98
|
return unless @needed && @buf.bytesize >= (@needed + CR_LF_SIZE)
|
|
99
99
|
if @header_needed
|
|
100
100
|
hbuf = @buf.slice(0, @header_needed)
|
|
101
|
-
payload = @buf.slice(@header_needed,
|
|
101
|
+
payload = @buf.slice(@header_needed, @needed - @header_needed)
|
|
102
102
|
@nc.send(:process_msg, @sub, @sid, @reply, payload, hbuf)
|
|
103
103
|
else
|
|
104
104
|
@nc.send(:process_msg, @sub, @sid, @reply, @buf.slice(0, @needed), nil)
|
|
105
105
|
end
|
|
106
|
-
@buf = @buf.slice(
|
|
106
|
+
@buf = @buf.slice(@needed + CR_LF_SIZE, @buf.bytesize)
|
|
107
107
|
|
|
108
108
|
@sub = @sid = @reply = @needed = @header_needed = nil
|
|
109
109
|
@parse_state = AWAITING_CONTROL_LINE
|
data/lib/nats/io/subscription.rb
CHANGED
|
@@ -89,18 +89,19 @@ module NATS
|
|
|
89
89
|
def next_msg(opts = {})
|
|
90
90
|
timeout = opts[:timeout] ||= 0.5
|
|
91
91
|
synchronize do
|
|
92
|
-
|
|
92
|
+
if @pending_queue.empty?
|
|
93
|
+
# Wait for a bit until getting a signal.
|
|
94
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
|
95
|
+
wait_for_msgs_cond.wait(timeout)
|
|
96
|
+
end
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
MonotonicTime.with_nats_timeout(timeout) do
|
|
96
|
-
wait_for_msgs_cond.wait(timeout)
|
|
98
|
+
raise NATS::Timeout if @pending_queue.empty?
|
|
97
99
|
end
|
|
98
100
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
end
|
|
101
|
+
# Decrease pending size since consumed already
|
|
102
|
+
msg = @pending_queue.pop
|
|
103
|
+
self.pending_size -= msg.data.size
|
|
104
|
+
msg
|
|
104
105
|
end
|
|
105
106
|
end
|
|
106
107
|
|
|
@@ -148,13 +149,20 @@ module NATS
|
|
|
148
149
|
msg = pending_queue.pop(true)
|
|
149
150
|
process(msg)
|
|
150
151
|
rescue ThreadError # queue is empty
|
|
151
|
-
|
|
152
|
+
# No release here: the ensure below releases the permit; a second
|
|
153
|
+
# release would grow the semaphore beyond processing_concurrency.
|
|
152
154
|
ensure
|
|
153
155
|
concurrency_semaphore.release
|
|
154
156
|
[concurrency_semaphore.available_permits, pending_queue.size].min.times do
|
|
155
157
|
enqueue_processing(executor)
|
|
156
158
|
end
|
|
157
159
|
end
|
|
160
|
+
rescue Concurrent::RejectedExecutionError
|
|
161
|
+
# The executor is being shut down (the connection is closing,
|
|
162
|
+
# draining or reconnecting). Release the permit so the
|
|
163
|
+
# subscription can process messages again after a reconnect;
|
|
164
|
+
# the message stays in pending_queue.
|
|
165
|
+
concurrency_semaphore.release
|
|
158
166
|
end
|
|
159
167
|
end
|
|
160
168
|
end
|
data/lib/nats/io/version.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Copyright 2016-
|
|
3
|
+
# Copyright 2016-2026 The NATS Authors
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
|
6
6
|
# You may obtain a copy of the License at
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
module NATS
|
|
18
18
|
module IO
|
|
19
19
|
# VERSION is the version of the client announced on CONNECT to the server.
|
|
20
|
-
VERSION = "2.
|
|
20
|
+
VERSION = "2.7.0"
|
|
21
21
|
|
|
22
22
|
# LANG is the lang runtime of the client announced on CONNECT to the server.
|
|
23
23
|
LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
|
data/lib/nats/io/websocket.rb
CHANGED
|
@@ -47,23 +47,28 @@ module NATS
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def read(max_bytes = MAX_SOCKET_READ_BYTES, deadline = nil)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
result << msg
|
|
50
|
+
# Hand over what read_line decoded beyond its last line first,
|
|
51
|
+
# otherwise those bytes are lost once the read loop takes over.
|
|
52
|
+
if @line_buf && !@line_buf.empty?
|
|
53
|
+
return @line_buf.slice!(0, @line_buf.bytesize)
|
|
55
54
|
end
|
|
56
|
-
|
|
55
|
+
|
|
56
|
+
read_frames(max_bytes, deadline)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def read_line(deadline = nil)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
@line_buf ||= +""
|
|
61
|
+
loop do
|
|
62
|
+
if (idx = @line_buf =~ /\r?\n/)
|
|
63
|
+
return @line_buf.slice!(0, idx + Regexp.last_match(0).length)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Pull more data from the wire; never from @line_buf itself,
|
|
67
|
+
# or a partial line would be fed back forever.
|
|
68
|
+
data = read_frames(MAX_SOCKET_READ_BYTES, deadline)
|
|
69
|
+
return nil unless data
|
|
70
|
+
@line_buf << data
|
|
65
71
|
end
|
|
66
|
-
result.join
|
|
67
72
|
end
|
|
68
73
|
|
|
69
74
|
def write(data, deadline = nil)
|
|
@@ -72,6 +77,21 @@ module NATS
|
|
|
72
77
|
frame = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: :binary, version: @handshake.version)
|
|
73
78
|
super(frame.to_s)
|
|
74
79
|
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Reads from the socket and returns the payloads of all complete
|
|
84
|
+
# frames decoded so far.
|
|
85
|
+
def read_frames(max_bytes, deadline)
|
|
86
|
+
data = Socket.instance_method(:read).bind_call(self, max_bytes, deadline)
|
|
87
|
+
@frame << data
|
|
88
|
+
[].tap do |parts|
|
|
89
|
+
while (msg = @frame.next)
|
|
90
|
+
payload = msg.respond_to?(:data) ? msg.data : msg
|
|
91
|
+
parts << payload if payload
|
|
92
|
+
end
|
|
93
|
+
end.join
|
|
94
|
+
end
|
|
75
95
|
end
|
|
76
96
|
end
|
|
77
97
|
end
|
|
@@ -6,8 +6,8 @@ module NATS
|
|
|
6
6
|
REGEX = {
|
|
7
7
|
name: /[A-Za-z0-9\-_]+$/,
|
|
8
8
|
version: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,
|
|
9
|
-
subject: /^[^ >]
|
|
10
|
-
queue: /^[^ >]
|
|
9
|
+
subject: /^[^ >]*>?$/,
|
|
10
|
+
queue: /^[^ >]*>?$/
|
|
11
11
|
}.freeze
|
|
12
12
|
|
|
13
13
|
class << self
|
|
@@ -7,18 +7,27 @@ module NATS
|
|
|
7
7
|
class SequenceInfo < Struct[Integer]
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class PriorityGroupState < Struct[String | Time]
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class ConsumerInfo < Struct[String | NATS::JetStream::API::ConsumerConfig | NATS::JetStream::API::SequenceInfo | Integer | Hash[Symbol, untyped] | Time | bool | Array[NATS::JetStream::API::PriorityGroupState]]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class ConsumerConfig < Struct[String | Integer | Time | Array[String]]
|
|
14
17
|
def to_json: (*Hash[untyped, untyped]) -> String
|
|
15
18
|
end
|
|
16
19
|
|
|
20
|
+
class ConsumerPauseResponse < Struct[bool | Time | Integer]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class ConsumerResetResponse < Struct[NATS::JetStream::API::ConsumerInfo | Integer]
|
|
24
|
+
end
|
|
25
|
+
|
|
17
26
|
class StreamConfig < Struct[String | Hash[Symbol, untyped] | NATS::JetStream::API::StreamState | bool | Integer]
|
|
18
27
|
def to_json: (*Hash[untyped, untyped]) -> String
|
|
19
28
|
end
|
|
20
29
|
|
|
21
|
-
class StreamInfo < Struct[String | Hash[Symbol, untyped]]
|
|
30
|
+
class StreamInfo < Struct[String | Hash[Symbol, untyped] | Array[Hash[Symbol, untyped]] | Time]
|
|
22
31
|
end
|
|
23
32
|
|
|
24
33
|
class StreamState < Struct[Integer]
|
|
@@ -49,6 +49,18 @@ module NATS
|
|
|
49
49
|
|
|
50
50
|
class BadRequest < NATS::JetStream::Error::APIError
|
|
51
51
|
end
|
|
52
|
+
|
|
53
|
+
class ConsumerAlreadyExists < NATS::JetStream::Error::BadRequest
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class ConsumerDoesNotExist < NATS::JetStream::Error::BadRequest
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class ConsumerInvalidReset < NATS::JetStream::Error::BadRequest
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class PinIdMismatch < NATS::JetStream::Error::APIError
|
|
63
|
+
end
|
|
52
64
|
end
|
|
53
65
|
end
|
|
54
66
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module NATS
|
|
2
|
+
class JetStream
|
|
3
|
+
module Header
|
|
4
|
+
MSG_ID: 'Nats-Msg-Id'
|
|
5
|
+
EXPECTED_STREAM: 'Nats-Expected-Stream'
|
|
6
|
+
EXPECTED_LAST_SEQUENCE: 'Nats-Expected-Last-Sequence'
|
|
7
|
+
EXPECTED_LAST_SUBJECT_SEQUENCE: 'Nats-Expected-Last-Subject-Sequence'
|
|
8
|
+
EXPECTED_LAST_SUBJECT_SEQUENCE_SUBJECT: 'Nats-Expected-Last-Subject-Sequence-Subject'
|
|
9
|
+
EXPECTED_LAST_MSG_ID: 'Nats-Expected-Last-Msg-Id'
|
|
10
|
+
ROLLUP: 'Nats-Rollup'
|
|
11
|
+
MSG_TTL: 'Nats-TTL'
|
|
12
|
+
MARKER_REASON: 'Nats-Marker-Reason'
|
|
13
|
+
STREAM: 'Nats-Stream'
|
|
14
|
+
SUBJECT: 'Nats-Subject'
|
|
15
|
+
SEQUENCE: 'Nats-Sequence'
|
|
16
|
+
TIME_STAMP: 'Nats-Time-Stamp'
|
|
17
|
+
LAST_SEQUENCE: 'Nats-Last-Sequence'
|
|
18
|
+
STREAM_SOURCE: 'Nats-Stream-Source'
|
|
19
|
+
BATCH_ID: 'Nats-Batch-Id'
|
|
20
|
+
BATCH_SEQUENCE: 'Nats-Batch-Sequence'
|
|
21
|
+
BATCH_COMMIT: 'Nats-Batch-Commit'
|
|
22
|
+
SCHEDULE: 'Nats-Schedule'
|
|
23
|
+
SCHEDULE_TARGET: 'Nats-Schedule-Target'
|
|
24
|
+
SCHEDULE_SOURCE: 'Nats-Schedule-Source'
|
|
25
|
+
SCHEDULE_TTL: 'Nats-Schedule-TTL'
|
|
26
|
+
SCHEDULE_TIME_ZONE: 'Nats-Schedule-Time-Zone'
|
|
27
|
+
SCHEDULE_ROLLUP: 'Nats-Schedule-Rollup'
|
|
28
|
+
SCHEDULER: 'Nats-Scheduler'
|
|
29
|
+
SCHEDULE_NEXT: 'Nats-Schedule-Next'
|
|
30
|
+
REQUIRED_API_LEVEL: 'Nats-Required-Api-Level'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -6,11 +6,13 @@ module NATS
|
|
|
6
6
|
# class << self
|
|
7
7
|
def self.next_req_to_json: (Hash[Symbol, untyped]) -> String
|
|
8
8
|
|
|
9
|
+
def self.parse_time: (String?) -> Time?
|
|
10
|
+
|
|
9
11
|
def self.is_status_msg: (NATS::Msg) -> bool
|
|
10
12
|
|
|
11
13
|
def self.check_503_error: (NATS::Msg) -> void
|
|
12
14
|
|
|
13
|
-
def self.from_msg: (NATS::Msg) -> NATS::JetStream::API::Error
|
|
15
|
+
def self.from_msg: (NATS::Msg) -> (NATS::JetStream::API::Error | NATS::JetStream::Error::PinIdMismatch)
|
|
14
16
|
|
|
15
17
|
def self.from_error: (Hash[Symbol, untyped]) -> (
|
|
16
18
|
NATS::JetStream::Error::ServiceUnavailable |
|
|
@@ -9,12 +9,24 @@ module NATS
|
|
|
9
9
|
|
|
10
10
|
def delete_stream: (String, ?Hash[Symbol, untyped]) -> bool
|
|
11
11
|
|
|
12
|
-
def add_consumer: (String, JetStream::API::ConsumerConfig, ?Hash[Symbol, untyped]) -> JetStream::API::ConsumerInfo
|
|
12
|
+
def add_consumer: (String, JetStream::API::ConsumerConfig | Hash[Symbol, untyped], ?Hash[Symbol, untyped]) -> JetStream::API::ConsumerInfo
|
|
13
|
+
|
|
14
|
+
def create_consumer: (String, JetStream::API::ConsumerConfig | Hash[Symbol, untyped], ?Hash[Symbol, untyped]) -> JetStream::API::ConsumerInfo
|
|
15
|
+
|
|
16
|
+
def update_consumer: (String, JetStream::API::ConsumerConfig | Hash[Symbol, untyped], ?Hash[Symbol, untyped]) -> JetStream::API::ConsumerInfo
|
|
13
17
|
|
|
14
18
|
def consumer_info: (String, String, ?Hash[Symbol, untyped]) -> JetStream::API::ConsumerInfo
|
|
15
19
|
|
|
16
20
|
def delete_consumer: (String, String, ?Hash[Symbol, untyped]) -> bool
|
|
17
21
|
|
|
22
|
+
def pause_consumer: (String, String, Time | String, ?Hash[Symbol, untyped]) -> JetStream::API::ConsumerPauseResponse
|
|
23
|
+
|
|
24
|
+
def resume_consumer: (String, String, ?Hash[Symbol, untyped]) -> JetStream::API::ConsumerPauseResponse
|
|
25
|
+
|
|
26
|
+
def unpin_consumer: (String, String, String, ?Hash[Symbol, untyped]) -> bool
|
|
27
|
+
|
|
28
|
+
def reset_consumer: (String, String, ?Hash[Symbol, untyped]) -> JetStream::API::ConsumerResetResponse
|
|
29
|
+
|
|
18
30
|
def find_stream_name_by_subject: (String, ?Hash[Symbol, untyped]) -> String
|
|
19
31
|
|
|
20
32
|
def get_msg: (String, ?Hash[Symbol, untyped]) -> JetStream::API::RawStreamMsg
|
|
@@ -25,9 +37,17 @@ module NATS
|
|
|
25
37
|
|
|
26
38
|
private
|
|
27
39
|
|
|
40
|
+
def upsert_consumer: (String, JetStream::API::ConsumerConfig | Hash[Symbol, untyped], String?, Hash[Symbol, untyped]) -> JetStream::API::ConsumerInfo
|
|
41
|
+
|
|
42
|
+
def request_pause: (String, String, Hash[Symbol, String], Hash[Symbol, untyped]) -> JetStream::API::ConsumerPauseResponse
|
|
43
|
+
|
|
44
|
+
def rfc3339: (Time | String | nil) -> String?
|
|
45
|
+
|
|
28
46
|
def api_request: (String, String, ?Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
|
|
29
47
|
|
|
30
48
|
def _lift_msg_to_raw_msg: (NATS::Msg) -> JetStream::API::RawStreamMsg
|
|
49
|
+
|
|
50
|
+
def stream_config_json: (JetStream::API::StreamConfig) -> String
|
|
31
51
|
end
|
|
32
52
|
end
|
|
33
53
|
end
|