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
|
@@ -39,231 +39,329 @@ module NATS
|
|
|
39
39
|
#
|
|
40
40
|
# @!visibility public
|
|
41
41
|
module PullSubscription
|
|
42
|
+
# For as long, in seconds, a server with leafnodes or gateways delivers
|
|
43
|
+
# to a new pull without checking for interest in its reply first
|
|
44
|
+
# (defaultGatewayRecentSubExpiration of nats-server).
|
|
45
|
+
UNCHECKED_PULL_AGE = 2
|
|
46
|
+
|
|
47
|
+
def self.extended(sub)
|
|
48
|
+
sub.instance_eval do
|
|
49
|
+
# The count of pulls, which names their replies, and the statuses
|
|
50
|
+
# that other fetches took for the fetches waiting for their pulls.
|
|
51
|
+
@pulls = 0
|
|
52
|
+
@pull_ends = {}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
42
56
|
# next_msg is not available for pull based subscriptions.
|
|
43
57
|
# @raise [NATS::JetStream::Error]
|
|
44
58
|
def next_msg(params = {})
|
|
45
59
|
raise ::NATS::JetStream::Error.new("nats: pull subscription cannot use next_msg")
|
|
46
60
|
end
|
|
47
61
|
|
|
48
|
-
# fetch
|
|
62
|
+
# fetch pulls a batch of messages from a pull consumer. It returns as
|
|
63
|
+
# soon as it has the batch; otherwise it waits for more messages, and
|
|
64
|
+
# returns those that came once its timeout is up. With :no_wait, it
|
|
65
|
+
# takes what the server delivers at once, without waiting for more.
|
|
66
|
+
#
|
|
67
|
+
# Given a block, it passes each delivery to it as it comes, in the
|
|
68
|
+
# calling thread. Otherwise the messages wait for the fetch to end. The
|
|
69
|
+
# server delivers again those not acked within the ack_wait of the
|
|
70
|
+
# consumer, and the fetch returns each message once, as last delivered.
|
|
71
|
+
# Keep the timeout below the ack_wait, or ack in a block. When the block
|
|
72
|
+
# breaks off the fetch, what the server still delivers to its pull is
|
|
73
|
+
# left to the next fetch.
|
|
74
|
+
#
|
|
75
|
+
# @example Ack each message as it comes.
|
|
76
|
+
#
|
|
77
|
+
# psub.fetch(100, timeout: 60) do |msg|
|
|
78
|
+
# process(msg)
|
|
79
|
+
# msg.ack
|
|
80
|
+
# end
|
|
49
81
|
#
|
|
50
82
|
# @param batch [Fixnum] Number of messages to pull from the stream.
|
|
51
83
|
# @param params [Hash] Options to customize the fetch request.
|
|
52
|
-
# @option params [Float] :timeout
|
|
84
|
+
# @option params [Float] :timeout How long to wait for the batch, 5 seconds
|
|
85
|
+
# by default. The fetch then waits up to a second longer for the server
|
|
86
|
+
# to end its pull, so that it leaves no message behind. With :no_wait,
|
|
87
|
+
# how long to wait for what the server holds back, 1 second by default.
|
|
88
|
+
# @option params [Boolean] :no_wait Take what the server delivers at once,
|
|
89
|
+
# without waiting for more messages: none, an empty Array, when none are
|
|
90
|
+
# pending, or when other pulls wait for more than are pending. While the
|
|
91
|
+
# consumer may not deliver more until some are acked (max_ack_pending),
|
|
92
|
+
# the server holds the pull back, and the fetch returns what it got once
|
|
93
|
+
# its timeout is up. Messages that come later are left to the next fetch.
|
|
94
|
+
# @option params [String] :group Priority group to pull from, which the pulls
|
|
95
|
+
# of a consumer with a priority policy must name (requires nats-server v2.11.0).
|
|
96
|
+
# @option params [Integer] :min_pending With the overflow priority policy, deliver
|
|
97
|
+
# only while the consumer has at least this many messages pending; at least 1.
|
|
98
|
+
# @option params [Integer] :min_ack_pending With the overflow priority policy, deliver
|
|
99
|
+
# only while at least this many messages await acks; at least 1. Given both
|
|
100
|
+
# minimums, either will do.
|
|
101
|
+
# @option params [Integer] :priority With the prioritized priority policy, the priority
|
|
102
|
+
# of the pull, from 0, served first, to 9 (requires nats-server v2.12.0).
|
|
103
|
+
# @yieldparam msg [NATS::Msg] Each delivery, as it comes.
|
|
53
104
|
# @return [Array<NATS::Msg>]
|
|
54
|
-
|
|
105
|
+
# @raise [NATS::Timeout] When a fetch that waits got no messages before its timeout.
|
|
106
|
+
# @raise [ArgumentError] When the timeout is not a finite positive number, or a
|
|
107
|
+
# minimum is not an integer of at least 1.
|
|
108
|
+
# @raise [NATS::JetStream::Error::PinIdMismatch] With the pinned_client priority
|
|
109
|
+
# policy, when the server turned the pull of the fetch away before it got
|
|
110
|
+
# messages, as the subscription is no longer pinned: its pin expired or it
|
|
111
|
+
# was unpinned. The next fetch can be pinned again. The server counts the
|
|
112
|
+
# priority_timeout of the consumer from each pull it gets, not while a pull
|
|
113
|
+
# waits, so keep the fetch timeout, plus the time between fetches, below it.
|
|
114
|
+
# @raise [NATS::JetStream::Error] When the server ended the pull of the fetch
|
|
115
|
+
# with an error before it got messages.
|
|
116
|
+
def fetch(batch = 1, params = {}, &block)
|
|
55
117
|
if batch < 1
|
|
56
118
|
raise ::NATS::JetStream::Error.new("nats: invalid batch size")
|
|
57
119
|
end
|
|
120
|
+
[:min_pending, :min_ack_pending].each do |min|
|
|
121
|
+
value = params[min]
|
|
122
|
+
next if value.nil? || (value.is_a?(Integer) && value >= 1)
|
|
58
123
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
124
|
+
raise ArgumentError.new("nats: #{min} should be an integer of at least 1")
|
|
125
|
+
end
|
|
126
|
+
timeout = params[:timeout] || (params[:no_wait] ? 1 : 5)
|
|
127
|
+
unless timeout.is_a?(Numeric) && timeout.positive? && timeout.finite?
|
|
128
|
+
raise ArgumentError.new("nats: timeout should be a finite positive number")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
deadline = MonotonicTime.now + timeout
|
|
132
|
+
msgs = []
|
|
133
|
+
# Take what earlier pulls delivered first.
|
|
134
|
+
while msgs.size < batch && (msg = next_pending)
|
|
135
|
+
collect(msgs, msg, &block)
|
|
136
|
+
end
|
|
137
|
+
return msgs if msgs.size == batch || (!msgs.empty? && MonotonicTime.now >= deadline)
|
|
138
|
+
|
|
139
|
+
# Like the nats.go jetstream package and nats.rs, pull once, for the
|
|
140
|
+
# rest of the batch.
|
|
62
141
|
next_req = {
|
|
63
|
-
batch: batch
|
|
142
|
+
batch: batch - msgs.size,
|
|
143
|
+
**params.slice(:group, :min_pending, :min_ack_pending, :priority)
|
|
64
144
|
}
|
|
145
|
+
if params[:no_wait]
|
|
146
|
+
next_req[:no_wait] = true
|
|
147
|
+
pull_pending(msgs, next_req, deadline, &block)
|
|
148
|
+
else
|
|
149
|
+
# The pull expires with the timeout. As nats.go does, wait up to a
|
|
150
|
+
# second longer for the server to end it, so that it leaves nothing
|
|
151
|
+
# behind.
|
|
152
|
+
next_req[:expires] = pull_expires(deadline)
|
|
153
|
+
pull_and_wait(msgs, next_req, deadline + 1, &block)
|
|
154
|
+
raise ::NATS::Timeout.new("nats: fetch timeout") if msgs.empty?
|
|
155
|
+
end
|
|
156
|
+
msgs
|
|
157
|
+
end
|
|
65
158
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
####################################################
|
|
74
|
-
|
|
75
|
-
# Check if there is any pending message in the queue that is
|
|
76
|
-
# ready to be consumed.
|
|
77
|
-
synchronize do
|
|
78
|
-
unless @pending_queue.empty?
|
|
79
|
-
msg = @pending_queue.pop
|
|
80
|
-
@pending_size -= msg.data.size
|
|
81
|
-
# Check for a no msgs response status.
|
|
82
|
-
if JS.is_status_msg(msg)
|
|
83
|
-
case msg.header["Status"]
|
|
84
|
-
when JS::Status::NoMsgs
|
|
85
|
-
nil
|
|
86
|
-
when JS::Status::RequestTimeout
|
|
87
|
-
# Skip
|
|
88
|
-
else
|
|
89
|
-
raise JS.from_msg(msg)
|
|
90
|
-
end
|
|
91
|
-
else
|
|
92
|
-
msgs << msg
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
159
|
+
# consumer_info retrieves the current status of the pull subscription consumer.
|
|
160
|
+
# @param params [Hash] Options to customize API request.
|
|
161
|
+
# @option params [Float] :timeout Time to wait for response.
|
|
162
|
+
# @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
|
|
163
|
+
def consumer_info(params = {})
|
|
164
|
+
@jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
|
|
165
|
+
end
|
|
96
166
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
duration = MonotonicTime.since(t)
|
|
114
|
-
if duration > timeout
|
|
115
|
-
raise ::NATS::Timeout.new("nats: fetch timeout")
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Should have received at least a message at this point,
|
|
119
|
-
# if that is not the case then error already.
|
|
120
|
-
if JS.is_status_msg(msgs.first)
|
|
121
|
-
msg = msgs.first
|
|
122
|
-
case msg.header[JS::Header::Status]
|
|
123
|
-
when JS::Status::RequestTimeout
|
|
124
|
-
raise NATS::Timeout.new("nats: fetch request timeout")
|
|
125
|
-
else
|
|
126
|
-
raise JS.from_msg(msgs.first)
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
when batch > 1
|
|
131
|
-
####################################################
|
|
132
|
-
# Fetch (n) #
|
|
133
|
-
####################################################
|
|
134
|
-
|
|
135
|
-
# Check if there already enough in the pending buffer.
|
|
136
|
-
synchronize do
|
|
137
|
-
if batch <= @pending_queue.size
|
|
138
|
-
batch.times do
|
|
139
|
-
msg = @pending_queue.pop
|
|
140
|
-
@pending_size -= msg.data.size
|
|
141
|
-
|
|
142
|
-
# Check for a no msgs response status.
|
|
143
|
-
if JS.is_status_msg(msg)
|
|
144
|
-
case msg.header[JS::Header::Status]
|
|
145
|
-
when JS::Status::NoMsgs, JS::Status::RequestTimeout
|
|
146
|
-
# Skip these
|
|
147
|
-
next
|
|
148
|
-
else
|
|
149
|
-
raise JS.from_msg(msg)
|
|
150
|
-
end
|
|
151
|
-
else
|
|
152
|
-
msgs << msg
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
return msgs
|
|
157
|
-
end
|
|
158
|
-
end
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
# pull_and_wait sends a pull that waits for messages, with a reply of
|
|
170
|
+
# its own under the subscription, as ADR-13 has it. The messages it
|
|
171
|
+
# delivers carry the subjects of the stream, so that fetches share
|
|
172
|
+
# them, but the status that ends it comes to its reply.
|
|
173
|
+
def pull_and_wait(msgs, next_req, deadline, &block)
|
|
174
|
+
reply = synchronize { "#{@subject.chomp("*")}#{@pulls += 1}" }
|
|
175
|
+
synchronize { @pull_ends[reply] = nil }
|
|
176
|
+
pin_id = pull(next_req, reply)
|
|
177
|
+
receive(msgs, next_req[:batch], pin_id, -> { wait_pending(reply, deadline) }, &block)
|
|
178
|
+
ensure
|
|
179
|
+
synchronize { @pull_ends.delete(reply) }
|
|
180
|
+
end
|
|
159
181
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
182
|
+
# pull_pending sends a pull that does not wait, with a subscription of
|
|
183
|
+
# its own: unsubscribing ends the pull if the server holds it, as it
|
|
184
|
+
# does while the consumer may not deliver more messages.
|
|
185
|
+
def pull_pending(msgs, next_req, deadline, &block)
|
|
186
|
+
inbox = @nc.subscribe(@nc.new_inbox)
|
|
187
|
+
pulled = MonotonicTime.now
|
|
188
|
+
pin_id = pull(next_req, inbox.subject)
|
|
189
|
+
ended = receive(msgs, next_req[:batch], pin_id, -> { next_reply(inbox, deadline) }, &block)
|
|
190
|
+
ensure
|
|
191
|
+
release(inbox, ended ? 0 : pulled + UNCHECKED_PULL_AGE - MonotonicTime.now) if inbox
|
|
192
|
+
end
|
|
163
193
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
194
|
+
# receive collects the messages a pull delivers. It returns true once
|
|
195
|
+
# the pull delivered its batch or the server ended it, and false when
|
|
196
|
+
# nothing came before the deadline.
|
|
197
|
+
def receive(msgs, batch, pin_id, next_msg, &block)
|
|
198
|
+
batch.times do
|
|
199
|
+
msg = next_msg.call
|
|
200
|
+
return false if msg.nil?
|
|
201
|
+
next collect(msgs, msg, &block) unless JS.is_status_msg(msg)
|
|
167
202
|
|
|
168
|
-
|
|
169
|
-
|
|
203
|
+
forget_pin(pin_id) if msg.header[JS::Header::Status] == JS::Status::PinIdMismatch
|
|
204
|
+
# An error ends the fetch too, with the messages taken before it.
|
|
205
|
+
return true if pull_ended?(msg) || !msgs.empty?
|
|
170
206
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
end
|
|
207
|
+
raise JS.from_msg(msg)
|
|
208
|
+
end
|
|
209
|
+
true
|
|
210
|
+
end
|
|
176
211
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
@nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
|
|
187
|
-
when JS::Status::RequestTimeout
|
|
188
|
-
raise NATS::Timeout.new("nats: fetch request timeout")
|
|
189
|
-
else
|
|
190
|
-
raise JS.from_msg(msg)
|
|
191
|
-
end
|
|
192
|
-
else
|
|
193
|
-
msgs << msg unless msg.nil?
|
|
212
|
+
# release unsubscribes the inbox of a pull that did not wait, handing
|
|
213
|
+
# what came to it to the next fetches. Unless the pull ended, it waits
|
|
214
|
+
# until the server checks for interest before it delivers to the pull.
|
|
215
|
+
def release(inbox, linger)
|
|
216
|
+
if linger > 0
|
|
217
|
+
return Thread.new do
|
|
218
|
+
hand_over(inbox, MonotonicTime.now + linger)
|
|
219
|
+
release(inbox, 0)
|
|
194
220
|
end
|
|
221
|
+
end
|
|
195
222
|
|
|
196
|
-
|
|
197
|
-
|
|
223
|
+
begin
|
|
224
|
+
inbox.unsubscribe
|
|
225
|
+
rescue NATS::IO::ConnectionClosedError, NATS::IO::BadSubscription
|
|
226
|
+
# The connection closed, which ended the pull too.
|
|
227
|
+
end
|
|
228
|
+
hand_over(inbox)
|
|
229
|
+
end
|
|
198
230
|
|
|
199
|
-
|
|
200
|
-
|
|
231
|
+
# hand_over adds the messages that come to the inbox of a pull that did
|
|
232
|
+
# not wait, until the deadline, to those of the subscription.
|
|
233
|
+
def hand_over(inbox, deadline = MonotonicTime.now)
|
|
234
|
+
while (msg = next_reply(inbox, deadline))
|
|
235
|
+
keep(msg) unless JS.is_status_msg(msg)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# keep adds a message to those delivered to the subscription, for the
|
|
240
|
+
# next fetches. Like the connection, it drops the message when the
|
|
241
|
+
# subscription holds as many as it may, and the server delivers it again.
|
|
242
|
+
def keep(msg)
|
|
243
|
+
synchronize do
|
|
244
|
+
return if @pending_queue.size >= pending_msgs_limit || @pending_size >= pending_bytes_limit
|
|
245
|
+
|
|
246
|
+
@pending_queue << msg
|
|
247
|
+
@pending_size += msg.data.size
|
|
248
|
+
wait_for_msgs_cond.signal
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# collect adds a message to those of the fetch, and passes it to the
|
|
253
|
+
# block, if there is one. A message the server delivers again, as it
|
|
254
|
+
# was not acked in time, takes the place of its earlier delivery.
|
|
255
|
+
def collect(msgs, msg)
|
|
256
|
+
yield msg if block_given?
|
|
257
|
+
meta = msg.metadata
|
|
258
|
+
if meta && meta.num_delivered > 1
|
|
259
|
+
i = msgs.index { |held| held.metadata.sequence.stream == meta.sequence.stream }
|
|
260
|
+
end
|
|
261
|
+
i ? msgs[i] = msg : msgs << msg
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# pull_ended? tells whether a status only says that a pull ended:
|
|
265
|
+
# 404 No Messages, or 408, as the pull expired or other pulls wait
|
|
266
|
+
# for more messages than are pending. Other statuses are errors.
|
|
267
|
+
def pull_ended?(msg)
|
|
268
|
+
[JS::Status::NoMsgs, JS::Status::RequestTimeout].include?(msg.header[JS::Header::Status])
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# pull_expires is how long a pull may wait, in nanoseconds: until the
|
|
272
|
+
# deadline. Never 0, with which the pull would wait for good.
|
|
273
|
+
def pull_expires(deadline)
|
|
274
|
+
[((deadline - MonotonicTime.now) * 1_000_000_000).to_i, 1].max
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# wait_pending takes the next message delivered to the subscription,
|
|
278
|
+
# or the status that ended the pull of the reply, waiting for one until
|
|
279
|
+
# the deadline; nil when none came. Other fetches can take the message
|
|
280
|
+
# it was woken up for.
|
|
281
|
+
def wait_pending(reply, deadline)
|
|
282
|
+
synchronize do
|
|
283
|
+
loop do
|
|
284
|
+
return @pull_ends.delete(reply) if @pull_ends[reply]
|
|
285
|
+
|
|
286
|
+
msg = next_pending(reply)
|
|
287
|
+
return msg if msg
|
|
288
|
+
|
|
289
|
+
remaining = deadline - MonotonicTime.now
|
|
290
|
+
return if remaining <= 0
|
|
291
|
+
|
|
292
|
+
wait_for_msgs_cond.wait(remaining)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# next_pending takes the next message delivered to the subscription,
|
|
298
|
+
# or the status that ended the pull of the reply, if there is one.
|
|
299
|
+
# Other statuses ended the pulls of other fetches, which get them if
|
|
300
|
+
# they still wait for them.
|
|
301
|
+
def next_pending(reply = nil)
|
|
302
|
+
synchronize do
|
|
303
|
+
while (msg = pop_pending)
|
|
304
|
+
return msg unless JS.is_status_msg(msg) && msg.subject != reply
|
|
305
|
+
next unless @pull_ends.key?(msg.subject)
|
|
306
|
+
|
|
307
|
+
@pull_ends[msg.subject] = msg
|
|
308
|
+
wait_for_msgs_cond.broadcast
|
|
201
309
|
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
202
312
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
MonotonicTime.now
|
|
213
|
-
|
|
214
|
-
wait_for_msgs_cond.wait(deadline) if deadline > 0
|
|
215
|
-
|
|
216
|
-
duration = MonotonicTime.since(start_time)
|
|
217
|
-
if msgs.empty? && @pending_queue.empty? && (duration > timeout)
|
|
218
|
-
raise NATS::Timeout.new("nats: fetch timeout")
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
unless @pending_queue.empty?
|
|
223
|
-
msg = @pending_queue.pop
|
|
224
|
-
@pending_size -= msg.data.size
|
|
225
|
-
|
|
226
|
-
if JS.is_status_msg(msg)
|
|
227
|
-
case msg.header[JS::Header::Status]
|
|
228
|
-
when JS::Status::NoMsgs, JS::Status::RequestTimeout
|
|
229
|
-
duration = MonotonicTime.since(start_time)
|
|
230
|
-
|
|
231
|
-
if duration > timeout
|
|
232
|
-
# Only received a subset of the messages.
|
|
233
|
-
if !msgs.empty?
|
|
234
|
-
return msgs
|
|
235
|
-
else
|
|
236
|
-
raise NATS::Timeout.new("nats: fetch timeout")
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
else
|
|
240
|
-
raise JS.from_msg(msg)
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
else
|
|
244
|
-
# Add to the set of messages that will be returned.
|
|
245
|
-
msgs << msg
|
|
246
|
-
needed -= 1
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
end # :end: synchronize
|
|
313
|
+
# next_reply takes the next reply to a pull that does not wait,
|
|
314
|
+
# waiting for one until the deadline; nil when none came.
|
|
315
|
+
def next_reply(inbox, deadline)
|
|
316
|
+
msg = inbox.synchronize do
|
|
317
|
+
while inbox.pending_queue.empty?
|
|
318
|
+
remaining = deadline - MonotonicTime.now
|
|
319
|
+
return if remaining <= 0
|
|
320
|
+
|
|
321
|
+
inbox.wait_for_msgs_cond.wait(remaining)
|
|
250
322
|
end
|
|
323
|
+
inbox.pending_queue.pop.tap { |reply| inbox.pending_size -= reply.data.size }
|
|
251
324
|
end
|
|
325
|
+
msg.sub = self
|
|
326
|
+
track_pin(msg)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# pull asks the server for messages, sending the pin id of the
|
|
330
|
+
# subscription, if it is pinned, as the messages it took last say.
|
|
331
|
+
# Returns the pin id it sent.
|
|
332
|
+
def pull(next_req, reply)
|
|
333
|
+
next_req[:id] = synchronize { @pin_id }
|
|
334
|
+
@nc.publish(@jsi.nms, JS.next_req_to_json(next_req), reply)
|
|
335
|
+
next_req[:id]
|
|
336
|
+
end
|
|
252
337
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
338
|
+
# pop_pending takes the next message delivered to the subscription,
|
|
339
|
+
# if there is one: the connection delivers under the same lock, so
|
|
340
|
+
# waiting for one here would stop it.
|
|
341
|
+
def pop_pending
|
|
342
|
+
synchronize do
|
|
343
|
+
return if @pending_queue.empty?
|
|
344
|
+
|
|
345
|
+
msg = @pending_queue.pop
|
|
346
|
+
@pending_size -= msg.data.size
|
|
347
|
+
track_pin(msg)
|
|
256
348
|
end
|
|
349
|
+
end
|
|
257
350
|
|
|
258
|
-
|
|
351
|
+
# track_pin keeps the pin id of the subscription. With the pinned_client
|
|
352
|
+
# priority policy, every message delivered to a pinned subscription
|
|
353
|
+
# carries its pin id, which it sends with its pulls.
|
|
354
|
+
def track_pin(msg)
|
|
355
|
+
pin_id = (msg.header || {})[JS::Header::PinId]
|
|
356
|
+
synchronize { @pin_id = pin_id } if pin_id
|
|
357
|
+
msg
|
|
259
358
|
end
|
|
260
359
|
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
@jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
|
|
360
|
+
# forget_pin drops the pin id a pull sent, which the server turned away
|
|
361
|
+
# (423) as the subscription is no longer pinned, unless the subscription
|
|
362
|
+
# got another one since.
|
|
363
|
+
def forget_pin(pin_id)
|
|
364
|
+
synchronize { @pin_id = nil if @pin_id == pin_id }
|
|
267
365
|
end
|
|
268
366
|
end
|
|
269
367
|
private_constant :PullSubscription
|