nats-pure 2.3.0 → 2.5.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 +3 -0
- data/README.md +10 -3
- data/lib/nats/client.rb +7 -2
- data/lib/nats/io/client.rb +304 -282
- data/lib/nats/io/errors.rb +2 -0
- data/lib/nats/io/jetstream/api.rb +54 -47
- data/lib/nats/io/jetstream/errors.rb +30 -14
- data/lib/nats/io/jetstream/js/config.rb +9 -3
- data/lib/nats/io/jetstream/js/header.rb +15 -9
- data/lib/nats/io/jetstream/js/status.rb +11 -5
- data/lib/nats/io/jetstream/js/sub.rb +4 -2
- data/lib/nats/io/jetstream/js.rb +10 -8
- data/lib/nats/io/jetstream/manager.rb +104 -83
- data/lib/nats/io/jetstream/msg/ack.rb +15 -9
- data/lib/nats/io/jetstream/msg/ack_methods.rb +24 -22
- data/lib/nats/io/jetstream/msg/metadata.rb +9 -7
- data/lib/nats/io/jetstream/msg.rb +11 -4
- data/lib/nats/io/jetstream/pull_subscription.rb +21 -10
- data/lib/nats/io/jetstream/push_subscription.rb +3 -1
- data/lib/nats/io/jetstream.rb +125 -54
- data/lib/nats/io/kv/api.rb +7 -3
- data/lib/nats/io/kv/bucket_status.rb +7 -5
- data/lib/nats/io/kv/errors.rb +25 -2
- data/lib/nats/io/kv/manager.rb +19 -10
- data/lib/nats/io/kv.rb +359 -22
- data/lib/nats/io/msg.rb +19 -19
- data/lib/nats/io/parser.rb +23 -23
- data/lib/nats/io/rails.rb +2 -0
- data/lib/nats/io/subscription.rb +25 -22
- data/lib/nats/io/version.rb +4 -2
- data/lib/nats/io/websocket.rb +10 -8
- data/lib/nats/nuid.rb +33 -22
- data/lib/nats/service/callbacks.rb +22 -0
- data/lib/nats/service/endpoint.rb +155 -0
- data/lib/nats/service/errors.rb +44 -0
- data/lib/nats/service/group.rb +37 -0
- data/lib/nats/service/monitoring.rb +108 -0
- data/lib/nats/service/stats.rb +52 -0
- data/lib/nats/service/status.rb +66 -0
- data/lib/nats/service/validator.rb +31 -0
- data/lib/nats/service.rb +121 -0
- data/lib/nats/utils/list.rb +26 -0
- data/lib/nats-pure.rb +5 -0
- data/lib/nats.rb +10 -6
- metadata +176 -5
data/lib/nats/io/subscription.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright 2016-2021 The NATS Authors
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
5
|
# you may not use this file except in compliance with the License.
|
@@ -13,45 +15,46 @@
|
|
13
15
|
#
|
14
16
|
|
15
17
|
module NATS
|
16
|
-
|
17
18
|
# A Subscription represents interest in a given subject.
|
18
|
-
#
|
19
|
+
#
|
19
20
|
# @example Create NATS subscription with callback.
|
20
21
|
# require 'nats/client'
|
21
|
-
#
|
22
|
+
#
|
22
23
|
# nc = NATS.connect("demo.nats.io")
|
23
24
|
# sub = nc.subscribe("foo") do |msg|
|
24
25
|
# puts "Received [#{msg.subject}]: #{}"
|
25
26
|
# end
|
26
|
-
#
|
27
|
+
#
|
27
28
|
class Subscription
|
28
29
|
include MonitorMixin
|
29
30
|
|
30
31
|
attr_accessor :subject, :queue, :future, :callback, :response, :received, :max, :pending, :sid
|
31
|
-
attr_accessor :pending_queue, :pending_size, :wait_for_msgs_cond
|
32
|
+
attr_accessor :pending_queue, :pending_size, :wait_for_msgs_cond
|
32
33
|
attr_accessor :pending_msgs_limit, :pending_bytes_limit
|
33
34
|
attr_accessor :nc
|
34
35
|
attr_accessor :jsi
|
35
|
-
attr_accessor :closed
|
36
|
+
attr_accessor :closed, :drained
|
37
|
+
alias_method :delivered, :received
|
36
38
|
|
37
39
|
def initialize(**opts)
|
38
40
|
super() # required to initialize monitor
|
39
|
-
@subject
|
40
|
-
@queue
|
41
|
-
@future
|
41
|
+
@subject = ""
|
42
|
+
@queue = nil
|
43
|
+
@future = nil
|
42
44
|
@callback = nil
|
43
45
|
@response = nil
|
44
46
|
@received = 0
|
45
|
-
@max
|
46
|
-
@pending
|
47
|
-
@sid
|
48
|
-
@nc
|
49
|
-
@closed
|
47
|
+
@max = nil
|
48
|
+
@pending = nil
|
49
|
+
@sid = nil
|
50
|
+
@nc = nil
|
51
|
+
@closed = nil
|
52
|
+
@drained = false
|
50
53
|
|
51
54
|
# State from async subscriber messages delivery
|
52
|
-
@pending_queue
|
53
|
-
@pending_size
|
54
|
-
@pending_msgs_limit
|
55
|
+
@pending_queue = nil
|
56
|
+
@pending_size = 0
|
57
|
+
@pending_msgs_limit = nil
|
55
58
|
@pending_bytes_limit = nil
|
56
59
|
|
57
60
|
# Sync subscriber
|
@@ -78,22 +81,22 @@ module NATS
|
|
78
81
|
|
79
82
|
# Auto unsubscribes the server by sending UNSUB command and throws away
|
80
83
|
# subscription in case already present and has received enough messages.
|
81
|
-
def unsubscribe(opt_max=nil)
|
84
|
+
def unsubscribe(opt_max = nil)
|
82
85
|
@nc.send(:unsubscribe, self, opt_max)
|
83
86
|
end
|
84
87
|
|
85
88
|
# next_msg blocks and waiting for the next message to be received.
|
86
|
-
def next_msg(opts={})
|
89
|
+
def next_msg(opts = {})
|
87
90
|
timeout = opts[:timeout] ||= 0.5
|
88
91
|
synchronize do
|
89
|
-
return @pending_queue.pop if
|
92
|
+
return @pending_queue.pop if !@pending_queue.empty?
|
90
93
|
|
91
94
|
# Wait for a bit until getting a signal.
|
92
|
-
MonotonicTime
|
95
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
93
96
|
wait_for_msgs_cond.wait(timeout)
|
94
97
|
end
|
95
98
|
|
96
|
-
if
|
99
|
+
if !@pending_queue.empty?
|
97
100
|
return @pending_queue.pop
|
98
101
|
else
|
99
102
|
raise NATS::Timeout
|
data/lib/nats/io/version.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2016-2025 The NATS Authors
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
5
|
# you may not use this file except in compliance with the License.
|
4
6
|
# You may obtain a copy of the License at
|
@@ -15,7 +17,7 @@
|
|
15
17
|
module NATS
|
16
18
|
module IO
|
17
19
|
# VERSION is the version of the client announced on CONNECT to the server.
|
18
|
-
VERSION = "2.
|
20
|
+
VERSION = "2.5.0"
|
19
21
|
|
20
22
|
# LANG is the lang runtime of the client announced on CONNECT to the server.
|
21
23
|
LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
|
data/lib/nats/io/websocket.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
|
-
require
|
4
|
+
require "websocket"
|
3
5
|
rescue LoadError
|
4
6
|
raise LoadError, "Please add `websocket` gem to your Gemfile to connect to NATS via WebSocket."
|
5
7
|
end
|
@@ -15,7 +17,7 @@ module NATS
|
|
15
17
|
|
16
18
|
attr_accessor :socket
|
17
19
|
|
18
|
-
def initialize(options={})
|
20
|
+
def initialize(options = {})
|
19
21
|
super
|
20
22
|
end
|
21
23
|
|
@@ -44,31 +46,31 @@ module NATS
|
|
44
46
|
super
|
45
47
|
end
|
46
48
|
|
47
|
-
def read(max_bytes=MAX_SOCKET_READ_BYTES, deadline=nil)
|
49
|
+
def read(max_bytes = MAX_SOCKET_READ_BYTES, deadline = nil)
|
48
50
|
data = super
|
49
51
|
@frame << data
|
50
52
|
result = []
|
51
|
-
while msg = @frame.next
|
53
|
+
while (msg = @frame.next)
|
52
54
|
result << msg
|
53
55
|
end
|
54
56
|
result.join
|
55
57
|
end
|
56
58
|
|
57
|
-
def read_line(deadline=nil)
|
59
|
+
def read_line(deadline = nil)
|
58
60
|
data = super
|
59
61
|
@frame << data
|
60
62
|
result = []
|
61
|
-
while msg = @frame.next
|
63
|
+
while (msg = @frame.next)
|
62
64
|
result << msg
|
63
65
|
end
|
64
66
|
result.join
|
65
67
|
end
|
66
68
|
|
67
|
-
def write(data, deadline=nil)
|
69
|
+
def write(data, deadline = nil)
|
68
70
|
raise HandshakeError, "Attempted to write to socket while WebSocket handshake is in progress" unless @handshaked
|
69
71
|
|
70
72
|
frame = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: :binary, version: @handshake.version)
|
71
|
-
super
|
73
|
+
super(frame.to_s)
|
72
74
|
end
|
73
75
|
end
|
74
76
|
end
|
data/lib/nats/nuid.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright 2016-2018 The NATS Authors
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
5
|
# you may not use this file except in compliance with the License.
|
@@ -11,27 +13,27 @@
|
|
11
13
|
# See the License for the specific language governing permissions and
|
12
14
|
# limitations under the License.
|
13
15
|
#
|
14
|
-
require
|
16
|
+
require "securerandom"
|
15
17
|
|
16
18
|
module NATS
|
17
19
|
class NUID
|
18
|
-
DIGITS
|
19
|
-
BASE
|
20
|
+
DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".chars
|
21
|
+
BASE = 62
|
20
22
|
PREFIX_LENGTH = 12
|
21
|
-
SEQ_LENGTH
|
22
|
-
TOTAL_LENGTH
|
23
|
-
MAX_SEQ
|
24
|
-
MIN_INC
|
25
|
-
MAX_INC
|
23
|
+
SEQ_LENGTH = 10
|
24
|
+
TOTAL_LENGTH = PREFIX_LENGTH + SEQ_LENGTH
|
25
|
+
MAX_SEQ = BASE**10
|
26
|
+
MIN_INC = 33
|
27
|
+
MAX_INC = 333
|
26
28
|
INC = MAX_INC - MIN_INC
|
27
29
|
|
28
30
|
Ractor.make_shareable(DIGITS) if defined?(Ractor)
|
29
31
|
|
30
32
|
def initialize
|
31
|
-
@prand
|
32
|
-
@seq
|
33
|
-
@inc
|
34
|
-
@prefix
|
33
|
+
@prand = Random.new
|
34
|
+
@seq = @prand.rand(MAX_SEQ)
|
35
|
+
@inc = MIN_INC + @prand.rand(INC)
|
36
|
+
@prefix = ""
|
35
37
|
randomize_prefix!
|
36
38
|
end
|
37
39
|
|
@@ -46,22 +48,31 @@ module NATS
|
|
46
48
|
# Do this inline 10 times to avoid even more extra allocs,
|
47
49
|
# then use string interpolation of everything which works
|
48
50
|
# faster for doing concat.
|
49
|
-
s_10 = DIGITS[l % BASE]
|
51
|
+
s_10 = DIGITS[l % BASE]
|
50
52
|
|
51
53
|
# Ugly, but parallel assignment is slightly faster here...
|
52
|
-
s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 =
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 =
|
55
|
+
(l /= BASE
|
56
|
+
DIGITS[l % BASE]), (l /= BASE
|
57
|
+
DIGITS[l % BASE]), (l /= BASE
|
58
|
+
DIGITS[l % BASE]),
|
59
|
+
(l /= BASE
|
60
|
+
DIGITS[l % BASE]), (l /= BASE
|
61
|
+
DIGITS[l % BASE]), (l /= BASE
|
62
|
+
DIGITS[l % BASE]),
|
63
|
+
(l /= BASE
|
64
|
+
DIGITS[l % BASE]), (l /= BASE
|
65
|
+
DIGITS[l % BASE]), (l /= BASE
|
66
|
+
DIGITS[l % BASE])
|
56
67
|
"#{@prefix}#{s_01}#{s_02}#{s_03}#{s_04}#{s_05}#{s_06}#{s_07}#{s_08}#{s_09}#{s_10}"
|
57
68
|
end
|
58
69
|
|
59
70
|
def randomize_prefix!
|
60
|
-
@prefix =
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
71
|
+
@prefix =
|
72
|
+
SecureRandom.random_bytes(PREFIX_LENGTH).each_byte
|
73
|
+
.reduce("".dup) do |prefix, n|
|
74
|
+
prefix << DIGITS[n % BASE]
|
75
|
+
end
|
65
76
|
end
|
66
77
|
|
67
78
|
private
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NATS
|
4
|
+
class Service
|
5
|
+
class Callbacks
|
6
|
+
attr_reader :service, :callbacks
|
7
|
+
|
8
|
+
def initialize(service)
|
9
|
+
@service = service
|
10
|
+
@callbacks = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def register(name, &block)
|
14
|
+
callbacks[name] = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(name, *args)
|
18
|
+
callbacks[name]&.call(*args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2025 The NATS Authors
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module NATS
|
17
|
+
class Service
|
18
|
+
class Request < ::NATS::Msg
|
19
|
+
attr_reader :error, :endpoint
|
20
|
+
|
21
|
+
def initialize(opts = {})
|
22
|
+
super
|
23
|
+
@endpoint = opts[:endpoint]
|
24
|
+
@error = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def respond_with_error(error)
|
28
|
+
@error = NATS::Service::ErrorWrapper.new(error)
|
29
|
+
|
30
|
+
message = dup
|
31
|
+
message.subject = reply
|
32
|
+
message.reply = ""
|
33
|
+
message.data = @error.data
|
34
|
+
|
35
|
+
message.header = {
|
36
|
+
"Nats-Service-Error" => @error.message,
|
37
|
+
"Nats-Service-Error-Code" => @error.code
|
38
|
+
}
|
39
|
+
|
40
|
+
respond_msg(message)
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
dot = "..." if @data.length > 10
|
45
|
+
dat = "#{data.slice(0, 10)}#{dot}"
|
46
|
+
"#<Service::Request(subject: \"#{@subject}\", reply: \"#{@reply}\", data: #{dat.inspect})>"
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def from_msg(svc, msg)
|
51
|
+
request = Request.new(endpoint: svc)
|
52
|
+
request.subject = msg.subject
|
53
|
+
request.reply = msg.reply
|
54
|
+
request.data = msg.data
|
55
|
+
request.header = msg.header
|
56
|
+
request.nc = msg.nc
|
57
|
+
request.sub = msg.sub
|
58
|
+
|
59
|
+
request
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Endpoint
|
65
|
+
attr_reader :name, :service, :subject, :metadata, :queue, :stats
|
66
|
+
|
67
|
+
def initialize(name:, options:, parent:, &block)
|
68
|
+
validate(name, options)
|
69
|
+
|
70
|
+
@name = name
|
71
|
+
|
72
|
+
@service = parent.service
|
73
|
+
@subject = build_subject(parent, options)
|
74
|
+
@queue = options[:queue] || parent.queue
|
75
|
+
@metadata = options[:metadata]
|
76
|
+
|
77
|
+
@stats = NATS::Service::Stats.new
|
78
|
+
@handler = create_handler(block)
|
79
|
+
|
80
|
+
@stopped = false
|
81
|
+
end
|
82
|
+
|
83
|
+
def stop
|
84
|
+
service.client.send(:drain_sub, @handler)
|
85
|
+
rescue
|
86
|
+
# nothing we can do here
|
87
|
+
ensure
|
88
|
+
@stopped = true
|
89
|
+
end
|
90
|
+
|
91
|
+
def reset
|
92
|
+
stats.reset
|
93
|
+
end
|
94
|
+
|
95
|
+
def stopped?
|
96
|
+
@stopped
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def validate(name, options)
|
102
|
+
Validator.validate(
|
103
|
+
name: name,
|
104
|
+
subject: options[:subject],
|
105
|
+
queue: options[:queue]
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_subject(parent, options)
|
110
|
+
subject = options[:subject] || name
|
111
|
+
|
112
|
+
parent.subject ? "#{parent.subject}.#{subject}" : subject
|
113
|
+
end
|
114
|
+
|
115
|
+
def create_handler(block)
|
116
|
+
service.client.subscribe(subject, queue: queue) do |msg|
|
117
|
+
started_at = Time.now
|
118
|
+
|
119
|
+
req = Request.from_msg(self, msg)
|
120
|
+
block.call(req)
|
121
|
+
stats.error(req.error) if req.error
|
122
|
+
rescue NATS::Error => error
|
123
|
+
stats.error(error)
|
124
|
+
service.stop(error)
|
125
|
+
|
126
|
+
raise error
|
127
|
+
rescue => error
|
128
|
+
stats.error(error)
|
129
|
+
Request.from_msg(self, msg).respond_with_error(error)
|
130
|
+
ensure
|
131
|
+
stats.record(started_at)
|
132
|
+
end
|
133
|
+
rescue => error
|
134
|
+
service.stop(error)
|
135
|
+
raise error
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Endpoints < NATS::Utils::List
|
140
|
+
def add(name, options = {}, &block)
|
141
|
+
endpoint = Endpoint.new(
|
142
|
+
name: name,
|
143
|
+
options: options,
|
144
|
+
parent: parent,
|
145
|
+
&block
|
146
|
+
)
|
147
|
+
|
148
|
+
insert(endpoint)
|
149
|
+
parent.service.endpoints.insert(endpoint)
|
150
|
+
|
151
|
+
endpoint
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NATS
|
4
|
+
class Service
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
class InvalidNameError < Error; end
|
8
|
+
|
9
|
+
class InvalidVersionError < Error; end
|
10
|
+
|
11
|
+
class InvalidQueueError < Error; end
|
12
|
+
|
13
|
+
class InvalidSubjectError < Error; end
|
14
|
+
|
15
|
+
class ErrorWrapper
|
16
|
+
attr_reader :code, :message, :data
|
17
|
+
|
18
|
+
def initialize(error)
|
19
|
+
case error
|
20
|
+
when Exception
|
21
|
+
@code = 500
|
22
|
+
@message = error.message
|
23
|
+
@data = ""
|
24
|
+
when Hash
|
25
|
+
@code = error[:code]
|
26
|
+
@message = error[:description]
|
27
|
+
@data = error[:data]
|
28
|
+
when ErrorWrapper
|
29
|
+
@code = error.code
|
30
|
+
@message = error.message
|
31
|
+
@data = error.data
|
32
|
+
else
|
33
|
+
@code = 500
|
34
|
+
@message = error.to_s
|
35
|
+
@data = ""
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def description
|
40
|
+
"#{code}:#{message}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NATS
|
4
|
+
class Service
|
5
|
+
class Group
|
6
|
+
attr_reader :service, :name, :subject, :queue, :groups, :endpoints
|
7
|
+
|
8
|
+
def initialize(name:, parent:, queue:)
|
9
|
+
Validator.validate(name: name, queue: queue)
|
10
|
+
|
11
|
+
@name = name
|
12
|
+
|
13
|
+
@service = parent.service
|
14
|
+
@subject = parent.subject ? "#{parent.subject}.#{name}" : name
|
15
|
+
@queue = queue || parent.queue
|
16
|
+
|
17
|
+
@groups = Groups.new(self)
|
18
|
+
@endpoints = Endpoints.new(self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Groups < NATS::Utils::List
|
23
|
+
def add(name, queue: nil)
|
24
|
+
group = Group.new(
|
25
|
+
name: name,
|
26
|
+
queue: queue,
|
27
|
+
parent: parent
|
28
|
+
)
|
29
|
+
|
30
|
+
insert(group)
|
31
|
+
parent.service.groups.insert(group)
|
32
|
+
|
33
|
+
group
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module NATS
|
6
|
+
class Service
|
7
|
+
class Monitoring
|
8
|
+
DEFAULT_PREFIX = "$SRV"
|
9
|
+
|
10
|
+
VERBS = {
|
11
|
+
ping: "PING",
|
12
|
+
info: "INFO",
|
13
|
+
stats: "STATS"
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
TYPES = {
|
17
|
+
ping: "io.nats.micro.v1.ping_response",
|
18
|
+
info: "io.nats.micro.v1.info_response",
|
19
|
+
stats: "io.nats.micro.v1.stats_response"
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
attr_reader :service, :prefix, :stopped
|
23
|
+
|
24
|
+
def initialize(service, prefix = nil)
|
25
|
+
@service = service
|
26
|
+
@prefix = prefix || DEFAULT_PREFIX
|
27
|
+
|
28
|
+
setup_monitors
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop
|
32
|
+
return if @monitors.nil?
|
33
|
+
|
34
|
+
@monitors.each do |monitor|
|
35
|
+
service.client.send(:drain_sub, monitor)
|
36
|
+
end
|
37
|
+
rescue
|
38
|
+
# nothing we can do here
|
39
|
+
ensure
|
40
|
+
@monitors = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def stopped?
|
44
|
+
@monitors.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def setup_monitors
|
50
|
+
@monitors = []
|
51
|
+
|
52
|
+
ping
|
53
|
+
info
|
54
|
+
stats
|
55
|
+
end
|
56
|
+
|
57
|
+
def ping
|
58
|
+
monitor(:ping) do
|
59
|
+
{
|
60
|
+
type: TYPES[:ping],
|
61
|
+
**service.status.basic
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def info
|
67
|
+
monitor(:info) do
|
68
|
+
{
|
69
|
+
type: TYPES[:info],
|
70
|
+
**service.status.info
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def stats
|
76
|
+
monitor(:stats) do
|
77
|
+
{
|
78
|
+
type: TYPES[:stats],
|
79
|
+
**service.status.stats
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def monitor(verb, &block)
|
85
|
+
subjects(verb).map do |subject|
|
86
|
+
@monitors << subscribe_monitor(subject, block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def subscribe_monitor(subject, block)
|
91
|
+
service.client.subscribe(subject) do |message|
|
92
|
+
message.respond(block.call.to_json)
|
93
|
+
end
|
94
|
+
rescue => error
|
95
|
+
service.stop(error)
|
96
|
+
raise error
|
97
|
+
end
|
98
|
+
|
99
|
+
def subjects(verb)
|
100
|
+
[
|
101
|
+
"#{prefix}.#{VERBS[verb]}",
|
102
|
+
"#{prefix}.#{VERBS[verb]}.#{service.name}",
|
103
|
+
"#{prefix}.#{VERBS[verb]}.#{service.name}.#{service.id}"
|
104
|
+
]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "monitor"
|
4
|
+
|
5
|
+
module NATS
|
6
|
+
class Service
|
7
|
+
class Stats
|
8
|
+
include MonitorMixin
|
9
|
+
|
10
|
+
attr_reader :num_requests, :num_errors, :last_error, :processing_time, :average_processing_time
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
super
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset
|
18
|
+
synchronize do
|
19
|
+
@num_requests = 0
|
20
|
+
@processing_time = 0
|
21
|
+
@average_processing_time = 0
|
22
|
+
|
23
|
+
@num_errors = 0
|
24
|
+
@last_error = ""
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def record(started_at)
|
29
|
+
synchronize do
|
30
|
+
@num_requests += 1
|
31
|
+
@processing_time += to_nsec(Time.now - started_at)
|
32
|
+
@average_processing_time = @processing_time / @num_requests
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def error(error)
|
37
|
+
error = ErrorWrapper.new(error)
|
38
|
+
|
39
|
+
synchronize do
|
40
|
+
@num_errors += 1
|
41
|
+
@last_error = error.description
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def to_nsec(seconds)
|
48
|
+
(seconds * 10**9).to_i
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|