nats-pure 0.7.2 → 2.0.0.pre.alpha
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/lib/nats/client.rb +15 -0
- data/lib/nats/io/client.rb +1350 -1293
- data/lib/nats/io/errors.rb +62 -0
- data/lib/nats/io/js.rb +1090 -0
- data/lib/nats/io/msg.rb +56 -0
- data/lib/nats/io/parser.rb +7 -7
- data/lib/nats/io/subscription.rb +92 -0
- data/lib/nats/io/version.rb +7 -3
- data/lib/nats.rb +39 -0
- metadata +10 -4
data/lib/nats/io/client.rb
CHANGED
@@ -12,8 +12,13 @@
|
|
12
12
|
# limitations under the License.
|
13
13
|
#
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
require_relative 'parser'
|
16
|
+
require_relative 'version'
|
17
|
+
require_relative 'errors'
|
18
|
+
require_relative 'msg'
|
19
|
+
require_relative 'subscription'
|
20
|
+
require_relative 'js'
|
21
|
+
|
17
22
|
require 'nats/nuid'
|
18
23
|
require 'thread'
|
19
24
|
require 'socket'
|
@@ -29,43 +34,58 @@ end
|
|
29
34
|
|
30
35
|
module NATS
|
31
36
|
class << self
|
37
|
+
# NATS.connect creates a connection to the NATS Server.
|
38
|
+
# @param uri [String] URL endpoint of the NATS Server or cluster.
|
39
|
+
# @param opts [Hash] Options to customize the NATS connection.
|
40
|
+
# @return [NATS::Client]
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# require 'nats'
|
44
|
+
# nc = NATS.connect("demo.nats.io")
|
45
|
+
# nc.publish("hello", "world")
|
46
|
+
# nc.close
|
47
|
+
#
|
32
48
|
def connect(uri=nil, opts={})
|
33
|
-
nc = NATS::
|
49
|
+
nc = NATS::Client.new
|
34
50
|
nc.connect(uri, opts)
|
35
51
|
|
36
52
|
nc
|
37
53
|
end
|
38
54
|
end
|
39
55
|
|
40
|
-
|
56
|
+
# Status represents the different states from a NATS connection.
|
57
|
+
# A client starts from the DISCONNECTED state to CONNECTING during
|
58
|
+
# the initial connect, then CONNECTED. If the connection is reset
|
59
|
+
# then it goes from DISCONNECTED to RECONNECTING until it is back to
|
60
|
+
# the CONNECTED state. In case the client gives up reconnecting or
|
61
|
+
# the connection is manually closed then it will reach the CLOSED
|
62
|
+
# connection state after which it will not reconnect again.
|
63
|
+
module Status
|
64
|
+
# When the client is not actively connected.
|
65
|
+
DISCONNECTED = 0
|
41
66
|
|
42
|
-
|
43
|
-
|
67
|
+
# When the client is connected.
|
68
|
+
CONNECTED = 1
|
44
69
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# Maximum accumulated pending commands bytesize before forcing a flush.
|
49
|
-
MAX_PENDING_SIZE = 32768
|
70
|
+
# When the client will no longer attempt to connect to a NATS Server.
|
71
|
+
CLOSED = 2
|
50
72
|
|
51
|
-
#
|
52
|
-
|
73
|
+
# When the client has disconnected and is attempting to reconnect.
|
74
|
+
RECONNECTING = 3
|
53
75
|
|
54
|
-
#
|
55
|
-
|
56
|
-
|
76
|
+
# When the client is attempting to connect to a NATS Server for the first time.
|
77
|
+
CONNECTING = 4
|
78
|
+
end
|
57
79
|
|
58
|
-
|
59
|
-
|
60
|
-
|
80
|
+
# Client creates a connection to the NATS Server.
|
81
|
+
class Client
|
82
|
+
include MonitorMixin
|
83
|
+
include Status
|
61
84
|
|
62
|
-
|
63
|
-
DEFAULT_CONNECT_TIMEOUT = 2
|
64
|
-
DEFAULT_READ_WRITE_TIMEOUT = 2
|
85
|
+
attr_reader :status, :server_info, :server_pool, :options, :connected_server, :stats, :uri
|
65
86
|
|
66
|
-
|
67
|
-
|
68
|
-
DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024
|
87
|
+
DEFAULT_PORT = 4222
|
88
|
+
DEFAULT_URI = ("nats://localhost:#{DEFAULT_PORT}".freeze)
|
69
89
|
|
70
90
|
CR_LF = ("\r\n".freeze)
|
71
91
|
CR_LF_SIZE = (CR_LF.bytesize)
|
@@ -82,1510 +102,1563 @@ module NATS
|
|
82
102
|
SUB_OP = ('SUB'.freeze)
|
83
103
|
EMPTY_MSG = (''.freeze)
|
84
104
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
105
|
+
def initialize
|
106
|
+
super # required to initialize monitor
|
107
|
+
@options = nil
|
108
|
+
|
109
|
+
# Read/Write IO
|
110
|
+
@io = nil
|
111
|
+
|
112
|
+
# Queues for coalescing writes of commands we need to send to server.
|
113
|
+
@flush_queue = nil
|
114
|
+
@pending_queue = nil
|
115
|
+
|
116
|
+
# Parser with state
|
117
|
+
@parser = NATS::Protocol::Parser.new(self)
|
118
|
+
|
119
|
+
# Threads for both reading and flushing command
|
120
|
+
@flusher_thread = nil
|
121
|
+
@read_loop_thread = nil
|
122
|
+
@ping_interval_thread = nil
|
123
|
+
|
124
|
+
# Info that we get from the server
|
125
|
+
@server_info = { }
|
126
|
+
|
127
|
+
# URI from server to which we are currently connected
|
128
|
+
@uri = nil
|
129
|
+
@server_pool = []
|
130
|
+
|
131
|
+
@status = DISCONNECTED
|
132
|
+
|
133
|
+
# Subscriptions
|
134
|
+
@subs = { }
|
135
|
+
@ssid = 0
|
136
|
+
|
137
|
+
# Ping interval
|
138
|
+
@pings_outstanding = 0
|
139
|
+
@pongs_received = 0
|
140
|
+
@pongs = []
|
141
|
+
@pongs.extend(MonitorMixin)
|
142
|
+
|
143
|
+
# Accounting
|
144
|
+
@pending_size = 0
|
145
|
+
@stats = {
|
146
|
+
in_msgs: 0,
|
147
|
+
out_msgs: 0,
|
148
|
+
in_bytes: 0,
|
149
|
+
out_bytes: 0,
|
150
|
+
reconnects: 0
|
151
|
+
}
|
152
|
+
|
153
|
+
# Sticky error
|
154
|
+
@last_err = nil
|
155
|
+
|
156
|
+
# Async callbacks, no ops by default.
|
157
|
+
@err_cb = proc { }
|
158
|
+
@close_cb = proc { }
|
159
|
+
@disconnect_cb = proc { }
|
160
|
+
@reconnect_cb = proc { }
|
161
|
+
|
162
|
+
# Secure TLS options
|
163
|
+
@tls = nil
|
164
|
+
|
165
|
+
# Hostname of current server; used for when TLS host
|
166
|
+
# verification is enabled.
|
167
|
+
@hostname = nil
|
168
|
+
@single_url_connect_used = false
|
169
|
+
|
170
|
+
# Track whether connect has been already been called.
|
171
|
+
@connect_called = false
|
172
|
+
|
173
|
+
# New style request/response implementation.
|
174
|
+
@resp_sub = nil
|
175
|
+
@resp_map = nil
|
176
|
+
@resp_sub_prefix = nil
|
177
|
+
@nuid = NATS::NUID.new
|
178
|
+
|
179
|
+
# NKEYS
|
180
|
+
@user_credentials = nil
|
181
|
+
@nkeys_seed = nil
|
182
|
+
@user_nkey_cb = nil
|
183
|
+
@user_jwt_cb = nil
|
184
|
+
@signature_cb = nil
|
185
|
+
end
|
108
186
|
|
109
|
-
#
|
110
|
-
|
187
|
+
# Establishes a connection to NATS.
|
188
|
+
def connect(uri=nil, opts={})
|
189
|
+
synchronize do
|
190
|
+
# In case it has been connected already, then do not need to call this again.
|
191
|
+
return if @connect_called
|
192
|
+
@connect_called = true
|
193
|
+
end
|
111
194
|
|
112
|
-
|
113
|
-
|
195
|
+
# Convert URI to string if needed.
|
196
|
+
uri = uri.to_s if uri.is_a?(URI)
|
197
|
+
|
198
|
+
case uri
|
199
|
+
when String
|
200
|
+
# Initialize TLS defaults in case any url is using it.
|
201
|
+
srvs = opts[:servers] = process_uri(uri)
|
202
|
+
if srvs.any? {|u| u.scheme == 'tls'} and !opts[:tls]
|
203
|
+
tls_context = OpenSSL::SSL::SSLContext.new
|
204
|
+
tls_context.set_params
|
205
|
+
opts[:tls] = {
|
206
|
+
context: tls_context
|
207
|
+
}
|
208
|
+
end
|
209
|
+
@single_url_connect_used = true if srvs.size == 1
|
210
|
+
when Hash
|
211
|
+
opts = uri
|
212
|
+
end
|
114
213
|
|
115
|
-
|
116
|
-
|
214
|
+
opts[:verbose] = false if opts[:verbose].nil?
|
215
|
+
opts[:pedantic] = false if opts[:pedantic].nil?
|
216
|
+
opts[:reconnect] = true if opts[:reconnect].nil?
|
217
|
+
opts[:old_style_request] = false if opts[:old_style_request].nil?
|
218
|
+
opts[:reconnect_time_wait] = NATS::IO::RECONNECT_TIME_WAIT if opts[:reconnect_time_wait].nil?
|
219
|
+
opts[:max_reconnect_attempts] = NATS::IO::MAX_RECONNECT_ATTEMPTS if opts[:max_reconnect_attempts].nil?
|
220
|
+
opts[:ping_interval] = NATS::IO::DEFAULT_PING_INTERVAL if opts[:ping_interval].nil?
|
221
|
+
opts[:max_outstanding_pings] = NATS::IO::DEFAULT_PING_MAX if opts[:max_outstanding_pings].nil?
|
222
|
+
|
223
|
+
# Override with ENV
|
224
|
+
opts[:verbose] = ENV['NATS_VERBOSE'].downcase == 'true' unless ENV['NATS_VERBOSE'].nil?
|
225
|
+
opts[:pedantic] = ENV['NATS_PEDANTIC'].downcase == 'true' unless ENV['NATS_PEDANTIC'].nil?
|
226
|
+
opts[:reconnect] = ENV['NATS_RECONNECT'].downcase == 'true' unless ENV['NATS_RECONNECT'].nil?
|
227
|
+
opts[:reconnect_time_wait] = ENV['NATS_RECONNECT_TIME_WAIT'].to_i unless ENV['NATS_RECONNECT_TIME_WAIT'].nil?
|
228
|
+
opts[:max_reconnect_attempts] = ENV['NATS_MAX_RECONNECT_ATTEMPTS'].to_i unless ENV['NATS_MAX_RECONNECT_ATTEMPTS'].nil?
|
229
|
+
opts[:ping_interval] = ENV['NATS_PING_INTERVAL'].to_i unless ENV['NATS_PING_INTERVAL'].nil?
|
230
|
+
opts[:max_outstanding_pings] = ENV['NATS_MAX_OUTSTANDING_PINGS'].to_i unless ENV['NATS_MAX_OUTSTANDING_PINGS'].nil?
|
231
|
+
opts[:connect_timeout] ||= NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
232
|
+
@options = opts
|
233
|
+
|
234
|
+
# Process servers in the NATS cluster and pick one to connect
|
235
|
+
uris = opts[:servers] || [DEFAULT_URI]
|
236
|
+
uris.shuffle! unless @options[:dont_randomize_servers]
|
237
|
+
uris.each do |u|
|
238
|
+
nats_uri = case u
|
239
|
+
when URI
|
240
|
+
u.dup
|
241
|
+
else
|
242
|
+
URI.parse(u)
|
243
|
+
end
|
244
|
+
@server_pool << {
|
245
|
+
:uri => nats_uri,
|
246
|
+
:hostname => nats_uri.host
|
247
|
+
}
|
248
|
+
end
|
117
249
|
|
118
|
-
|
119
|
-
|
250
|
+
if @options[:old_style_request]
|
251
|
+
# Replace for this instance the implementation
|
252
|
+
# of request to use the old_request style.
|
253
|
+
class << self; alias_method :request, :old_request; end
|
254
|
+
end
|
120
255
|
|
121
|
-
|
122
|
-
|
256
|
+
# NKEYS
|
257
|
+
@user_credentials ||= opts[:user_credentials]
|
258
|
+
@nkeys_seed ||= opts[:nkeys_seed]
|
259
|
+
setup_nkeys_connect if @user_credentials or @nkeys_seed
|
123
260
|
|
124
|
-
|
125
|
-
|
261
|
+
# Check for TLS usage
|
262
|
+
@tls = @options[:tls]
|
126
263
|
|
127
|
-
|
128
|
-
|
264
|
+
srv = nil
|
265
|
+
begin
|
266
|
+
srv = select_next_server
|
129
267
|
|
130
|
-
|
268
|
+
# Create TCP socket connection to NATS
|
269
|
+
@io = create_socket
|
270
|
+
@io.connect
|
131
271
|
|
132
|
-
|
133
|
-
|
134
|
-
|
272
|
+
# Capture state that we have had a TCP connection established against
|
273
|
+
# this server and could potentially be used for reconnecting.
|
274
|
+
srv[:was_connected] = true
|
135
275
|
|
136
|
-
#
|
137
|
-
@
|
276
|
+
# Connection established and now in process of sending CONNECT to NATS
|
277
|
+
@status = CONNECTING
|
138
278
|
|
139
|
-
#
|
140
|
-
|
141
|
-
|
279
|
+
# Use the hostname from the server for TLS hostname verification.
|
280
|
+
if client_using_secure_connection? and single_url_connect_used?
|
281
|
+
# Always reuse the original hostname used to connect.
|
282
|
+
@hostname ||= srv[:hostname]
|
283
|
+
else
|
284
|
+
@hostname = srv[:hostname]
|
285
|
+
end
|
142
286
|
|
143
|
-
#
|
144
|
-
|
287
|
+
# Established TCP connection successfully so can start connect
|
288
|
+
process_connect_init
|
145
289
|
|
146
|
-
#
|
147
|
-
|
148
|
-
|
149
|
-
@ping_interval_thread = nil
|
290
|
+
# Reset reconnection attempts if connection is valid
|
291
|
+
srv[:reconnect_attempts] = 0
|
292
|
+
srv[:auth_required] ||= true if @server_info[:auth_required]
|
150
293
|
|
151
|
-
#
|
152
|
-
|
294
|
+
# Add back to rotation since successfully connected
|
295
|
+
server_pool << srv
|
296
|
+
rescue NATS::IO::NoServersError => e
|
297
|
+
@disconnect_cb.call(e) if @disconnect_cb
|
298
|
+
raise @last_err || e
|
299
|
+
rescue => e
|
300
|
+
# Capture sticky error
|
301
|
+
synchronize do
|
302
|
+
@last_err = e
|
303
|
+
srv[:auth_required] ||= true if @server_info[:auth_required]
|
304
|
+
server_pool << srv if can_reuse_server?(srv)
|
305
|
+
end
|
153
306
|
|
154
|
-
|
155
|
-
@uri = nil
|
156
|
-
@server_pool = []
|
307
|
+
err_cb_call(self, e, nil) if @err_cb
|
157
308
|
|
158
|
-
|
309
|
+
if should_not_reconnect?
|
310
|
+
@disconnect_cb.call(e) if @disconnect_cb
|
311
|
+
raise e
|
312
|
+
end
|
159
313
|
|
160
|
-
#
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
# Ping interval
|
165
|
-
@pings_outstanding = 0
|
166
|
-
@pongs_received = 0
|
167
|
-
@pongs = []
|
168
|
-
@pongs.extend(MonitorMixin)
|
169
|
-
|
170
|
-
# Accounting
|
171
|
-
@pending_size = 0
|
172
|
-
@stats = {
|
173
|
-
in_msgs: 0,
|
174
|
-
out_msgs: 0,
|
175
|
-
in_bytes: 0,
|
176
|
-
out_bytes: 0,
|
177
|
-
reconnects: 0
|
178
|
-
}
|
314
|
+
# Clean up any connecting state and close connection without
|
315
|
+
# triggering the disconnection/closed callbacks.
|
316
|
+
close_connection(DISCONNECTED, false)
|
179
317
|
|
180
|
-
#
|
181
|
-
|
318
|
+
# always sleep here to safe guard against errors before current[:was_connected]
|
319
|
+
# is set for the first time
|
320
|
+
sleep @options[:reconnect_time_wait] if @options[:reconnect_time_wait]
|
182
321
|
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
@disconnect_cb = proc { }
|
187
|
-
@reconnect_cb = proc { }
|
322
|
+
# Continue retrying until there are no options left in the server pool
|
323
|
+
retry
|
324
|
+
end
|
188
325
|
|
189
|
-
|
190
|
-
|
326
|
+
# Initialize queues and loops for message dispatching and processing engine
|
327
|
+
@flush_queue = SizedQueue.new(NATS::IO::MAX_FLUSH_KICK_SIZE)
|
328
|
+
@pending_queue = SizedQueue.new(NATS::IO::MAX_PENDING_SIZE)
|
329
|
+
@pings_outstanding = 0
|
330
|
+
@pongs_received = 0
|
331
|
+
@pending_size = 0
|
191
332
|
|
192
|
-
|
193
|
-
|
194
|
-
@hostname = nil
|
195
|
-
@single_url_connect_used = false
|
333
|
+
# Server roundtrip went ok so consider to be connected at this point
|
334
|
+
@status = CONNECTED
|
196
335
|
|
197
|
-
|
198
|
-
|
336
|
+
# Connected to NATS so Ready to start parser loop, flusher and ping interval
|
337
|
+
start_threads!
|
199
338
|
|
200
|
-
|
201
|
-
|
202
|
-
@resp_map = nil
|
203
|
-
@resp_sub_prefix = nil
|
204
|
-
@nuid = NATS::NUID.new
|
339
|
+
self
|
340
|
+
end
|
205
341
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
@user_jwt_cb = nil
|
211
|
-
@signature_cb = nil
|
342
|
+
def publish(subject, msg=EMPTY_MSG, opt_reply=nil, **options, &blk)
|
343
|
+
raise NATS::IO::BadSubject if !subject or subject.empty?
|
344
|
+
if options[:header]
|
345
|
+
return publish_msg(NATS::Msg.new(subject: subject, data: msg, reply: opt_reply, header: options[:header]))
|
212
346
|
end
|
213
347
|
|
214
|
-
#
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
return if @connect_called
|
219
|
-
@connect_called = true
|
220
|
-
end
|
221
|
-
|
222
|
-
# Convert URI to string if needed.
|
223
|
-
uri = uri.to_s if uri.is_a?(URI)
|
224
|
-
|
225
|
-
case uri
|
226
|
-
when String
|
227
|
-
# Initialize TLS defaults in case any url is using it.
|
228
|
-
srvs = opts[:servers] = process_uri(uri)
|
229
|
-
if srvs.any? {|u| u.scheme == 'tls'} and !opts[:tls]
|
230
|
-
tls_context = OpenSSL::SSL::SSLContext.new
|
231
|
-
tls_context.set_params
|
232
|
-
opts[:tls] = {
|
233
|
-
context: tls_context
|
234
|
-
}
|
235
|
-
end
|
236
|
-
@single_url_connect_used = true if srvs.size == 1
|
237
|
-
when Hash
|
238
|
-
opts = uri
|
239
|
-
end
|
240
|
-
|
241
|
-
opts[:verbose] = false if opts[:verbose].nil?
|
242
|
-
opts[:pedantic] = false if opts[:pedantic].nil?
|
243
|
-
opts[:reconnect] = true if opts[:reconnect].nil?
|
244
|
-
opts[:old_style_request] = false if opts[:old_style_request].nil?
|
245
|
-
opts[:reconnect_time_wait] = RECONNECT_TIME_WAIT if opts[:reconnect_time_wait].nil?
|
246
|
-
opts[:max_reconnect_attempts] = MAX_RECONNECT_ATTEMPTS if opts[:max_reconnect_attempts].nil?
|
247
|
-
opts[:ping_interval] = DEFAULT_PING_INTERVAL if opts[:ping_interval].nil?
|
248
|
-
opts[:max_outstanding_pings] = DEFAULT_PING_MAX if opts[:max_outstanding_pings].nil?
|
249
|
-
|
250
|
-
# Override with ENV
|
251
|
-
opts[:verbose] = ENV['NATS_VERBOSE'].downcase == 'true' unless ENV['NATS_VERBOSE'].nil?
|
252
|
-
opts[:pedantic] = ENV['NATS_PEDANTIC'].downcase == 'true' unless ENV['NATS_PEDANTIC'].nil?
|
253
|
-
opts[:reconnect] = ENV['NATS_RECONNECT'].downcase == 'true' unless ENV['NATS_RECONNECT'].nil?
|
254
|
-
opts[:reconnect_time_wait] = ENV['NATS_RECONNECT_TIME_WAIT'].to_i unless ENV['NATS_RECONNECT_TIME_WAIT'].nil?
|
255
|
-
opts[:max_reconnect_attempts] = ENV['NATS_MAX_RECONNECT_ATTEMPTS'].to_i unless ENV['NATS_MAX_RECONNECT_ATTEMPTS'].nil?
|
256
|
-
opts[:ping_interval] = ENV['NATS_PING_INTERVAL'].to_i unless ENV['NATS_PING_INTERVAL'].nil?
|
257
|
-
opts[:max_outstanding_pings] = ENV['NATS_MAX_OUTSTANDING_PINGS'].to_i unless ENV['NATS_MAX_OUTSTANDING_PINGS'].nil?
|
258
|
-
opts[:connect_timeout] ||= DEFAULT_CONNECT_TIMEOUT
|
259
|
-
@options = opts
|
260
|
-
|
261
|
-
# Process servers in the NATS cluster and pick one to connect
|
262
|
-
uris = opts[:servers] || [DEFAULT_URI]
|
263
|
-
uris.shuffle! unless @options[:dont_randomize_servers]
|
264
|
-
uris.each do |u|
|
265
|
-
nats_uri = case u
|
266
|
-
when URI
|
267
|
-
u.dup
|
268
|
-
else
|
269
|
-
URI.parse(u)
|
270
|
-
end
|
271
|
-
@server_pool << {
|
272
|
-
:uri => nats_uri,
|
273
|
-
:hostname => nats_uri.host
|
274
|
-
}
|
275
|
-
end
|
348
|
+
# Accounting
|
349
|
+
msg_size = msg.bytesize
|
350
|
+
@stats[:out_msgs] += 1
|
351
|
+
@stats[:out_bytes] += msg_size
|
276
352
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
class << self; alias_method :request, :old_request; end
|
281
|
-
end
|
353
|
+
send_command("PUB #{subject} #{opt_reply} #{msg_size}\r\n#{msg}\r\n")
|
354
|
+
@flush_queue << :pub if @flush_queue.empty?
|
355
|
+
end
|
282
356
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
357
|
+
# Publishes a NATS::Msg that may include headers.
|
358
|
+
def publish_msg(msg)
|
359
|
+
raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg)
|
360
|
+
raise NATS::IO::BadSubject if !msg.subject or msg.subject.empty?
|
287
361
|
|
288
|
-
|
289
|
-
|
362
|
+
msg.reply ||= ''
|
363
|
+
msg.data ||= ''
|
364
|
+
msg_size = msg.data.bytesize
|
290
365
|
|
291
|
-
|
292
|
-
|
293
|
-
|
366
|
+
# Accounting
|
367
|
+
@stats[:out_msgs] += 1
|
368
|
+
@stats[:out_bytes] += msg_size
|
294
369
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
370
|
+
if msg.header
|
371
|
+
hdr = ''
|
372
|
+
hdr << NATS_HDR_LINE
|
373
|
+
msg.header.each do |k, v|
|
374
|
+
hdr << "#{k}: #{v}#{CR_LF}"
|
375
|
+
end
|
376
|
+
hdr << CR_LF
|
377
|
+
hdr_len = hdr.bytesize
|
378
|
+
total_size = msg_size + hdr_len
|
379
|
+
send_command("HPUB #{msg.subject} #{msg.reply} #{hdr_len} #{total_size}\r\n#{hdr}#{msg.data}\r\n")
|
380
|
+
else
|
381
|
+
send_command("PUB #{msg.subject} #{msg.reply} #{msg_size}\r\n#{msg.data}\r\n")
|
382
|
+
end
|
302
383
|
|
303
|
-
|
304
|
-
|
384
|
+
@flush_queue << :pub if @flush_queue.empty?
|
385
|
+
end
|
305
386
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
387
|
+
# Create subscription which is dispatched asynchronously
|
388
|
+
# messages to a callback.
|
389
|
+
def subscribe(subject, opts={}, &callback)
|
390
|
+
sid = nil
|
391
|
+
sub = nil
|
392
|
+
synchronize do
|
393
|
+
sid = (@ssid += 1)
|
394
|
+
sub = @subs[sid] = Subscription.new
|
395
|
+
sub.nc = self
|
396
|
+
sub.sid = sid
|
397
|
+
end
|
398
|
+
opts[:pending_msgs_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT
|
399
|
+
opts[:pending_bytes_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_BYTES_LIMIT
|
400
|
+
|
401
|
+
sub.subject = subject
|
402
|
+
sub.callback = callback
|
403
|
+
sub.received = 0
|
404
|
+
sub.queue = opts[:queue] if opts[:queue]
|
405
|
+
sub.max = opts[:max] if opts[:max]
|
406
|
+
sub.pending_msgs_limit = opts[:pending_msgs_limit]
|
407
|
+
sub.pending_bytes_limit = opts[:pending_bytes_limit]
|
408
|
+
sub.pending_queue = SizedQueue.new(sub.pending_msgs_limit)
|
409
|
+
|
410
|
+
send_command("SUB #{subject} #{opts[:queue]} #{sid}#{CR_LF}")
|
411
|
+
@flush_queue << :sub
|
412
|
+
|
413
|
+
# Setup server support for auto-unsubscribe when receiving enough messages
|
414
|
+
sub.unsubscribe(opts[:max]) if opts[:max]
|
415
|
+
|
416
|
+
unless callback
|
417
|
+
cond = sub.new_cond
|
418
|
+
sub.wait_for_msgs_cond = cond
|
419
|
+
end
|
313
420
|
|
314
|
-
|
315
|
-
|
421
|
+
# Async subscriptions each own a single thread for the
|
422
|
+
# delivery of messages.
|
423
|
+
# FIXME: Support shared thread pool with configurable limits
|
424
|
+
# to better support case of having a lot of subscriptions.
|
425
|
+
sub.wait_for_msgs_t = Thread.new do
|
426
|
+
loop do
|
427
|
+
msg = sub.pending_queue.pop
|
316
428
|
|
317
|
-
|
318
|
-
|
319
|
-
srv[:auth_required] ||= true if @server_info[:auth_required]
|
429
|
+
cb = nil
|
430
|
+
sub.synchronize do
|
320
431
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
@disconnect_cb.call(e) if @disconnect_cb
|
325
|
-
raise @last_err || e
|
326
|
-
rescue => e
|
327
|
-
# Capture sticky error
|
328
|
-
synchronize do
|
329
|
-
@last_err = e
|
330
|
-
srv[:auth_required] ||= true if @server_info[:auth_required]
|
331
|
-
server_pool << srv if can_reuse_server?(srv)
|
432
|
+
# Decrease pending size since consumed already
|
433
|
+
sub.pending_size -= msg.data.size
|
434
|
+
cb = sub.callback
|
332
435
|
end
|
333
436
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
437
|
+
begin
|
438
|
+
# Note: Keep some of the alternative arity versions to slightly
|
439
|
+
# improve backwards compatibility. Eventually fine to deprecate
|
440
|
+
# since recommended version would be arity of 1 to get a NATS::Msg.
|
441
|
+
case cb.arity
|
442
|
+
when 0 then cb.call
|
443
|
+
when 1 then cb.call(msg)
|
444
|
+
when 2 then cb.call(msg.data, msg.reply)
|
445
|
+
when 3 then cb.call(msg.data, msg.reply, msg.subject)
|
446
|
+
else cb.call(msg.data, msg.reply, msg.subject, msg.header)
|
447
|
+
end
|
448
|
+
rescue => e
|
449
|
+
synchronize do
|
450
|
+
err_cb_call(self, e, sub) if @err_cb
|
451
|
+
end
|
339
452
|
end
|
340
|
-
|
341
|
-
# Clean up any connecting state and close connection without
|
342
|
-
# triggering the disconnection/closed callbacks.
|
343
|
-
close_connection(DISCONNECTED, false)
|
344
|
-
|
345
|
-
# always sleep here to safe guard against errors before current[:was_connected]
|
346
|
-
# is set for the first time
|
347
|
-
sleep @options[:reconnect_time_wait] if @options[:reconnect_time_wait]
|
348
|
-
|
349
|
-
# Continue retrying until there are no options left in the server pool
|
350
|
-
retry
|
351
453
|
end
|
454
|
+
end if callback
|
455
|
+
|
456
|
+
sub
|
457
|
+
end
|
352
458
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
459
|
+
# Sends a request using expecting a single response using a
|
460
|
+
# single subscription per connection for receiving the responses.
|
461
|
+
# It times out in case the request is not retrieved within the
|
462
|
+
# specified deadline.
|
463
|
+
# If given a callback, then the request happens asynchronously.
|
464
|
+
def request(subject, payload="", **opts, &blk)
|
465
|
+
raise NATS::IO::BadSubject if !subject or subject.empty?
|
359
466
|
|
360
|
-
|
361
|
-
|
467
|
+
# If a block was given then fallback to method using auto unsubscribe.
|
468
|
+
return old_request(subject, payload, opts, &blk) if blk
|
469
|
+
return old_request(subject, payload, opts) if opts[:old_style]
|
362
470
|
|
363
|
-
|
364
|
-
|
471
|
+
if opts[:header]
|
472
|
+
return request_msg(NATS::Msg.new(subject: subject, data: payload, header: opts[:header]), **opts)
|
365
473
|
end
|
366
474
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
475
|
+
token = nil
|
476
|
+
inbox = nil
|
477
|
+
future = nil
|
478
|
+
response = nil
|
479
|
+
timeout = opts[:timeout] ||= 0.5
|
480
|
+
synchronize do
|
481
|
+
start_resp_mux_sub! unless @resp_sub_prefix
|
482
|
+
|
483
|
+
# Create token for this request.
|
484
|
+
token = @nuid.next
|
485
|
+
inbox = "#{@resp_sub_prefix}.#{token}"
|
486
|
+
|
487
|
+
# Create the a future for the request that will
|
488
|
+
# get signaled when it receives the request.
|
489
|
+
future = @resp_sub.new_cond
|
490
|
+
@resp_map[token][:future] = future
|
491
|
+
end
|
374
492
|
|
375
|
-
|
376
|
-
|
493
|
+
# Publish request and wait for reply.
|
494
|
+
publish(subject, payload, inbox)
|
495
|
+
begin
|
496
|
+
MonotonicTime::with_nats_timeout(timeout) do
|
497
|
+
@resp_sub.synchronize do
|
498
|
+
future.wait(timeout)
|
499
|
+
end
|
500
|
+
end
|
501
|
+
rescue NATS::Timeout => e
|
502
|
+
synchronize { @resp_map.delete(token) }
|
503
|
+
raise e
|
377
504
|
end
|
378
505
|
|
379
|
-
#
|
380
|
-
|
381
|
-
|
382
|
-
|
506
|
+
# Check if there is a response already.
|
507
|
+
synchronize do
|
508
|
+
result = @resp_map[token]
|
509
|
+
response = result[:response]
|
510
|
+
@resp_map.delete(token)
|
511
|
+
end
|
383
512
|
|
384
|
-
|
385
|
-
|
386
|
-
|
513
|
+
if response and response.header
|
514
|
+
status = response.header[STATUS_HDR]
|
515
|
+
raise NATS::IO::NoRespondersError if status == "503"
|
516
|
+
end
|
387
517
|
|
388
|
-
|
389
|
-
|
390
|
-
@stats[:out_bytes] += msg_size
|
518
|
+
response
|
519
|
+
end
|
391
520
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
521
|
+
# request_msg makes a NATS request using a NATS::Msg that may include headers.
|
522
|
+
def request_msg(msg, **opts)
|
523
|
+
raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg)
|
524
|
+
raise NATS::IO::BadSubject if !msg.subject or msg.subject.empty?
|
525
|
+
|
526
|
+
token = nil
|
527
|
+
inbox = nil
|
528
|
+
future = nil
|
529
|
+
response = nil
|
530
|
+
timeout = opts[:timeout] ||= 0.5
|
531
|
+
synchronize do
|
532
|
+
start_resp_mux_sub! unless @resp_sub_prefix
|
533
|
+
|
534
|
+
# Create token for this request.
|
535
|
+
token = @nuid.next
|
536
|
+
inbox = "#{@resp_sub_prefix}.#{token}"
|
537
|
+
|
538
|
+
# Create the a future for the request that will
|
539
|
+
# get signaled when it receives the request.
|
540
|
+
future = @resp_sub.new_cond
|
541
|
+
@resp_map[token][:future] = future
|
542
|
+
end
|
543
|
+
msg.reply = inbox
|
544
|
+
msg.data ||= ''
|
545
|
+
msg_size = msg.data.bytesize
|
546
|
+
|
547
|
+
# Publish request and wait for reply.
|
548
|
+
publish_msg(msg)
|
549
|
+
begin
|
550
|
+
MonotonicTime::with_nats_timeout(timeout) do
|
551
|
+
@resp_sub.synchronize do
|
552
|
+
future.wait(timeout)
|
397
553
|
end
|
398
|
-
hdr << CR_LF
|
399
|
-
hdr_len = hdr.bytesize
|
400
|
-
total_size = msg_size + hdr_len
|
401
|
-
send_command("HPUB #{msg.subject} #{msg.reply} #{hdr_len} #{total_size}\r\n#{hdr}#{msg.data}\r\n")
|
402
|
-
else
|
403
|
-
send_command("PUB #{msg.subject} #{msg.reply} #{msg_size}\r\n#{msg.data}\r\n")
|
404
554
|
end
|
555
|
+
rescue NATS::Timeout => e
|
556
|
+
synchronize { @resp_map.delete(token) }
|
557
|
+
raise e
|
558
|
+
end
|
405
559
|
|
406
|
-
|
560
|
+
# Check if there is a response already.
|
561
|
+
synchronize do
|
562
|
+
result = @resp_map[token]
|
563
|
+
response = result[:response]
|
564
|
+
@resp_map.delete(token)
|
407
565
|
end
|
408
566
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
sub = nil
|
414
|
-
synchronize do
|
415
|
-
sid = (@ssid += 1)
|
416
|
-
sub = @subs[sid] = Subscription.new
|
417
|
-
end
|
418
|
-
opts[:pending_msgs_limit] ||= DEFAULT_SUB_PENDING_MSGS_LIMIT
|
419
|
-
opts[:pending_bytes_limit] ||= DEFAULT_SUB_PENDING_BYTES_LIMIT
|
420
|
-
|
421
|
-
sub.subject = subject
|
422
|
-
sub.callback = callback
|
423
|
-
sub.received = 0
|
424
|
-
sub.queue = opts[:queue] if opts[:queue]
|
425
|
-
sub.max = opts[:max] if opts[:max]
|
426
|
-
sub.pending_msgs_limit = opts[:pending_msgs_limit]
|
427
|
-
sub.pending_bytes_limit = opts[:pending_bytes_limit]
|
428
|
-
sub.pending_queue = SizedQueue.new(sub.pending_msgs_limit)
|
429
|
-
|
430
|
-
send_command("SUB #{subject} #{opts[:queue]} #{sid}#{CR_LF}")
|
431
|
-
@flush_queue << :sub
|
432
|
-
|
433
|
-
# Setup server support for auto-unsubscribe when receiving enough messages
|
434
|
-
unsubscribe(sid, opts[:max]) if opts[:max]
|
435
|
-
|
436
|
-
# Async subscriptions each own a single thread for the
|
437
|
-
# delivery of messages.
|
438
|
-
# FIXME: Support shared thread pool with configurable limits
|
439
|
-
# to better support case of having a lot of subscriptions.
|
440
|
-
sub.wait_for_msgs_t = Thread.new do
|
441
|
-
loop do
|
442
|
-
msg = sub.pending_queue.pop
|
443
|
-
|
444
|
-
cb = nil
|
445
|
-
sub.synchronize do
|
446
|
-
# Decrease pending size since consumed already
|
447
|
-
sub.pending_size -= msg.data.size
|
448
|
-
cb = sub.callback
|
449
|
-
end
|
567
|
+
if response and response.header
|
568
|
+
status = response.header[STATUS_HDR]
|
569
|
+
raise NATS::IO::NoRespondersError if status == "503"
|
570
|
+
end
|
450
571
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
572
|
+
response
|
573
|
+
end
|
574
|
+
|
575
|
+
# Sends a request creating an ephemeral subscription for the request,
|
576
|
+
# expecting a single response or raising a timeout in case the request
|
577
|
+
# is not retrieved within the specified deadline.
|
578
|
+
# If given a callback, then the request happens asynchronously.
|
579
|
+
def old_request(subject, payload, opts={}, &blk)
|
580
|
+
return unless subject
|
581
|
+
inbox = new_inbox
|
582
|
+
|
583
|
+
# If a callback was passed, then have it process
|
584
|
+
# the messages asynchronously and return the sid.
|
585
|
+
if blk
|
586
|
+
opts[:max] ||= 1
|
587
|
+
s = subscribe(inbox, opts) do |msg|
|
588
|
+
case blk.arity
|
589
|
+
when 0 then blk.call
|
590
|
+
when 1 then blk.call(msg)
|
591
|
+
when 2 then blk.call(msg.data, msg.reply)
|
592
|
+
when 3 then blk.call(msg.data, msg.reply, msg.subject)
|
593
|
+
else blk.call(msg.data, msg.reply, msg.subject, msg.header)
|
464
594
|
end
|
465
595
|
end
|
596
|
+
publish(subject, payload, inbox)
|
466
597
|
|
467
|
-
|
598
|
+
return s
|
468
599
|
end
|
469
600
|
|
470
|
-
#
|
471
|
-
#
|
472
|
-
|
473
|
-
|
474
|
-
# If given a callback, then the request happens asynchronously.
|
475
|
-
def request(subject, payload="", opts={}, &blk)
|
476
|
-
raise BadSubject if !subject or subject.empty?
|
477
|
-
|
478
|
-
# If a block was given then fallback to method using auto unsubscribe.
|
479
|
-
return old_request(subject, payload, opts, &blk) if blk
|
480
|
-
return old_request(subject, payload, opts) if opts[:old_style]
|
601
|
+
# In case block was not given, handle synchronously
|
602
|
+
# with a timeout and only allow a single response.
|
603
|
+
timeout = opts[:timeout] ||= 0.5
|
604
|
+
opts[:max] = 1
|
481
605
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
start_resp_mux_sub! unless @resp_sub_prefix
|
606
|
+
sub = Subscription.new
|
607
|
+
sub.subject = inbox
|
608
|
+
sub.received = 0
|
609
|
+
future = sub.new_cond
|
610
|
+
sub.future = future
|
611
|
+
sub.nc = self
|
489
612
|
|
490
|
-
|
491
|
-
|
492
|
-
|
613
|
+
sid = nil
|
614
|
+
synchronize do
|
615
|
+
sid = (@ssid += 1)
|
616
|
+
sub.sid = sid
|
617
|
+
@subs[sid] = sub
|
618
|
+
end
|
493
619
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
@resp_map[token][:future] = future
|
498
|
-
end
|
620
|
+
send_command("SUB #{inbox} #{sid}#{CR_LF}")
|
621
|
+
@flush_queue << :sub
|
622
|
+
unsubscribe(sub, 1)
|
499
623
|
|
500
|
-
|
624
|
+
sub.synchronize do
|
625
|
+
# Publish the request and then wait for the response...
|
501
626
|
publish(subject, payload, inbox)
|
502
|
-
begin
|
503
|
-
with_nats_timeout(timeout) do
|
504
|
-
@resp_sub.synchronize do
|
505
|
-
future.wait(timeout)
|
506
|
-
end
|
507
|
-
end
|
508
|
-
rescue NATS::IO::Timeout => e
|
509
|
-
synchronize { @resp_map.delete(token) }
|
510
|
-
raise e
|
511
|
-
end
|
512
|
-
|
513
|
-
# Check if there is a response already.
|
514
|
-
synchronize do
|
515
|
-
result = @resp_map[token]
|
516
|
-
response = result[:response]
|
517
|
-
@resp_map.delete(token)
|
518
|
-
end
|
519
627
|
|
520
|
-
|
521
|
-
|
522
|
-
raise NoRespondersError if status == "503"
|
628
|
+
MonotonicTime::with_nats_timeout(timeout) do
|
629
|
+
future.wait(timeout)
|
523
630
|
end
|
524
|
-
|
525
|
-
response
|
526
631
|
end
|
632
|
+
response = sub.response
|
527
633
|
|
528
|
-
|
529
|
-
|
530
|
-
raise
|
531
|
-
|
634
|
+
if response and response.header
|
635
|
+
status = response.header[STATUS_HDR]
|
636
|
+
raise NATS::IO::NoRespondersError if status == "503"
|
637
|
+
end
|
532
638
|
|
533
|
-
|
534
|
-
|
535
|
-
future = nil
|
536
|
-
response = nil
|
537
|
-
timeout = opts[:timeout] ||= 0.5
|
538
|
-
synchronize do
|
539
|
-
start_resp_mux_sub! unless @resp_sub_prefix
|
639
|
+
response
|
640
|
+
end
|
540
641
|
|
541
|
-
|
542
|
-
|
543
|
-
|
642
|
+
# Send a ping and wait for a pong back within a timeout.
|
643
|
+
def flush(timeout=60)
|
644
|
+
# Schedule sending a PING, and block until we receive PONG back,
|
645
|
+
# or raise a timeout in case the response is past the deadline.
|
646
|
+
pong = @pongs.new_cond
|
647
|
+
@pongs.synchronize do
|
648
|
+
@pongs << pong
|
544
649
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
650
|
+
# Flush once pong future has been prepared
|
651
|
+
@pending_queue << PING_REQUEST
|
652
|
+
@flush_queue << :ping
|
653
|
+
MonotonicTime::with_nats_timeout(timeout) do
|
654
|
+
pong.wait(timeout)
|
549
655
|
end
|
550
|
-
|
551
|
-
|
552
|
-
msg_size = msg.data.bytesize
|
656
|
+
end
|
657
|
+
end
|
553
658
|
|
554
|
-
|
555
|
-
publish_msg(msg)
|
556
|
-
begin
|
557
|
-
with_nats_timeout(timeout) do
|
558
|
-
@resp_sub.synchronize do
|
559
|
-
future.wait(timeout)
|
560
|
-
end
|
561
|
-
end
|
562
|
-
rescue NATS::IO::Timeout => e
|
563
|
-
synchronize { @resp_map.delete(token) }
|
564
|
-
raise e
|
565
|
-
end
|
659
|
+
alias :servers :server_pool
|
566
660
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
end
|
661
|
+
# discovered_servers returns the NATS Servers that have been discovered
|
662
|
+
# via INFO protocol updates.
|
663
|
+
def discovered_servers
|
664
|
+
servers.select {|s| s[:discovered] }
|
665
|
+
end
|
573
666
|
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
667
|
+
# Close connection to NATS, flushing in case connection is alive
|
668
|
+
# and there are any pending messages, should not be used while
|
669
|
+
# holding the lock.
|
670
|
+
def close
|
671
|
+
close_connection(CLOSED, true)
|
672
|
+
end
|
578
673
|
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
# is not retrieved within the specified deadline.
|
585
|
-
# If given a callback, then the request happens asynchronously.
|
586
|
-
def old_request(subject, payload, opts={}, &blk)
|
587
|
-
return unless subject
|
588
|
-
inbox = new_inbox
|
589
|
-
|
590
|
-
# If a callback was passed, then have it process
|
591
|
-
# the messages asynchronously and return the sid.
|
592
|
-
if blk
|
593
|
-
opts[:max] ||= 1
|
594
|
-
s = subscribe(inbox, opts) do |msg, reply, subject, header|
|
595
|
-
case blk.arity
|
596
|
-
when 0 then blk.call
|
597
|
-
when 1 then blk.call(msg)
|
598
|
-
when 2 then blk.call(msg, reply)
|
599
|
-
when 3 then blk.call(msg, reply, subject)
|
600
|
-
else blk.call(msg, reply, subject, header)
|
601
|
-
end
|
602
|
-
end
|
603
|
-
publish(subject, payload, inbox)
|
674
|
+
# new_inbox returns a unique inbox used for subscriptions.
|
675
|
+
# @return [String]
|
676
|
+
def new_inbox
|
677
|
+
"_INBOX.#{@nuid.next}"
|
678
|
+
end
|
604
679
|
|
605
|
-
|
606
|
-
|
680
|
+
def connected_server
|
681
|
+
connected? ? @uri : nil
|
682
|
+
end
|
607
683
|
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
opts[:max] = 1
|
684
|
+
def connected?
|
685
|
+
@status == CONNECTED
|
686
|
+
end
|
612
687
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
future = sub.new_cond
|
617
|
-
sub.future = future
|
688
|
+
def connecting?
|
689
|
+
@status == CONNECTING
|
690
|
+
end
|
618
691
|
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
@subs[sid] = sub
|
623
|
-
end
|
692
|
+
def reconnecting?
|
693
|
+
@status == RECONNECTING
|
694
|
+
end
|
624
695
|
|
625
|
-
|
626
|
-
|
627
|
-
|
696
|
+
def closed?
|
697
|
+
@status == CLOSED
|
698
|
+
end
|
628
699
|
|
629
|
-
|
630
|
-
|
631
|
-
|
700
|
+
def on_error(&callback)
|
701
|
+
@err_cb = callback
|
702
|
+
end
|
632
703
|
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
end
|
637
|
-
response = sub.response
|
704
|
+
def on_disconnect(&callback)
|
705
|
+
@disconnect_cb = callback
|
706
|
+
end
|
638
707
|
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
708
|
+
def on_reconnect(&callback)
|
709
|
+
@reconnect_cb = callback
|
710
|
+
end
|
711
|
+
|
712
|
+
def on_close(&callback)
|
713
|
+
@close_cb = callback
|
714
|
+
end
|
643
715
|
|
644
|
-
|
716
|
+
def last_error
|
717
|
+
synchronize do
|
718
|
+
@last_err
|
645
719
|
end
|
720
|
+
end
|
646
721
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
722
|
+
# Create a JetStream context.
|
723
|
+
# @param opts [Hash] Options to customize the JetStream context.
|
724
|
+
# @option params [String] :prefix JetStream API prefix to use for the requests.
|
725
|
+
# @option params [String] :domain JetStream Domain to use for the requests.
|
726
|
+
# @option params [Float] :timeout Default timeout to use for JS requests.
|
727
|
+
# @return [NATS::JetStream]
|
728
|
+
def jetstream(opts={})
|
729
|
+
::NATS::JetStream.new(self, opts)
|
730
|
+
end
|
731
|
+
alias_method :JetStream, :jetstream
|
732
|
+
alias_method :jsm, :jetstream
|
653
733
|
|
654
|
-
|
655
|
-
synchronize do
|
656
|
-
sub.max = opt_max
|
657
|
-
@subs.delete(sid) unless (sub.max && (sub.received < sub.max))
|
734
|
+
private
|
658
735
|
|
659
|
-
|
660
|
-
|
661
|
-
sub.wait_for_msgs_t.exit
|
662
|
-
sub.pending_queue.clear
|
663
|
-
end
|
664
|
-
end
|
665
|
-
end
|
736
|
+
def process_info(line)
|
737
|
+
parsed_info = JSON.parse(line)
|
666
738
|
|
667
|
-
#
|
668
|
-
|
669
|
-
|
670
|
-
#
|
671
|
-
|
672
|
-
|
673
|
-
@pongs << pong
|
739
|
+
# INFO can be received asynchronously too,
|
740
|
+
# so has to be done under the lock.
|
741
|
+
synchronize do
|
742
|
+
# Symbolize keys from parsed info line
|
743
|
+
@server_info = parsed_info.reduce({}) do |info, (k,v)|
|
744
|
+
info[k.to_sym] = v
|
674
745
|
|
675
|
-
|
676
|
-
@pending_queue << PING_REQUEST
|
677
|
-
@flush_queue << :ping
|
678
|
-
with_nats_timeout(timeout) do
|
679
|
-
pong.wait(timeout)
|
680
|
-
end
|
746
|
+
info
|
681
747
|
end
|
682
|
-
end
|
683
748
|
|
684
|
-
|
749
|
+
# Detect any announced server that we might not be aware of...
|
750
|
+
connect_urls = @server_info[:connect_urls]
|
751
|
+
if connect_urls
|
752
|
+
srvs = []
|
753
|
+
connect_urls.each do |url|
|
754
|
+
scheme = client_using_secure_connection? ? "tls" : "nats"
|
755
|
+
u = URI.parse("#{scheme}://#{url}")
|
685
756
|
|
686
|
-
|
687
|
-
|
688
|
-
end
|
757
|
+
# Skip in case it is the current server which we already know
|
758
|
+
next if @uri.host == u.host && @uri.port == u.port
|
689
759
|
|
690
|
-
|
760
|
+
present = server_pool.detect do |srv|
|
761
|
+
srv[:uri].host == u.host && srv[:uri].port == u.port
|
762
|
+
end
|
691
763
|
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
pong.signal unless pong.nil?
|
697
|
-
end
|
698
|
-
@pings_outstanding -= 1
|
699
|
-
@pongs_received += 1
|
700
|
-
end
|
764
|
+
if not present
|
765
|
+
# Let explicit user and pass options set the credentials.
|
766
|
+
u.user = options[:user] if options[:user]
|
767
|
+
u.password = options[:pass] if options[:pass]
|
701
768
|
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
@pongs.synchronize { @pongs << pong }
|
708
|
-
end
|
769
|
+
# Use creds from the current server if not set explicitly.
|
770
|
+
if @uri
|
771
|
+
u.user ||= @uri.user if @uri.user
|
772
|
+
u.password ||= @uri.password if @uri.password
|
773
|
+
end
|
709
774
|
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
e = synchronize do
|
715
|
-
current = server_pool.first
|
716
|
-
case
|
717
|
-
when err =~ /'Stale Connection'/
|
718
|
-
@last_err = NATS::IO::StaleConnectionError.new(err)
|
719
|
-
when current && current[:auth_required]
|
720
|
-
# We cannot recover from auth errors so mark it to avoid
|
721
|
-
# retrying to unecessarily next time.
|
722
|
-
current[:error_received] = true
|
723
|
-
@last_err = NATS::IO::AuthError.new(err)
|
724
|
-
else
|
725
|
-
@last_err = NATS::IO::ServerError.new(err)
|
775
|
+
# NOTE: Auto discovery won't work here when TLS host verification is enabled.
|
776
|
+
srv = { :uri => u, :reconnect_attempts => 0, :discovered => true, :hostname => u.host }
|
777
|
+
srvs << srv
|
778
|
+
end
|
726
779
|
end
|
780
|
+
srvs.shuffle! unless @options[:dont_randomize_servers]
|
781
|
+
|
782
|
+
# Include in server pool but keep current one as the first one.
|
783
|
+
server_pool.push(*srvs)
|
727
784
|
end
|
728
|
-
process_op_error(e)
|
729
785
|
end
|
730
786
|
|
731
|
-
|
732
|
-
|
733
|
-
@stats[:in_bytes] += data.size
|
787
|
+
@server_info
|
788
|
+
end
|
734
789
|
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
790
|
+
def process_hdr(header)
|
791
|
+
hdr = nil
|
792
|
+
if header
|
793
|
+
hdr = {}
|
794
|
+
lines = header.lines
|
739
795
|
|
740
|
-
|
741
|
-
|
742
|
-
|
796
|
+
# Check if it is an inline status and description.
|
797
|
+
if lines.count <= 2
|
798
|
+
status_hdr = lines.first.rstrip
|
799
|
+
hdr[STATUS_HDR] = status_hdr.slice(NATS_HDR_LINE_SIZE-1, STATUS_MSG_LEN)
|
743
800
|
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
when sub.received > sub.max
|
748
|
-
# Client side support in case server did not receive unsubscribe
|
749
|
-
unsubscribe(sid)
|
750
|
-
return
|
751
|
-
when sub.received == sub.max
|
752
|
-
# Cleanup here if we have hit the max..
|
753
|
-
synchronize { @subs.delete(sid) }
|
754
|
-
end
|
801
|
+
if NATS_HDR_LINE_SIZE+2 < status_hdr.bytesize
|
802
|
+
desc = status_hdr.slice(NATS_HDR_LINE_SIZE+STATUS_MSG_LEN, status_hdr.bytesize)
|
803
|
+
hdr[DESC_HDR] = desc unless desc.empty?
|
755
804
|
end
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
future.signal
|
764
|
-
|
765
|
-
return
|
766
|
-
elsif sub.pending_queue
|
767
|
-
# Async subscribers use a sized queue for processing
|
768
|
-
# and should be able to consume messages in parallel.
|
769
|
-
if sub.pending_queue.size >= sub.pending_msgs_limit \
|
770
|
-
or sub.pending_size >= sub.pending_bytes_limit then
|
771
|
-
err = SlowConsumer.new("nats: slow consumer, messages dropped")
|
772
|
-
else
|
773
|
-
hdr = process_hdr(header)
|
774
|
-
|
775
|
-
# Only dispatch message when sure that it would not block
|
776
|
-
# the main read loop from the parser.
|
777
|
-
msg = Msg.new(subject: subject, reply: reply, data: data, header: hdr)
|
778
|
-
sub.pending_queue << msg
|
779
|
-
sub.pending_size += data.size
|
780
|
-
end
|
805
|
+
end
|
806
|
+
begin
|
807
|
+
lines.slice(1, header.size).each do |line|
|
808
|
+
line.rstrip!
|
809
|
+
next if line.empty?
|
810
|
+
key, value = line.strip.split(/\s*:\s*/, 2)
|
811
|
+
hdr[key] = value
|
781
812
|
end
|
813
|
+
rescue => e
|
814
|
+
err = e
|
782
815
|
end
|
783
|
-
|
784
|
-
synchronize do
|
785
|
-
@last_err = err
|
786
|
-
@err_cb.call(err) if @err_cb
|
787
|
-
end if err
|
788
816
|
end
|
789
817
|
|
790
|
-
|
791
|
-
|
792
|
-
if header
|
793
|
-
hdr = {}
|
794
|
-
lines = header.lines
|
795
|
-
|
796
|
-
# Check if it is an inline status and description.
|
797
|
-
if lines.count <= 2
|
798
|
-
status_hdr = lines.first.rstrip
|
799
|
-
hdr[STATUS_HDR] = status_hdr.slice(NATS_HDR_LINE_SIZE-1, STATUS_MSG_LEN)
|
818
|
+
hdr
|
819
|
+
end
|
800
820
|
|
801
|
-
|
802
|
-
desc = status_hdr.slice(NATS_HDR_LINE_SIZE+STATUS_MSG_LEN, status_hdr.bytesize)
|
803
|
-
hdr[DESC_HDR] = desc unless desc.empty?
|
804
|
-
end
|
805
|
-
end
|
806
|
-
begin
|
807
|
-
lines.slice(1, header.size).each do |line|
|
808
|
-
line.rstrip!
|
809
|
-
next if line.empty?
|
810
|
-
key, value = line.strip.split(/\s*:\s*/, 2)
|
811
|
-
hdr[key] = value
|
812
|
-
end
|
813
|
-
rescue => e
|
814
|
-
err = e
|
815
|
-
end
|
816
|
-
end
|
821
|
+
# Methods only used by the parser
|
817
822
|
|
818
|
-
|
823
|
+
def process_pong
|
824
|
+
# Take first pong wait and signal any flush in case there was one
|
825
|
+
@pongs.synchronize do
|
826
|
+
pong = @pongs.pop
|
827
|
+
pong.signal unless pong.nil?
|
819
828
|
end
|
829
|
+
@pings_outstanding -= 1
|
830
|
+
@pongs_received += 1
|
831
|
+
end
|
820
832
|
|
821
|
-
|
822
|
-
|
833
|
+
# Received a ping so respond back with a pong
|
834
|
+
def process_ping
|
835
|
+
@pending_queue << PONG_RESPONSE
|
836
|
+
@flush_queue << :ping
|
837
|
+
pong = @pongs.new_cond
|
838
|
+
@pongs.synchronize { @pongs << pong }
|
839
|
+
end
|
823
840
|
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
841
|
+
# Handles protocol errors being sent by the server.
|
842
|
+
def process_err(err)
|
843
|
+
# In case of permissions violation then dispatch the error callback
|
844
|
+
# while holding the lock.
|
845
|
+
e = synchronize do
|
846
|
+
current = server_pool.first
|
847
|
+
case
|
848
|
+
when err =~ /'Stale Connection'/
|
849
|
+
@last_err = NATS::IO::StaleConnectionError.new(err)
|
850
|
+
when current && current[:auth_required]
|
851
|
+
# We cannot recover from auth errors so mark it to avoid
|
852
|
+
# retrying to unecessarily next time.
|
853
|
+
current[:error_received] = true
|
854
|
+
@last_err = NATS::IO::AuthError.new(err)
|
855
|
+
else
|
856
|
+
@last_err = NATS::IO::ServerError.new(err)
|
857
|
+
end
|
858
|
+
end
|
859
|
+
process_op_error(e)
|
860
|
+
end
|
830
861
|
|
831
|
-
|
832
|
-
|
862
|
+
def process_msg(subject, sid, reply, data, header)
|
863
|
+
@stats[:in_msgs] += 1
|
864
|
+
@stats[:in_bytes] += data.size
|
833
865
|
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
connect_urls.each do |url|
|
839
|
-
scheme = client_using_secure_connection? ? "tls" : "nats"
|
840
|
-
u = URI.parse("#{scheme}://#{url}")
|
866
|
+
# Throw away in case we no longer manage the subscription
|
867
|
+
sub = nil
|
868
|
+
synchronize { sub = @subs[sid] }
|
869
|
+
return unless sub
|
841
870
|
|
842
|
-
|
843
|
-
|
871
|
+
err = nil
|
872
|
+
sub.synchronize do
|
873
|
+
sub.received += 1
|
844
874
|
|
845
|
-
|
846
|
-
|
847
|
-
|
875
|
+
# Check for auto_unsubscribe
|
876
|
+
if sub.max
|
877
|
+
case
|
878
|
+
when sub.received > sub.max
|
879
|
+
# Client side support in case server did not receive unsubscribe
|
880
|
+
unsubscribe(sid)
|
881
|
+
return
|
882
|
+
when sub.received == sub.max
|
883
|
+
# Cleanup here if we have hit the max..
|
884
|
+
synchronize { @subs.delete(sid) }
|
885
|
+
end
|
886
|
+
end
|
848
887
|
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
888
|
+
# In case of a request which requires a future
|
889
|
+
# do so here already while holding the lock and return
|
890
|
+
if sub.future
|
891
|
+
future = sub.future
|
892
|
+
hdr = process_hdr(header)
|
893
|
+
sub.response = Msg.new(subject: subject, reply: reply, data: data, header: hdr, nc: self, sub: sub)
|
894
|
+
future.signal
|
895
|
+
|
896
|
+
return
|
897
|
+
elsif sub.pending_queue
|
898
|
+
# Async subscribers use a sized queue for processing
|
899
|
+
# and should be able to consume messages in parallel.
|
900
|
+
if sub.pending_queue.size >= sub.pending_msgs_limit \
|
901
|
+
or sub.pending_size >= sub.pending_bytes_limit then
|
902
|
+
err = NATS::IO::SlowConsumer.new("nats: slow consumer, messages dropped")
|
903
|
+
else
|
904
|
+
hdr = process_hdr(header)
|
853
905
|
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
end
|
906
|
+
# Only dispatch message when sure that it would not block
|
907
|
+
# the main read loop from the parser.
|
908
|
+
msg = Msg.new(subject: subject, reply: reply, data: data, header: hdr, nc: self, sub: sub)
|
909
|
+
sub.pending_queue << msg
|
859
910
|
|
860
|
-
|
861
|
-
|
862
|
-
srvs << srv
|
863
|
-
end
|
864
|
-
end
|
865
|
-
srvs.shuffle! unless @options[:dont_randomize_servers]
|
911
|
+
# For sync subscribers, signal that there is a new message.
|
912
|
+
sub.wait_for_msgs_cond.signal if sub.wait_for_msgs_cond
|
866
913
|
|
867
|
-
|
868
|
-
server_pool.push(*srvs)
|
914
|
+
sub.pending_size += data.size
|
869
915
|
end
|
870
916
|
end
|
871
|
-
|
872
|
-
@server_info
|
873
917
|
end
|
874
918
|
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
end
|
919
|
+
synchronize do
|
920
|
+
@last_err = err
|
921
|
+
err_cb_call(self, err, sub) if @err_cb
|
922
|
+
end if err
|
923
|
+
end
|
881
924
|
|
882
|
-
|
883
|
-
|
884
|
-
end
|
925
|
+
def select_next_server
|
926
|
+
raise NATS::IO::NoServersError.new("nats: No servers available") if server_pool.empty?
|
885
927
|
|
886
|
-
|
887
|
-
|
888
|
-
end
|
928
|
+
# Pick next from head of the list
|
929
|
+
srv = server_pool.shift
|
889
930
|
|
890
|
-
|
891
|
-
|
892
|
-
|
931
|
+
# Track connection attempts to this server
|
932
|
+
srv[:reconnect_attempts] ||= 0
|
933
|
+
srv[:reconnect_attempts] += 1
|
893
934
|
|
894
|
-
|
895
|
-
|
896
|
-
end
|
935
|
+
# Back off in case we are reconnecting to it and have been connected
|
936
|
+
sleep @options[:reconnect_time_wait] if should_delay_connect?(srv)
|
897
937
|
|
898
|
-
|
899
|
-
|
900
|
-
|
938
|
+
# Set url of the server to which we would be connected
|
939
|
+
@uri = srv[:uri]
|
940
|
+
@uri.user = @options[:user] if @options[:user]
|
941
|
+
@uri.password = @options[:pass] if @options[:pass]
|
901
942
|
|
902
|
-
|
903
|
-
|
904
|
-
end
|
943
|
+
srv
|
944
|
+
end
|
905
945
|
|
906
|
-
|
907
|
-
|
908
|
-
|
946
|
+
def server_using_secure_connection?
|
947
|
+
@server_info[:ssl_required] || @server_info[:tls_required]
|
948
|
+
end
|
909
949
|
|
910
|
-
|
911
|
-
|
912
|
-
|
950
|
+
def client_using_secure_connection?
|
951
|
+
@uri.scheme == "tls" || @tls
|
952
|
+
end
|
913
953
|
|
914
|
-
|
915
|
-
|
916
|
-
|
954
|
+
def single_url_connect_used?
|
955
|
+
@single_url_connect_used
|
956
|
+
end
|
917
957
|
|
918
|
-
|
919
|
-
|
920
|
-
|
958
|
+
def send_command(command)
|
959
|
+
@pending_size += command.bytesize
|
960
|
+
@pending_queue << command
|
921
961
|
|
922
|
-
|
923
|
-
|
924
|
-
|
962
|
+
# TODO: kick flusher here in case pending_size growing large
|
963
|
+
end
|
964
|
+
|
965
|
+
# Auto unsubscribes the server by sending UNSUB command and throws away
|
966
|
+
# subscription in case already present and has received enough messages.
|
967
|
+
def unsubscribe(sub, opt_max=nil)
|
968
|
+
sid = nil
|
969
|
+
closed = nil
|
970
|
+
sub.synchronize do
|
971
|
+
sid = sub.sid
|
972
|
+
closed = sub.closed
|
973
|
+
end
|
974
|
+
raise NATS::IO::BadSubscription.new("nats: invalid subscription") if closed
|
975
|
+
|
976
|
+
opt_max_str = " #{opt_max}" unless opt_max.nil?
|
977
|
+
send_command("UNSUB #{sid}#{opt_max_str}#{CR_LF}")
|
978
|
+
@flush_queue << :unsub
|
979
|
+
|
980
|
+
synchronize { sub = @subs[sid] }
|
981
|
+
return unless sub
|
982
|
+
synchronize do
|
983
|
+
sub.max = opt_max
|
984
|
+
@subs.delete(sid) unless (sub.max && (sub.received < sub.max))
|
985
|
+
|
986
|
+
# Stop messages delivery thread for async subscribers
|
987
|
+
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
988
|
+
sub.wait_for_msgs_t.exit
|
989
|
+
sub.pending_queue.clear
|
925
990
|
end
|
926
991
|
end
|
927
992
|
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
# Pick next from head of the list
|
934
|
-
srv = server_pool.shift
|
993
|
+
sub.synchronize do
|
994
|
+
sub.closed = true
|
995
|
+
end
|
996
|
+
end
|
935
997
|
|
936
|
-
|
937
|
-
|
938
|
-
|
998
|
+
def send_flush_queue(s)
|
999
|
+
@flush_queue << s
|
1000
|
+
end
|
939
1001
|
|
940
|
-
|
941
|
-
|
1002
|
+
def delete_sid(sid)
|
1003
|
+
@subs.delete(sid)
|
1004
|
+
end
|
942
1005
|
|
943
|
-
|
944
|
-
|
945
|
-
@uri.user = @options[:user] if @options[:user]
|
946
|
-
@uri.password = @options[:pass] if @options[:pass]
|
1006
|
+
def err_cb_call(nc, e, sub)
|
1007
|
+
return unless @err_cb
|
947
1008
|
|
948
|
-
|
1009
|
+
cb = @err_cb
|
1010
|
+
case cb.arity
|
1011
|
+
when 0 then cb.call
|
1012
|
+
when 1 then cb.call(e)
|
1013
|
+
when 2 then cb.call(e, sub)
|
1014
|
+
else cb.call(nc, e, sub)
|
949
1015
|
end
|
1016
|
+
end
|
950
1017
|
|
951
|
-
|
952
|
-
|
953
|
-
|
1018
|
+
def auth_connection?
|
1019
|
+
!@uri.user.nil?
|
1020
|
+
end
|
954
1021
|
|
955
|
-
|
956
|
-
|
1022
|
+
def connect_command
|
1023
|
+
cs = {
|
1024
|
+
:verbose => @options[:verbose],
|
1025
|
+
:pedantic => @options[:pedantic],
|
1026
|
+
:lang => NATS::IO::LANG,
|
1027
|
+
:version => NATS::IO::VERSION,
|
1028
|
+
:protocol => NATS::IO::PROTOCOL
|
1029
|
+
}
|
1030
|
+
cs[:name] = @options[:name] if @options[:name]
|
1031
|
+
|
1032
|
+
case
|
1033
|
+
when auth_connection?
|
1034
|
+
if @uri.password
|
1035
|
+
cs[:user] = @uri.user
|
1036
|
+
cs[:pass] = @uri.password
|
1037
|
+
else
|
1038
|
+
cs[:auth_token] = @uri.user
|
1039
|
+
end
|
1040
|
+
when @user_credentials
|
1041
|
+
nonce = @server_info[:nonce]
|
1042
|
+
cs[:jwt] = @user_jwt_cb.call
|
1043
|
+
cs[:sig] = @signature_cb.call(nonce)
|
1044
|
+
when @nkeys_seed
|
1045
|
+
nonce = @server_info[:nonce]
|
1046
|
+
cs[:nkey] = @user_nkey_cb.call
|
1047
|
+
cs[:sig] = @signature_cb.call(nonce)
|
957
1048
|
end
|
958
1049
|
|
959
|
-
|
960
|
-
@
|
1050
|
+
if @server_info[:headers]
|
1051
|
+
cs[:headers] = @server_info[:headers]
|
1052
|
+
cs[:no_responders] = if @options[:no_responders] == false
|
1053
|
+
@options[:no_responders]
|
1054
|
+
else
|
1055
|
+
@server_info[:headers]
|
1056
|
+
end
|
961
1057
|
end
|
962
1058
|
|
963
|
-
|
964
|
-
|
965
|
-
@pending_queue << command
|
1059
|
+
"CONNECT #{cs.to_json}#{CR_LF}"
|
1060
|
+
end
|
966
1061
|
|
967
|
-
|
1062
|
+
# Handles errors from reading, parsing the protocol or stale connection.
|
1063
|
+
# the lock should not be held entering this function.
|
1064
|
+
def process_op_error(e)
|
1065
|
+
should_bail = synchronize do
|
1066
|
+
connecting? || closed? || reconnecting?
|
968
1067
|
end
|
1068
|
+
return if should_bail
|
969
1069
|
|
970
|
-
|
971
|
-
|
972
|
-
|
1070
|
+
synchronize do
|
1071
|
+
@last_err = e
|
1072
|
+
err_cb_call(self, e, nil) if @err_cb
|
973
1073
|
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
:protocol => NATS::IO::PROTOCOL
|
981
|
-
}
|
982
|
-
cs[:name] = @options[:name] if @options[:name]
|
1074
|
+
# If we were connected and configured to reconnect,
|
1075
|
+
# then trigger disconnect and start reconnection logic
|
1076
|
+
if connected? and should_reconnect?
|
1077
|
+
@status = RECONNECTING
|
1078
|
+
@io.close if @io
|
1079
|
+
@io = nil
|
983
1080
|
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
1081
|
+
# TODO: Reconnecting pending buffer?
|
1082
|
+
|
1083
|
+
# Do reconnect under a different thread than the one
|
1084
|
+
# in which we got the error.
|
1085
|
+
Thread.new do
|
1086
|
+
begin
|
1087
|
+
# Abort currently running reads in case they're around
|
1088
|
+
# FIXME: There might be more graceful way here...
|
1089
|
+
@read_loop_thread.exit if @read_loop_thread.alive?
|
1090
|
+
@flusher_thread.exit if @flusher_thread.alive?
|
1091
|
+
@ping_interval_thread.exit if @ping_interval_thread.alive?
|
1092
|
+
|
1093
|
+
attempt_reconnect
|
1094
|
+
rescue NATS::IO::NoServersError => e
|
1095
|
+
@last_err = e
|
1096
|
+
close
|
1097
|
+
end
|
991
1098
|
end
|
992
|
-
when @user_credentials
|
993
|
-
nonce = @server_info[:nonce]
|
994
|
-
cs[:jwt] = @user_jwt_cb.call
|
995
|
-
cs[:sig] = @signature_cb.call(nonce)
|
996
|
-
when @nkeys_seed
|
997
|
-
nonce = @server_info[:nonce]
|
998
|
-
cs[:nkey] = @user_nkey_cb.call
|
999
|
-
cs[:sig] = @signature_cb.call(nonce)
|
1000
|
-
end
|
1001
1099
|
|
1002
|
-
|
1003
|
-
|
1004
|
-
cs[:no_responders] = if @options[:no_responders] == false
|
1005
|
-
@options[:no_responders]
|
1006
|
-
else
|
1007
|
-
@server_info[:headers]
|
1008
|
-
end
|
1100
|
+
Thread.exit
|
1101
|
+
return
|
1009
1102
|
end
|
1010
1103
|
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
def with_nats_timeout(timeout)
|
1015
|
-
start_time = MonotonicTime.now
|
1016
|
-
yield
|
1017
|
-
end_time = MonotonicTime.now
|
1018
|
-
duration = end_time - start_time
|
1019
|
-
raise NATS::IO::Timeout.new("nats: timeout") if duration > timeout
|
1104
|
+
# Otherwise, stop trying to reconnect and close the connection
|
1105
|
+
@status = DISCONNECTED
|
1020
1106
|
end
|
1021
1107
|
|
1022
|
-
#
|
1023
|
-
|
1024
|
-
|
1025
|
-
should_bail = synchronize do
|
1026
|
-
connecting? || closed? || reconnecting?
|
1027
|
-
end
|
1028
|
-
return if should_bail
|
1029
|
-
|
1030
|
-
synchronize do
|
1031
|
-
@last_err = e
|
1032
|
-
@err_cb.call(e) if @err_cb
|
1033
|
-
|
1034
|
-
# If we were connected and configured to reconnect,
|
1035
|
-
# then trigger disconnect and start reconnection logic
|
1036
|
-
if connected? and should_reconnect?
|
1037
|
-
@status = RECONNECTING
|
1038
|
-
@io.close if @io
|
1039
|
-
@io = nil
|
1040
|
-
|
1041
|
-
# TODO: Reconnecting pending buffer?
|
1042
|
-
|
1043
|
-
# Do reconnect under a different thread than the one
|
1044
|
-
# in which we got the error.
|
1045
|
-
Thread.new do
|
1046
|
-
begin
|
1047
|
-
# Abort currently running reads in case they're around
|
1048
|
-
# FIXME: There might be more graceful way here...
|
1049
|
-
@read_loop_thread.exit if @read_loop_thread.alive?
|
1050
|
-
@flusher_thread.exit if @flusher_thread.alive?
|
1051
|
-
@ping_interval_thread.exit if @ping_interval_thread.alive?
|
1052
|
-
|
1053
|
-
attempt_reconnect
|
1054
|
-
rescue NoServersError => e
|
1055
|
-
@last_err = e
|
1056
|
-
close
|
1057
|
-
end
|
1058
|
-
end
|
1108
|
+
# Otherwise close the connection to NATS
|
1109
|
+
close
|
1110
|
+
end
|
1059
1111
|
|
1060
|
-
|
1112
|
+
# Gathers data from the socket and sends it to the parser.
|
1113
|
+
def read_loop
|
1114
|
+
loop do
|
1115
|
+
begin
|
1116
|
+
should_bail = synchronize do
|
1117
|
+
# FIXME: In case of reconnect as well?
|
1118
|
+
@status == CLOSED or @status == RECONNECTING
|
1119
|
+
end
|
1120
|
+
if !@io or @io.closed? or should_bail
|
1061
1121
|
return
|
1062
1122
|
end
|
1063
1123
|
|
1064
|
-
#
|
1065
|
-
|
1124
|
+
# TODO: Remove timeout and just wait to be ready
|
1125
|
+
data = @io.read(NATS::IO::MAX_SOCKET_READ_BYTES)
|
1126
|
+
@parser.parse(data) if data
|
1127
|
+
rescue Errno::ETIMEDOUT
|
1128
|
+
# FIXME: We do not really need a timeout here...
|
1129
|
+
retry
|
1130
|
+
rescue => e
|
1131
|
+
# In case of reading/parser errors, trigger
|
1132
|
+
# reconnection logic in case desired.
|
1133
|
+
process_op_error(e)
|
1066
1134
|
end
|
1067
|
-
|
1068
|
-
# Otherwise close the connection to NATS
|
1069
|
-
close
|
1070
1135
|
end
|
1136
|
+
end
|
1071
1137
|
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
@status == CLOSED or @status == RECONNECTING
|
1079
|
-
end
|
1080
|
-
if !@io or @io.closed? or should_bail
|
1081
|
-
return
|
1082
|
-
end
|
1138
|
+
# Waits for client to notify the flusher that it will be
|
1139
|
+
# it is sending a command.
|
1140
|
+
def flusher_loop
|
1141
|
+
loop do
|
1142
|
+
# Blocks waiting for the flusher to be kicked...
|
1143
|
+
@flush_queue.pop
|
1083
1144
|
|
1084
|
-
|
1085
|
-
|
1086
|
-
@parser.parse(data) if data
|
1087
|
-
rescue Errno::ETIMEDOUT
|
1088
|
-
# FIXME: We do not really need a timeout here...
|
1089
|
-
retry
|
1090
|
-
rescue => e
|
1091
|
-
# In case of reading/parser errors, trigger
|
1092
|
-
# reconnection logic in case desired.
|
1093
|
-
process_op_error(e)
|
1094
|
-
end
|
1145
|
+
should_bail = synchronize do
|
1146
|
+
@status != CONNECTED || @status == CONNECTING
|
1095
1147
|
end
|
1096
|
-
|
1148
|
+
return if should_bail
|
1097
1149
|
|
1098
|
-
|
1099
|
-
|
1100
|
-
def flusher_loop
|
1101
|
-
loop do
|
1102
|
-
# Blocks waiting for the flusher to be kicked...
|
1103
|
-
@flush_queue.pop
|
1150
|
+
# Skip in case nothing remains pending already.
|
1151
|
+
next if @pending_queue.empty?
|
1104
1152
|
|
1105
|
-
|
1106
|
-
|
1153
|
+
# FIXME: should limit how many commands to take at once
|
1154
|
+
# since producers could be adding as many as possible
|
1155
|
+
# until reaching the max pending queue size.
|
1156
|
+
cmds = []
|
1157
|
+
cmds << @pending_queue.pop until @pending_queue.empty?
|
1158
|
+
begin
|
1159
|
+
@io.write(cmds.join) unless cmds.empty?
|
1160
|
+
rescue => e
|
1161
|
+
synchronize do
|
1162
|
+
@last_err = e
|
1163
|
+
err_cb_call(self, e, nil) if @err_cb
|
1107
1164
|
end
|
1108
|
-
return if should_bail
|
1109
|
-
|
1110
|
-
# Skip in case nothing remains pending already.
|
1111
|
-
next if @pending_queue.empty?
|
1112
|
-
|
1113
|
-
# FIXME: should limit how many commands to take at once
|
1114
|
-
# since producers could be adding as many as possible
|
1115
|
-
# until reaching the max pending queue size.
|
1116
|
-
cmds = []
|
1117
|
-
cmds << @pending_queue.pop until @pending_queue.empty?
|
1118
|
-
begin
|
1119
|
-
@io.write(cmds.join) unless cmds.empty?
|
1120
|
-
rescue => e
|
1121
|
-
synchronize do
|
1122
|
-
@last_err = e
|
1123
|
-
@err_cb.call(e) if @err_cb
|
1124
|
-
end
|
1125
1165
|
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1166
|
+
process_op_error(e)
|
1167
|
+
return
|
1168
|
+
end if @io
|
1129
1169
|
|
1130
|
-
|
1131
|
-
|
1132
|
-
end
|
1170
|
+
synchronize do
|
1171
|
+
@pending_size = 0
|
1133
1172
|
end
|
1134
1173
|
end
|
1174
|
+
end
|
1135
1175
|
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
# Skip ping interval until connected
|
1141
|
-
next if !connected?
|
1176
|
+
def ping_interval_loop
|
1177
|
+
loop do
|
1178
|
+
sleep @options[:ping_interval]
|
1142
1179
|
|
1143
|
-
|
1144
|
-
|
1145
|
-
return
|
1146
|
-
end
|
1180
|
+
# Skip ping interval until connected
|
1181
|
+
next if !connected?
|
1147
1182
|
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1183
|
+
if @pings_outstanding >= @options[:max_outstanding_pings]
|
1184
|
+
process_op_error(NATS::IO::StaleConnectionError.new("nats: stale connection"))
|
1185
|
+
return
|
1151
1186
|
end
|
1152
|
-
|
1153
|
-
|
1187
|
+
|
1188
|
+
@pings_outstanding += 1
|
1189
|
+
send_command(PING_REQUEST)
|
1190
|
+
@flush_queue << :ping
|
1191
|
+
end
|
1192
|
+
rescue => e
|
1193
|
+
process_op_error(e)
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
def process_connect_init
|
1197
|
+
line = @io.read_line(options[:connect_timeout])
|
1198
|
+
if !line or line.empty?
|
1199
|
+
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not received")
|
1154
1200
|
end
|
1155
1201
|
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1202
|
+
if match = line.match(NATS::Protocol::INFO)
|
1203
|
+
info_json = match.captures.first
|
1204
|
+
process_info(info_json)
|
1205
|
+
else
|
1206
|
+
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not valid")
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
case
|
1210
|
+
when (server_using_secure_connection? and client_using_secure_connection?)
|
1211
|
+
tls_context = nil
|
1161
1212
|
|
1162
|
-
if
|
1163
|
-
|
1164
|
-
|
1213
|
+
if @tls
|
1214
|
+
# Allow prepared context and customizations via :tls opts
|
1215
|
+
tls_context = @tls[:context] if @tls[:context]
|
1165
1216
|
else
|
1166
|
-
|
1217
|
+
# Defaults
|
1218
|
+
tls_context = OpenSSL::SSL::SSLContext.new
|
1219
|
+
|
1220
|
+
# Use the default verification options from Ruby:
|
1221
|
+
# https://github.com/ruby/ruby/blob/96db72ce38b27799dd8e80ca00696e41234db6ba/ext/openssl/lib/openssl/ssl.rb#L19-L29
|
1222
|
+
#
|
1223
|
+
# Insecure TLS versions not supported already:
|
1224
|
+
# https://github.com/ruby/openssl/commit/3e5a009966bd7f806f7180d82cf830a04be28986
|
1225
|
+
#
|
1226
|
+
tls_context.set_params
|
1167
1227
|
end
|
1168
1228
|
|
1169
|
-
|
1170
|
-
|
1171
|
-
tls_context = nil
|
1172
|
-
|
1173
|
-
if @tls
|
1174
|
-
# Allow prepared context and customizations via :tls opts
|
1175
|
-
tls_context = @tls[:context] if @tls[:context]
|
1176
|
-
else
|
1177
|
-
# Defaults
|
1178
|
-
tls_context = OpenSSL::SSL::SSLContext.new
|
1179
|
-
|
1180
|
-
# Use the default verification options from Ruby:
|
1181
|
-
# https://github.com/ruby/ruby/blob/96db72ce38b27799dd8e80ca00696e41234db6ba/ext/openssl/lib/openssl/ssl.rb#L19-L29
|
1182
|
-
#
|
1183
|
-
# Insecure TLS versions not supported already:
|
1184
|
-
# https://github.com/ruby/openssl/commit/3e5a009966bd7f806f7180d82cf830a04be28986
|
1185
|
-
#
|
1186
|
-
tls_context.set_params
|
1187
|
-
end
|
1188
|
-
|
1189
|
-
# Setup TLS connection by rewrapping the socket
|
1190
|
-
tls_socket = OpenSSL::SSL::SSLSocket.new(@io.socket, tls_context)
|
1229
|
+
# Setup TLS connection by rewrapping the socket
|
1230
|
+
tls_socket = OpenSSL::SSL::SSLSocket.new(@io.socket, tls_context)
|
1191
1231
|
|
1192
|
-
|
1193
|
-
|
1232
|
+
# Close TCP socket after closing TLS socket as well.
|
1233
|
+
tls_socket.sync_close = true
|
1194
1234
|
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1235
|
+
# Required to enable hostname verification if Ruby runtime supports it (>= 2.4):
|
1236
|
+
# https://github.com/ruby/openssl/commit/028e495734e9e6aa5dba1a2e130b08f66cf31a21
|
1237
|
+
tls_socket.hostname = @hostname
|
1198
1238
|
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1239
|
+
tls_socket.connect
|
1240
|
+
@io.socket = tls_socket
|
1241
|
+
when (server_using_secure_connection? and !client_using_secure_connection?)
|
1242
|
+
raise NATS::IO::ConnectError.new('TLS/SSL required by server')
|
1243
|
+
when (client_using_secure_connection? and !server_using_secure_connection?)
|
1244
|
+
raise NATS::IO::ConnectError.new('TLS/SSL not supported by server')
|
1245
|
+
else
|
1246
|
+
# Otherwise, use a regular connection.
|
1247
|
+
end
|
1208
1248
|
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1249
|
+
# Send connect and process synchronously. If using TLS,
|
1250
|
+
# it should have handled upgrading at this point.
|
1251
|
+
@io.write(connect_command)
|
1212
1252
|
|
1213
|
-
|
1214
|
-
|
1253
|
+
# Send ping/pong after connect
|
1254
|
+
@io.write(PING_REQUEST)
|
1215
1255
|
|
1256
|
+
next_op = @io.read_line(options[:connect_timeout])
|
1257
|
+
if @options[:verbose]
|
1258
|
+
# Need to get another command here if verbose
|
1259
|
+
raise NATS::IO::ConnectError.new("expected to receive +OK") unless next_op =~ NATS::Protocol::OK
|
1216
1260
|
next_op = @io.read_line(options[:connect_timeout])
|
1217
|
-
|
1218
|
-
# Need to get another command here if verbose
|
1219
|
-
raise NATS::IO::ConnectError.new("expected to receive +OK") unless next_op =~ NATS::Protocol::OK
|
1220
|
-
next_op = @io.read_line(options[:connect_timeout])
|
1221
|
-
end
|
1261
|
+
end
|
1222
1262
|
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
else
|
1229
|
-
raise NATS::IO::ServerError.new($1)
|
1230
|
-
end
|
1263
|
+
case next_op
|
1264
|
+
when NATS::Protocol::PONG
|
1265
|
+
when NATS::Protocol::ERR
|
1266
|
+
if @server_info[:auth_required]
|
1267
|
+
raise NATS::IO::AuthError.new($1)
|
1231
1268
|
else
|
1232
|
-
raise NATS::IO::
|
1269
|
+
raise NATS::IO::ServerError.new($1)
|
1233
1270
|
end
|
1271
|
+
else
|
1272
|
+
raise NATS::IO::ConnectError.new("expected PONG, got #{next_op}")
|
1234
1273
|
end
|
1274
|
+
end
|
1235
1275
|
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1276
|
+
# Reconnect logic, this is done while holding the lock.
|
1277
|
+
def attempt_reconnect
|
1278
|
+
@disconnect_cb.call(@last_err) if @disconnect_cb
|
1239
1279
|
|
1240
|
-
|
1241
|
-
|
1280
|
+
# Clear sticky error
|
1281
|
+
@last_err = nil
|
1242
1282
|
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1283
|
+
# Do reconnect
|
1284
|
+
srv = nil
|
1285
|
+
begin
|
1286
|
+
srv = select_next_server
|
1247
1287
|
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1288
|
+
# Establish TCP connection with new server
|
1289
|
+
@io = create_socket
|
1290
|
+
@io.connect
|
1291
|
+
@stats[:reconnects] += 1
|
1252
1292
|
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1293
|
+
# Set hostname to use for TLS hostname verification
|
1294
|
+
if client_using_secure_connection? and single_url_connect_used?
|
1295
|
+
# Reuse original hostname name in case of using TLS.
|
1296
|
+
@hostname ||= srv[:hostname]
|
1297
|
+
else
|
1298
|
+
@hostname = srv[:hostname]
|
1299
|
+
end
|
1260
1300
|
|
1261
|
-
|
1262
|
-
|
1301
|
+
# Established TCP connection successfully so can start connect
|
1302
|
+
process_connect_init
|
1263
1303
|
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1304
|
+
# Reset reconnection attempts if connection is valid
|
1305
|
+
srv[:reconnect_attempts] = 0
|
1306
|
+
srv[:auth_required] ||= true if @server_info[:auth_required]
|
1267
1307
|
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1308
|
+
# Add back to rotation since successfully connected
|
1309
|
+
server_pool << srv
|
1310
|
+
rescue NATS::IO::NoServersError => e
|
1311
|
+
raise e
|
1312
|
+
rescue => e
|
1313
|
+
# In case there was an error from the server check
|
1314
|
+
# to see whether need to take it out from rotation
|
1315
|
+
srv[:auth_required] ||= true if @server_info[:auth_required]
|
1316
|
+
server_pool << srv if can_reuse_server?(srv)
|
1277
1317
|
|
1278
|
-
|
1318
|
+
@last_err = e
|
1279
1319
|
|
1280
|
-
|
1281
|
-
|
1320
|
+
# Trigger async error handler
|
1321
|
+
err_cb_call(self, e, nil) if @err_cb
|
1282
1322
|
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1323
|
+
# Continue retrying until there are no options left in the server pool
|
1324
|
+
retry
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
# Clear pending flush calls and reset state before restarting loops
|
1328
|
+
@flush_queue.clear
|
1329
|
+
@pings_outstanding = 0
|
1330
|
+
@pongs_received = 0
|
1331
|
+
|
1332
|
+
# Replay all subscriptions
|
1333
|
+
@subs.each_pair do |sid, sub|
|
1334
|
+
@io.write("SUB #{sub.subject} #{sub.queue} #{sid}#{CR_LF}")
|
1335
|
+
end
|
1286
1336
|
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1337
|
+
# Flush anything which was left pending, in case of errors during flush
|
1338
|
+
# then we should raise error then retry the reconnect logic
|
1339
|
+
cmds = []
|
1340
|
+
cmds << @pending_queue.pop until @pending_queue.empty?
|
1341
|
+
@io.write(cmds.join) unless cmds.empty?
|
1342
|
+
@status = CONNECTED
|
1343
|
+
@pending_size = 0
|
1344
|
+
|
1345
|
+
# Reset parser state here to avoid unknown protocol errors
|
1346
|
+
# on reconnect...
|
1347
|
+
@parser.reset!
|
1348
|
+
|
1349
|
+
# Now connected to NATS, and we can restart parser loop, flusher
|
1350
|
+
# and ping interval
|
1351
|
+
start_threads!
|
1352
|
+
|
1353
|
+
# Dispatch the reconnected callback while holding lock
|
1354
|
+
# which we should have already
|
1355
|
+
@reconnect_cb.call if @reconnect_cb
|
1356
|
+
end
|
1291
1357
|
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1358
|
+
def close_connection(conn_status, do_cbs=true)
|
1359
|
+
synchronize do
|
1360
|
+
if @status == CLOSED
|
1361
|
+
@status = conn_status
|
1362
|
+
return
|
1295
1363
|
end
|
1364
|
+
end
|
1296
1365
|
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
cmds << @pending_queue.pop until @pending_queue.empty?
|
1301
|
-
@io.write(cmds.join) unless cmds.empty?
|
1302
|
-
@status = CONNECTED
|
1303
|
-
@pending_size = 0
|
1366
|
+
# Kick the flusher so it bails due to closed state
|
1367
|
+
@flush_queue << :fallout if @flush_queue
|
1368
|
+
Thread.pass
|
1304
1369
|
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1370
|
+
# FIXME: More graceful way of handling the following?
|
1371
|
+
# Ensure ping interval and flusher are not running anymore
|
1372
|
+
if @ping_interval_thread and @ping_interval_thread.alive?
|
1373
|
+
@ping_interval_thread.exit
|
1374
|
+
end
|
1308
1375
|
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1376
|
+
if @flusher_thread and @flusher_thread.alive?
|
1377
|
+
@flusher_thread.exit
|
1378
|
+
end
|
1312
1379
|
|
1313
|
-
|
1314
|
-
|
1315
|
-
@reconnect_cb.call if @reconnect_cb
|
1380
|
+
if @read_loop_thread and @read_loop_thread.alive?
|
1381
|
+
@read_loop_thread.exit
|
1316
1382
|
end
|
1317
1383
|
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1384
|
+
# TODO: Delete any other state which we are not using here too.
|
1385
|
+
synchronize do
|
1386
|
+
@pongs.synchronize do
|
1387
|
+
@pongs.each do |pong|
|
1388
|
+
pong.signal
|
1323
1389
|
end
|
1390
|
+
@pongs.clear
|
1324
1391
|
end
|
1325
1392
|
|
1326
|
-
#
|
1327
|
-
|
1328
|
-
|
1393
|
+
# Try to write any pending flushes in case
|
1394
|
+
# we have a connection then close it.
|
1395
|
+
should_flush = (@pending_queue && @io && @io.socket && !@io.closed?)
|
1396
|
+
begin
|
1397
|
+
cmds = []
|
1398
|
+
cmds << @pending_queue.pop until @pending_queue.empty?
|
1329
1399
|
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
@
|
1334
|
-
|
1400
|
+
# FIXME: Fails when empty on TLS connection?
|
1401
|
+
@io.write(cmds.join) unless cmds.empty?
|
1402
|
+
rescue => e
|
1403
|
+
@last_err = e
|
1404
|
+
err_cb_call(self, e, nil) if @err_cb
|
1405
|
+
end if should_flush
|
1335
1406
|
|
1336
|
-
|
1337
|
-
|
1407
|
+
# Destroy any remaining subscriptions.
|
1408
|
+
@subs.each do |_, sub|
|
1409
|
+
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1410
|
+
sub.wait_for_msgs_t.exit
|
1411
|
+
sub.pending_queue.clear
|
1412
|
+
end
|
1338
1413
|
end
|
1414
|
+
@subs.clear
|
1339
1415
|
|
1340
|
-
if
|
1341
|
-
@
|
1416
|
+
if do_cbs
|
1417
|
+
@disconnect_cb.call(@last_err) if @disconnect_cb
|
1418
|
+
@close_cb.call if @close_cb
|
1342
1419
|
end
|
1343
1420
|
|
1344
|
-
|
1345
|
-
synchronize do
|
1346
|
-
@pongs.synchronize do
|
1347
|
-
@pongs.each do |pong|
|
1348
|
-
pong.signal
|
1349
|
-
end
|
1350
|
-
@pongs.clear
|
1351
|
-
end
|
1352
|
-
|
1353
|
-
# Try to write any pending flushes in case
|
1354
|
-
# we have a connection then close it.
|
1355
|
-
should_flush = (@pending_queue && @io && @io.socket && !@io.closed?)
|
1356
|
-
begin
|
1357
|
-
cmds = []
|
1358
|
-
cmds << @pending_queue.pop until @pending_queue.empty?
|
1359
|
-
|
1360
|
-
# FIXME: Fails when empty on TLS connection?
|
1361
|
-
@io.write(cmds.join) unless cmds.empty?
|
1362
|
-
rescue => e
|
1363
|
-
@last_err = e
|
1364
|
-
@err_cb.call(e) if @err_cb
|
1365
|
-
end if should_flush
|
1366
|
-
|
1367
|
-
# Destroy any remaining subscriptions.
|
1368
|
-
@subs.each do |_, sub|
|
1369
|
-
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1370
|
-
sub.wait_for_msgs_t.exit
|
1371
|
-
sub.pending_queue.clear
|
1372
|
-
end
|
1373
|
-
end
|
1374
|
-
@subs.clear
|
1375
|
-
|
1376
|
-
if do_cbs
|
1377
|
-
@disconnect_cb.call(@last_err) if @disconnect_cb
|
1378
|
-
@close_cb.call if @close_cb
|
1379
|
-
end
|
1421
|
+
@status = conn_status
|
1380
1422
|
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
@io.close if @io.socket
|
1387
|
-
@io = nil
|
1388
|
-
end
|
1423
|
+
# Close the established connection in case
|
1424
|
+
# we still have it.
|
1425
|
+
if @io
|
1426
|
+
@io.close if @io.socket
|
1427
|
+
@io = nil
|
1389
1428
|
end
|
1390
1429
|
end
|
1430
|
+
end
|
1391
1431
|
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
# Flusher loop for sending commands
|
1398
|
-
@flusher_thread = Thread.new { flusher_loop }
|
1399
|
-
@flusher_thread.abort_on_exception = true
|
1400
|
-
|
1401
|
-
# Ping interval handling for keeping alive the connection
|
1402
|
-
@ping_interval_thread = Thread.new { ping_interval_loop }
|
1403
|
-
@ping_interval_thread.abort_on_exception = true
|
1404
|
-
end
|
1432
|
+
def start_threads!
|
1433
|
+
# Reading loop for gathering data
|
1434
|
+
@read_loop_thread = Thread.new { read_loop }
|
1435
|
+
@read_loop_thread.abort_on_exception = true
|
1405
1436
|
|
1406
|
-
#
|
1407
|
-
|
1408
|
-
|
1409
|
-
@resp_sub_prefix = "_INBOX.#{@nuid.next}"
|
1410
|
-
@resp_map = Hash.new { |h,k| h[k] = { }}
|
1437
|
+
# Flusher loop for sending commands
|
1438
|
+
@flusher_thread = Thread.new { flusher_loop }
|
1439
|
+
@flusher_thread.abort_on_exception = true
|
1411
1440
|
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1441
|
+
# Ping interval handling for keeping alive the connection
|
1442
|
+
@ping_interval_thread = Thread.new { ping_interval_loop }
|
1443
|
+
@ping_interval_thread.abort_on_exception = true
|
1444
|
+
end
|
1415
1445
|
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1446
|
+
# Prepares requests subscription that handles the responses
|
1447
|
+
# for the new style request response.
|
1448
|
+
def start_resp_mux_sub!
|
1449
|
+
@resp_sub_prefix = "_INBOX.#{@nuid.next}"
|
1450
|
+
@resp_map = Hash.new { |h,k| h[k] = { }}
|
1451
|
+
|
1452
|
+
@resp_sub = Subscription.new
|
1453
|
+
@resp_sub.subject = "#{@resp_sub_prefix}.*"
|
1454
|
+
@resp_sub.received = 0
|
1455
|
+
@resp_sub.nc = self
|
1456
|
+
|
1457
|
+
# FIXME: Allow setting pending limits for responses mux subscription.
|
1458
|
+
@resp_sub.pending_msgs_limit = NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT
|
1459
|
+
@resp_sub.pending_bytes_limit = NATS::IO::DEFAULT_SUB_PENDING_BYTES_LIMIT
|
1460
|
+
@resp_sub.pending_queue = SizedQueue.new(@resp_sub.pending_msgs_limit)
|
1461
|
+
@resp_sub.wait_for_msgs_t = Thread.new do
|
1462
|
+
loop do
|
1463
|
+
msg = @resp_sub.pending_queue.pop
|
1464
|
+
@resp_sub.pending_size -= msg.data.size
|
1424
1465
|
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1466
|
+
# Pick the token and signal the request under the mutex
|
1467
|
+
# from the subscription itself.
|
1468
|
+
token = msg.subject.split('.').last
|
1469
|
+
future = nil
|
1470
|
+
synchronize do
|
1471
|
+
future = @resp_map[token][:future]
|
1472
|
+
@resp_map[token][:response] = msg
|
1473
|
+
end
|
1433
1474
|
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1475
|
+
# Signal back that the response has arrived
|
1476
|
+
# in case the future has not been yet delete.
|
1477
|
+
@resp_sub.synchronize do
|
1478
|
+
future.signal if future
|
1438
1479
|
end
|
1439
1480
|
end
|
1440
|
-
|
1441
|
-
sid = (@ssid += 1)
|
1442
|
-
@subs[sid] = @resp_sub
|
1443
|
-
send_command("SUB #{@resp_sub.subject} #{sid}#{CR_LF}")
|
1444
|
-
@flush_queue << :sub
|
1445
1481
|
end
|
1446
1482
|
|
1447
|
-
|
1448
|
-
|
1483
|
+
sid = (@ssid += 1)
|
1484
|
+
@subs[sid] = @resp_sub
|
1485
|
+
send_command("SUB #{@resp_sub.subject} #{sid}#{CR_LF}")
|
1486
|
+
@flush_queue << :sub
|
1487
|
+
end
|
1449
1488
|
|
1450
|
-
|
1451
|
-
|
1489
|
+
def can_reuse_server?(server)
|
1490
|
+
return false if server.nil?
|
1452
1491
|
|
1453
|
-
|
1454
|
-
|
1455
|
-
return false if server[:error_received]
|
1492
|
+
# We can always reuse servers with infinite reconnects settings
|
1493
|
+
return true if @options[:max_reconnect_attempts] < 0
|
1456
1494
|
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1495
|
+
# In case of hard errors like authorization errors, drop the server
|
1496
|
+
# already since won't be able to connect.
|
1497
|
+
return false if server[:error_received]
|
1460
1498
|
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1499
|
+
# We will retry a number of times to reconnect to a server.
|
1500
|
+
return server[:reconnect_attempts] <= @options[:max_reconnect_attempts]
|
1501
|
+
end
|
1464
1502
|
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1503
|
+
def should_delay_connect?(server)
|
1504
|
+
server[:was_connected] && server[:reconnect_attempts] >= 0
|
1505
|
+
end
|
1468
1506
|
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1507
|
+
def should_not_reconnect?
|
1508
|
+
!@options[:reconnect]
|
1509
|
+
end
|
1472
1510
|
|
1473
|
-
|
1474
|
-
|
1511
|
+
def should_reconnect?
|
1512
|
+
@options[:reconnect]
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
def create_socket
|
1516
|
+
NATS::IO::Socket.new({
|
1475
1517
|
uri: @uri,
|
1476
|
-
connect_timeout: DEFAULT_CONNECT_TIMEOUT
|
1518
|
+
connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
1477
1519
|
})
|
1478
|
-
|
1520
|
+
end
|
1479
1521
|
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1522
|
+
def setup_nkeys_connect
|
1523
|
+
begin
|
1524
|
+
require 'nkeys'
|
1525
|
+
require 'base64'
|
1526
|
+
rescue LoadError
|
1527
|
+
raise(Error, "nkeys is not installed")
|
1528
|
+
end
|
1487
1529
|
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1530
|
+
case
|
1531
|
+
when @nkeys_seed
|
1532
|
+
@user_nkey_cb = proc {
|
1533
|
+
seed = File.read(@nkeys_seed).chomp
|
1534
|
+
kp = NKEYS::from_seed(seed)
|
1493
1535
|
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1536
|
+
# Take a copy since original will be gone with the wipe.
|
1537
|
+
pub_key = kp.public_key.dup
|
1538
|
+
kp.wipe!
|
1497
1539
|
|
1498
|
-
|
1499
|
-
|
1540
|
+
pub_key
|
1541
|
+
}
|
1500
1542
|
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
end
|
1543
|
+
@signature_cb = proc { |nonce|
|
1544
|
+
seed = File.read(@nkeys_seed).chomp
|
1545
|
+
kp = NKEYS::from_seed(seed)
|
1546
|
+
raw_signed = kp.sign(nonce)
|
1547
|
+
kp.wipe!
|
1548
|
+
encoded = Base64.urlsafe_encode64(raw_signed)
|
1549
|
+
encoded.gsub('=', '')
|
1550
|
+
}
|
1551
|
+
when @user_credentials
|
1552
|
+
# When the credentials are within a single decorated file.
|
1553
|
+
@user_jwt_cb = proc {
|
1554
|
+
jwt_start = "BEGIN NATS USER JWT".freeze
|
1555
|
+
found = false
|
1556
|
+
jwt = nil
|
1557
|
+
File.readlines(@user_credentials).each do |line|
|
1558
|
+
case
|
1559
|
+
when found
|
1560
|
+
jwt = line.chomp
|
1561
|
+
break
|
1562
|
+
when line.include?(jwt_start)
|
1563
|
+
found = true
|
1523
1564
|
end
|
1524
|
-
|
1565
|
+
end
|
1566
|
+
raise(Error, "No JWT found in #{@user_credentials}") if not found
|
1525
1567
|
|
1526
|
-
|
1527
|
-
|
1568
|
+
jwt
|
1569
|
+
}
|
1528
1570
|
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
end
|
1571
|
+
@signature_cb = proc { |nonce|
|
1572
|
+
seed_start = "BEGIN USER NKEY SEED".freeze
|
1573
|
+
found = false
|
1574
|
+
seed = nil
|
1575
|
+
File.readlines(@user_credentials).each do |line|
|
1576
|
+
case
|
1577
|
+
when found
|
1578
|
+
seed = line.chomp
|
1579
|
+
break
|
1580
|
+
when line.include?(seed_start)
|
1581
|
+
found = true
|
1541
1582
|
end
|
1542
|
-
|
1583
|
+
end
|
1584
|
+
raise(Error, "No nkey user seed found in #{@user_credentials}") if not found
|
1543
1585
|
|
1544
|
-
|
1545
|
-
|
1586
|
+
kp = NKEYS::from_seed(seed)
|
1587
|
+
raw_signed = kp.sign(nonce)
|
1546
1588
|
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1589
|
+
# seed is a reference so also cleared when doing wipe,
|
1590
|
+
# which can be done since Ruby strings are mutable.
|
1591
|
+
kp.wipe
|
1592
|
+
encoded = Base64.urlsafe_encode64(raw_signed)
|
1551
1593
|
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
end
|
1594
|
+
# Remove padding
|
1595
|
+
encoded.gsub('=', '')
|
1596
|
+
}
|
1556
1597
|
end
|
1598
|
+
end
|
1557
1599
|
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1600
|
+
def process_uri(uris)
|
1601
|
+
connect_uris = []
|
1602
|
+
uris.split(',').each do |uri|
|
1603
|
+
opts = {}
|
1562
1604
|
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1605
|
+
# Scheme
|
1606
|
+
if uri.include?("://")
|
1607
|
+
scheme, uri = uri.split("://")
|
1608
|
+
opts[:scheme] = scheme
|
1609
|
+
else
|
1610
|
+
opts[:scheme] = 'nats'
|
1611
|
+
end
|
1570
1612
|
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1613
|
+
# UserInfo
|
1614
|
+
if uri.include?("@")
|
1615
|
+
userinfo, endpoint = uri.split("@")
|
1616
|
+
host, port = endpoint.split(":")
|
1617
|
+
opts[:userinfo] = userinfo
|
1618
|
+
else
|
1619
|
+
host, port = uri.split(":")
|
1620
|
+
end
|
1579
1621
|
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1622
|
+
# Host and Port
|
1623
|
+
opts[:host] = host || "localhost"
|
1624
|
+
opts[:port] = port || DEFAULT_PORT
|
1583
1625
|
|
1584
|
-
|
1585
|
-
end
|
1586
|
-
connect_uris
|
1626
|
+
connect_uris << URI::Generic.build(opts)
|
1587
1627
|
end
|
1628
|
+
connect_uris
|
1588
1629
|
end
|
1630
|
+
end
|
1631
|
+
|
1632
|
+
module IO
|
1633
|
+
include Status
|
1634
|
+
|
1635
|
+
# Client creates a connection to the NATS Server.
|
1636
|
+
Client = ::NATS::Client
|
1637
|
+
|
1638
|
+
MAX_RECONNECT_ATTEMPTS = 10
|
1639
|
+
RECONNECT_TIME_WAIT = 2
|
1640
|
+
|
1641
|
+
# Maximum accumulated pending commands bytesize before forcing a flush.
|
1642
|
+
MAX_PENDING_SIZE = 32768
|
1643
|
+
|
1644
|
+
# Maximum number of flush kicks that can be queued up before we block.
|
1645
|
+
MAX_FLUSH_KICK_SIZE = 1024
|
1646
|
+
|
1647
|
+
# Maximum number of bytes which we will be gathering on a single read.
|
1648
|
+
# TODO: Make dynamic?
|
1649
|
+
MAX_SOCKET_READ_BYTES = 32768
|
1650
|
+
|
1651
|
+
# Ping intervals
|
1652
|
+
DEFAULT_PING_INTERVAL = 120
|
1653
|
+
DEFAULT_PING_MAX = 2
|
1654
|
+
|
1655
|
+
# Default IO timeouts
|
1656
|
+
DEFAULT_CONNECT_TIMEOUT = 2
|
1657
|
+
DEFAULT_READ_WRITE_TIMEOUT = 2
|
1658
|
+
|
1659
|
+
# Default Pending Limits
|
1660
|
+
DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536
|
1661
|
+
DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024
|
1589
1662
|
|
1590
1663
|
# Implementation adapted from https://github.com/redis/redis-rb
|
1591
1664
|
class Socket
|
@@ -1618,7 +1691,7 @@ module NATS
|
|
1618
1691
|
def read_line(deadline=nil)
|
1619
1692
|
# FIXME: Should accumulate and read in a non blocking way instead
|
1620
1693
|
unless ::IO.select([@socket], nil, nil, deadline)
|
1621
|
-
raise SocketTimeoutError
|
1694
|
+
raise NATS::IO::SocketTimeoutError
|
1622
1695
|
end
|
1623
1696
|
@socket.gets
|
1624
1697
|
end
|
@@ -1631,13 +1704,13 @@ module NATS
|
|
1631
1704
|
if ::IO.select([@socket], nil, nil, deadline)
|
1632
1705
|
retry
|
1633
1706
|
else
|
1634
|
-
raise SocketTimeoutError
|
1707
|
+
raise NATS::IO::SocketTimeoutError
|
1635
1708
|
end
|
1636
1709
|
rescue ::IO::WaitWritable
|
1637
1710
|
if ::IO.select(nil, [@socket], nil, deadline)
|
1638
1711
|
retry
|
1639
1712
|
else
|
1640
|
-
raise SocketTimeoutError
|
1713
|
+
raise NATS::IO::SocketTimeoutError
|
1641
1714
|
end
|
1642
1715
|
end
|
1643
1716
|
rescue EOFError => e
|
@@ -1665,13 +1738,13 @@ module NATS
|
|
1665
1738
|
if ::IO.select(nil, [@socket], nil, deadline)
|
1666
1739
|
retry
|
1667
1740
|
else
|
1668
|
-
raise SocketTimeoutError
|
1741
|
+
raise NATS::IO::SocketTimeoutError
|
1669
1742
|
end
|
1670
1743
|
rescue ::IO::WaitReadable
|
1671
1744
|
if ::IO.select([@socket], nil, nil, deadline)
|
1672
1745
|
retry
|
1673
1746
|
else
|
1674
|
-
raise SocketTimeoutError
|
1747
|
+
raise NATS::IO::SocketTimeoutError
|
1675
1748
|
end
|
1676
1749
|
end
|
1677
1750
|
end
|
@@ -1698,7 +1771,7 @@ module NATS
|
|
1698
1771
|
sock.connect_nonblock(sockaddr)
|
1699
1772
|
rescue Errno::EINPROGRESS, Errno::EALREADY, ::IO::WaitWritable
|
1700
1773
|
unless ::IO.select(nil, [sock], nil, @connect_timeout)
|
1701
|
-
raise SocketTimeoutError
|
1774
|
+
raise NATS::IO::SocketTimeoutError
|
1702
1775
|
end
|
1703
1776
|
|
1704
1777
|
# Confirm that connection was established
|
@@ -1714,39 +1787,9 @@ module NATS
|
|
1714
1787
|
end
|
1715
1788
|
end
|
1716
1789
|
|
1717
|
-
Msg = Struct.new(:subject, :reply, :data, :header, keyword_init: true)
|
1718
|
-
|
1719
|
-
class Subscription
|
1720
|
-
include MonitorMixin
|
1721
|
-
|
1722
|
-
attr_accessor :subject, :queue, :future, :callback, :response, :received, :max, :pending
|
1723
|
-
attr_accessor :pending_queue, :pending_size, :wait_for_msgs_t, :is_slow_consumer
|
1724
|
-
attr_accessor :pending_msgs_limit, :pending_bytes_limit
|
1725
|
-
|
1726
|
-
def initialize
|
1727
|
-
super # required to initialize monitor
|
1728
|
-
@subject = ''
|
1729
|
-
@queue = nil
|
1730
|
-
@future = nil
|
1731
|
-
@callback = nil
|
1732
|
-
@response = nil
|
1733
|
-
@received = 0
|
1734
|
-
@max = nil
|
1735
|
-
@pending = nil
|
1736
|
-
|
1737
|
-
# State from async subscriber messages delivery
|
1738
|
-
@pending_queue = nil
|
1739
|
-
@pending_size = 0
|
1740
|
-
@pending_msgs_limit = nil
|
1741
|
-
@pending_bytes_limit = nil
|
1742
|
-
@wait_for_msgs_t = nil
|
1743
|
-
@is_slow_consumer = false
|
1744
|
-
end
|
1745
|
-
end
|
1746
|
-
|
1747
|
-
# Implementation of MonotonicTime adapted from
|
1748
|
-
# https://github.com/ruby-concurrency/concurrent-ruby/
|
1749
1790
|
class MonotonicTime
|
1791
|
+
# Implementation of MonotonicTime adapted from
|
1792
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/
|
1750
1793
|
class << self
|
1751
1794
|
case
|
1752
1795
|
when defined?(Process::CLOCK_MONOTONIC)
|
@@ -1763,6 +1806,20 @@ module NATS
|
|
1763
1806
|
::Time.now.to_f
|
1764
1807
|
end
|
1765
1808
|
end
|
1809
|
+
|
1810
|
+
def with_nats_timeout(timeout)
|
1811
|
+
start_time = now
|
1812
|
+
yield
|
1813
|
+
end_time = now
|
1814
|
+
duration = end_time - start_time
|
1815
|
+
if duration > timeout
|
1816
|
+
raise NATS::Timeout.new("nats: timeout")
|
1817
|
+
end
|
1818
|
+
end
|
1819
|
+
|
1820
|
+
def since(t0)
|
1821
|
+
now - t0
|
1822
|
+
end
|
1766
1823
|
end
|
1767
1824
|
end
|
1768
1825
|
end
|