protobuf-nats 0.1.3 → 0.2.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/bench/real_client.rb +0 -4
- data/ext/jars/gson-2.6.2.jar +0 -0
- data/ext/jars/jnats-1.1-SNAPSHOT.jar +0 -0
- data/ext/jars/slf4j-api-1.7.25.jar +0 -0
- data/ext/jars/slf4j-simple-1.7.25.jar +0 -0
- data/lib/protobuf/nats.rb +31 -2
- data/lib/protobuf/nats/client.rb +92 -41
- data/lib/protobuf/nats/config.rb +6 -1
- data/lib/protobuf/nats/jnats.rb +239 -0
- data/lib/protobuf/nats/server.rb +18 -24
- data/lib/protobuf/nats/thread_pool.rb +108 -0
- data/lib/protobuf/nats/version.rb +1 -1
- data/protobuf-nats.gemspec +0 -1
- metadata +8 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eeff2a5c3d939012fe3cd2cb7cce12ba89392a81
|
4
|
+
data.tar.gz: 406a4b1f779d1a3d91ea136bbb3616560e8152a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a517022291c46d42a4c6b243cc50625ca37770e64c3fb387c990b43d06b70078ab697a2b153b1e731f04d622758c4a7a33915267014f0f47440e238f3d1455e
|
7
|
+
data.tar.gz: 2368ebed1aead198a600eebac3cdea821c4004737a9983e549b217025fb7d1d10c4a3bed46523b189b1920bb872af2e462acf61d00b07f07c1f264292bd433cb
|
data/bench/real_client.rb
CHANGED
@@ -4,10 +4,6 @@ ENV["PB_SERVER_TYPE"] = "protobuf/nats/runner"
|
|
4
4
|
require "benchmark/ips"
|
5
5
|
require "./examples/warehouse/app"
|
6
6
|
|
7
|
-
payload = ::Warehouse::Shipment.new(:guid => ::SecureRandom.uuid, :address => "123 yolo st")
|
8
|
-
nats = ::NATS::IO::Client.new
|
9
|
-
nats.connect({:servers => ["nats://127.0.0.1:4222"]})
|
10
|
-
|
11
7
|
Protobuf::Logging.logger = ::Logger.new(nil)
|
12
8
|
|
13
9
|
Benchmark.ips do |config|
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/protobuf/nats.rb
CHANGED
@@ -4,7 +4,6 @@ require "protobuf"
|
|
4
4
|
# We don't need this, but the CLI attempts to terminate.
|
5
5
|
require "protobuf/rpc/service_directory"
|
6
6
|
|
7
|
-
require "concurrent"
|
8
7
|
require "nats/io/client"
|
9
8
|
|
10
9
|
require "protobuf/nats/client"
|
@@ -22,6 +21,13 @@ module Protobuf
|
|
22
21
|
ACK = "\1".freeze
|
23
22
|
end
|
24
23
|
|
24
|
+
NatsClient = if defined? JRUBY_VERSION
|
25
|
+
require "protobuf/nats/jnats"
|
26
|
+
::Protobuf::Nats::JNats
|
27
|
+
else
|
28
|
+
::NATS::IO::Client
|
29
|
+
end
|
30
|
+
|
25
31
|
GET_CONNECTED_MUTEX = ::Mutex.new
|
26
32
|
|
27
33
|
def self.config
|
@@ -46,16 +52,39 @@ module Protobuf
|
|
46
52
|
GET_CONNECTED_MUTEX.synchronize do
|
47
53
|
return if @start_client_nats_connection
|
48
54
|
|
49
|
-
@client_nats_connection =
|
55
|
+
@client_nats_connection = NatsClient.new
|
50
56
|
@client_nats_connection.connect(config.connection_options)
|
51
57
|
|
52
58
|
# Ensure we have a valid connection to the NATS server.
|
53
59
|
@client_nats_connection.flush(5)
|
54
60
|
|
61
|
+
@client_nats_connection.on_error do |error|
|
62
|
+
log_error(error)
|
63
|
+
end
|
64
|
+
|
55
65
|
true
|
56
66
|
end
|
57
67
|
end
|
58
68
|
end
|
59
69
|
|
70
|
+
# This will work with both ruby and java errors
|
71
|
+
def self.log_error(error)
|
72
|
+
logger.error error.to_s
|
73
|
+
logger.error error.class.to_s
|
74
|
+
if error.respond_to?(:backtrace) && error.backtrace.is_a?(::Array)
|
75
|
+
logger.error error.backtrace.join("\n")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.logger
|
80
|
+
::Protobuf::Logging.logger
|
81
|
+
end
|
82
|
+
|
83
|
+
logger.info "Using #{NatsClient} to connect"
|
84
|
+
|
85
|
+
at_exit do
|
86
|
+
::Protobuf::Nats.client_nats_connection.close rescue nil
|
87
|
+
end
|
88
|
+
|
60
89
|
end
|
61
90
|
end
|
data/lib/protobuf/nats/client.rb
CHANGED
@@ -44,55 +44,106 @@ module Protobuf
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
|
51
|
-
nats = Protobuf::Nats.client_nats_connection
|
52
|
-
inbox = nats.new_inbox
|
53
|
-
lock = ::Monitor.new
|
54
|
-
ack_condition = lock.new_cond
|
55
|
-
pb_response_condition = lock.new_cond
|
56
|
-
response = nil
|
57
|
-
sid = nats.subscribe(inbox, :max => 2) do |message|
|
58
|
-
lock.synchronize do
|
59
|
-
case message
|
60
|
-
when ::Protobuf::Nats::Messages::ACK
|
61
|
-
ack_condition.signal
|
62
|
-
next
|
63
|
-
else
|
64
|
-
response = message
|
65
|
-
pb_response_condition.signal
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
lock.synchronize do
|
71
|
-
# Publish to server
|
72
|
-
nats.publish(subject, data, inbox)
|
47
|
+
# The Java nats client offers better message queueing so we're going to use
|
48
|
+
# that over locking ourselves. This split in code isn't great, but we can
|
49
|
+
# refactor this later.
|
50
|
+
if defined? JRUBY_VERSION
|
73
51
|
|
52
|
+
# This is a request that expects two responses.
|
53
|
+
# 1. An ACK from the server. We use a shorter timeout.
|
54
|
+
# 2. A PB message from the server. We use a longer timoeut.
|
55
|
+
def nats_request_with_two_responses(subject, data, opts)
|
74
56
|
# Wait for the ACK from the server
|
75
57
|
ack_timeout = opts[:ack_timeout] || 5
|
76
|
-
with_timeout(ack_timeout) { ack_condition.wait(ack_timeout) }
|
77
|
-
|
78
58
|
# Wait for the protobuf response
|
79
59
|
timeout = opts[:timeout] || 60
|
80
|
-
|
60
|
+
|
61
|
+
nats = ::Protobuf::Nats.client_nats_connection
|
62
|
+
inbox = nats.new_inbox
|
63
|
+
|
64
|
+
# Publish to server
|
65
|
+
sub = nats.subscribe(inbox, :max => 2)
|
66
|
+
nats.publish(subject, data, inbox)
|
67
|
+
|
68
|
+
# Wait for reply
|
69
|
+
first_message = nats.next_message(sub, ack_timeout)
|
70
|
+
fail ::NATS::IO::Timeout if first_message.nil?
|
71
|
+
second_message = nats.next_message(sub, timeout)
|
72
|
+
fail ::NATS::IO::Timeout if second_message.nil?
|
73
|
+
|
74
|
+
# Check messages
|
75
|
+
response = nil
|
76
|
+
has_ack = false
|
77
|
+
case first_message.data
|
78
|
+
when ::Protobuf::Nats::Messages::ACK then has_ack = true
|
79
|
+
else response = first_message.data
|
80
|
+
end
|
81
|
+
case second_message.data
|
82
|
+
when ::Protobuf::Nats::Messages::ACK then has_ack = true
|
83
|
+
else response = second_message.data
|
84
|
+
end
|
85
|
+
|
86
|
+
success = has_ack && response
|
87
|
+
fail ::NATS::IO::Timeout unless success
|
88
|
+
|
89
|
+
response
|
90
|
+
ensure
|
91
|
+
# Ensure we don't leave a subscriptiosn sitting around.
|
92
|
+
# This also cleans up memory. It's a no-op if the subscription
|
93
|
+
# is already cleaned up.
|
94
|
+
nats.unsubscribe(sub)
|
81
95
|
end
|
82
96
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
97
|
+
else
|
98
|
+
|
99
|
+
def nats_request_with_two_responses(subject, data, opts)
|
100
|
+
nats = Protobuf::Nats.client_nats_connection
|
101
|
+
inbox = nats.new_inbox
|
102
|
+
lock = ::Monitor.new
|
103
|
+
ack_condition = lock.new_cond
|
104
|
+
pb_response_condition = lock.new_cond
|
105
|
+
response = nil
|
106
|
+
sid = nats.subscribe(inbox, :max => 2) do |message, _, _|
|
107
|
+
lock.synchronize do
|
108
|
+
case message
|
109
|
+
when ::Protobuf::Nats::Messages::ACK
|
110
|
+
ack_condition.signal
|
111
|
+
next
|
112
|
+
else
|
113
|
+
response = message
|
114
|
+
pb_response_condition.signal
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
lock.synchronize do
|
120
|
+
# Publish to server
|
121
|
+
nats.publish(subject, data, inbox)
|
122
|
+
|
123
|
+
# Wait for the ACK from the server
|
124
|
+
ack_timeout = opts[:ack_timeout] || 5
|
125
|
+
with_timeout(ack_timeout) { ack_condition.wait(ack_timeout) }
|
126
|
+
|
127
|
+
# Wait for the protobuf response
|
128
|
+
timeout = opts[:timeout] || 60
|
129
|
+
with_timeout(timeout) { pb_response_condition.wait(timeout) } unless response
|
130
|
+
end
|
131
|
+
|
132
|
+
response
|
133
|
+
ensure
|
134
|
+
# Ensure we don't leave a subscription sitting around.
|
135
|
+
nats.unsubscribe(sid) if response.nil?
|
136
|
+
end
|
137
|
+
|
138
|
+
# This is a copy of #with_nats_timeout
|
139
|
+
def with_timeout(timeout)
|
140
|
+
start_time = ::NATS::MonotonicTime.now
|
141
|
+
yield
|
142
|
+
end_time = ::NATS::MonotonicTime.now
|
143
|
+
duration = end_time - start_time
|
144
|
+
raise ::NATS::IO::Timeout.new("nats: timeout") if duration > timeout
|
145
|
+
end
|
88
146
|
|
89
|
-
# This is a copy of #with_nats_timeout
|
90
|
-
def with_timeout(timeout)
|
91
|
-
start_time = ::NATS::MonotonicTime.now
|
92
|
-
yield
|
93
|
-
end_time = ::NATS::MonotonicTime.now
|
94
|
-
duration = end_time - start_time
|
95
|
-
raise ::NATS::IO::Timeout.new("nats: timeout") if duration > timeout
|
96
147
|
end
|
97
148
|
|
98
149
|
end
|
data/lib/protobuf/nats/config.rb
CHANGED
@@ -4,7 +4,7 @@ require "yaml"
|
|
4
4
|
module Protobuf
|
5
5
|
module Nats
|
6
6
|
class Config
|
7
|
-
attr_accessor :uses_tls, :servers, :connect_timeout, :tls_client_cert, :tls_client_key
|
7
|
+
attr_accessor :uses_tls, :servers, :connect_timeout, :tls_client_cert, :tls_client_key, :tls_ca_cert
|
8
8
|
|
9
9
|
CONFIG_MUTEX = ::Mutex.new
|
10
10
|
|
@@ -13,6 +13,7 @@ module Protobuf
|
|
13
13
|
:servers => nil,
|
14
14
|
:tls_client_cert => nil,
|
15
15
|
:tls_client_key => nil,
|
16
|
+
:tls_ca_cert => nil,
|
16
17
|
:uses_tls => false,
|
17
18
|
}.freeze
|
18
19
|
|
@@ -53,6 +54,10 @@ module Protobuf
|
|
53
54
|
@connection_options ||= begin
|
54
55
|
options = {
|
55
56
|
servers: servers,
|
57
|
+
uses_tls: uses_tls,
|
58
|
+
tls_client_cert: tls_client_cert,
|
59
|
+
tls_client_key: tls_client_key,
|
60
|
+
tls_ca_cert: tls_ca_cert,
|
56
61
|
connect_timeout: connect_timeout
|
57
62
|
}
|
58
63
|
options[:tls] = {:context => new_tls_context} if uses_tls
|
@@ -0,0 +1,239 @@
|
|
1
|
+
ext_base = ::File.join(::File.dirname(__FILE__), '..', '..', '..', 'ext')
|
2
|
+
|
3
|
+
require ::File.join(ext_base, "jars/slf4j-api-1.7.25.jar")
|
4
|
+
require ::File.join(ext_base, "jars/slf4j-simple-1.7.25.jar")
|
5
|
+
require ::File.join(ext_base, "jars/gson-2.6.2.jar")
|
6
|
+
require ::File.join(ext_base, "jars/jnats-1.1-SNAPSHOT.jar")
|
7
|
+
|
8
|
+
# Set field accessors so we can access the member variables directly.
|
9
|
+
class Java::IoNatsClient::SubscriptionImpl
|
10
|
+
field_accessor :pMsgs, :pBytes, :delivered
|
11
|
+
end
|
12
|
+
|
13
|
+
module Protobuf
|
14
|
+
module Nats
|
15
|
+
class JNats
|
16
|
+
attr_reader :connection
|
17
|
+
|
18
|
+
class Message
|
19
|
+
attr_reader :data, :subject, :reply
|
20
|
+
|
21
|
+
def initialize(nats_message)
|
22
|
+
@data = nats_message.getData.to_s
|
23
|
+
@reply = nats_message.getReplyTo.to_s
|
24
|
+
@subject = nats_message.getSubject
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@on_error_cb = lambda {|error|}
|
30
|
+
@on_reconnect_cb = lambda {}
|
31
|
+
@on_disconnect_cb = lambda {}
|
32
|
+
@on_close_cb = lambda {}
|
33
|
+
@subz_cbs = {}
|
34
|
+
@subz_mutex = ::Mutex.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def connect(options = {})
|
38
|
+
servers = options[:servers] || ["nats://localhost:4222"]
|
39
|
+
servers = [servers].flatten.map { |uri_string| java.net.URI.new(uri_string) }
|
40
|
+
connection_factory = ::Java::IoNatsClient::ConnectionFactory.new
|
41
|
+
connection_factory.setServers(servers)
|
42
|
+
# Basically never stop trying to connect
|
43
|
+
connection_factory.setMaxReconnect(60_000)
|
44
|
+
|
45
|
+
# Setup callbacks
|
46
|
+
connection_factory.setDisconnectedCallback { |event| @on_disconnect_cb.call }
|
47
|
+
connection_factory.setReconnectedCallback { |_event| @on_reconnect_cb.call }
|
48
|
+
connection_factory.setClosedCallback { |_event| @on_close_cb.call }
|
49
|
+
connection_factory.setExceptionHandler { |error| @on_error_cb.call(error) }
|
50
|
+
|
51
|
+
# Setup ssl context if we're using tls
|
52
|
+
if options[:uses_tls]
|
53
|
+
ssl_context = create_ssl_context(options)
|
54
|
+
connection_factory.setSecure(true)
|
55
|
+
connection_factory.setSSLContext(ssl_context)
|
56
|
+
end
|
57
|
+
|
58
|
+
@connection = connection_factory.createConnection
|
59
|
+
|
60
|
+
# We're going to spawn a consumer and supervisor
|
61
|
+
@work_queue = @connection.createMsgChannel
|
62
|
+
spwan_supervisor_and_consumer
|
63
|
+
|
64
|
+
@connection
|
65
|
+
end
|
66
|
+
|
67
|
+
# Do not depend on #close for a greaceful disconnect.
|
68
|
+
def close
|
69
|
+
@connection.close
|
70
|
+
@supervisor.kill rescue nil
|
71
|
+
@consumer.kill rescue nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def flush(timeout_sec = 0.5)
|
75
|
+
@connection.flush(timeout_sec * 1000)
|
76
|
+
end
|
77
|
+
|
78
|
+
def next_message(sub, timeout_sec)
|
79
|
+
nats_message = sub.nextMessage(timeout_sec * 1000)
|
80
|
+
return nil unless nats_message
|
81
|
+
Message.new(nats_message)
|
82
|
+
end
|
83
|
+
|
84
|
+
def publish(subject, data, mailbox = nil)
|
85
|
+
# The "true" here is to force flush. May not need this.
|
86
|
+
@connection.publish(subject, mailbox, data.to_java_bytes, true)
|
87
|
+
end
|
88
|
+
|
89
|
+
def subscribe(subject, options = {}, &block)
|
90
|
+
queue = options[:queue]
|
91
|
+
max = options[:max]
|
92
|
+
work_queue = nil
|
93
|
+
# We pass our work queue for processing async work because java nats
|
94
|
+
# uses a cahced thread pool: 1 thread per async subscription.
|
95
|
+
# Sync subs need their own queue so work is not processed async.
|
96
|
+
work_queue = block.nil? ? @connection.createMsgChannel : @work_queue
|
97
|
+
sub = @connection.subscribe(subject, queue, nil, work_queue)
|
98
|
+
|
99
|
+
# Register the block callback. We only lock to save the callback.
|
100
|
+
if block
|
101
|
+
@subz_mutex.synchronize do
|
102
|
+
@subz_cbs[sub.getSid] = block
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Auto unsub if max message option was provided.
|
107
|
+
sub.autoUnsubscribe(max) if max
|
108
|
+
|
109
|
+
sub
|
110
|
+
end
|
111
|
+
|
112
|
+
def unsubscribe(sub)
|
113
|
+
return if sub.nil?
|
114
|
+
|
115
|
+
# Cleanup our async callback
|
116
|
+
if @subz_cbs[sub.getSid]
|
117
|
+
@subz_mutex.synchronize do
|
118
|
+
@subz_cbs.delete(sub.getSid)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# The "true" here is to ignore and invalid conn.
|
123
|
+
sub.unsubscribe(true)
|
124
|
+
end
|
125
|
+
|
126
|
+
def new_inbox
|
127
|
+
"_INBOX.#{::SecureRandom.hex(13)}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def on_reconnect(&cb)
|
131
|
+
@on_reconnect_cb = cb
|
132
|
+
end
|
133
|
+
|
134
|
+
def on_disconnect(&cb)
|
135
|
+
@on_disconnect_cb = cb
|
136
|
+
end
|
137
|
+
|
138
|
+
def on_error(&cb)
|
139
|
+
@on_error_cb = cb
|
140
|
+
end
|
141
|
+
|
142
|
+
def on_close(&cb)
|
143
|
+
@on_close_cb = cb
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def spwan_supervisor_and_consumer
|
149
|
+
spawn_consumer
|
150
|
+
@supervisor = ::Thread.new do
|
151
|
+
loop do
|
152
|
+
begin
|
153
|
+
sleep 1
|
154
|
+
next if @consumer && @consumer.alive?
|
155
|
+
# We need to recreate the consumer thread
|
156
|
+
@consumer.kill if @consumer
|
157
|
+
spawn_consumer
|
158
|
+
rescue => error
|
159
|
+
@on_error_cb.call(error)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def spawn_consumer
|
166
|
+
@consumer = ::Thread.new do
|
167
|
+
loop do
|
168
|
+
begin
|
169
|
+
message = @work_queue.take
|
170
|
+
next unless message
|
171
|
+
sub = message.getSubscription
|
172
|
+
|
173
|
+
# We have to update the subscription stats so we're not considered a slow consumer.
|
174
|
+
begin
|
175
|
+
sub.lock
|
176
|
+
sub.pMsgs -= 1
|
177
|
+
sub.pBytes -= message.getData.length if message.getData
|
178
|
+
sub.delivered += 1 unless sub.isClosed
|
179
|
+
ensure
|
180
|
+
sub.unlock
|
181
|
+
end
|
182
|
+
|
183
|
+
# We don't need t
|
184
|
+
callback = @subz_cbs[sub.getSid]
|
185
|
+
next unless callback
|
186
|
+
callback.call(message.getData.to_s, message.getReplyTo, message.getSubject)
|
187
|
+
rescue => error
|
188
|
+
@on_error_cb.call(error)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Jruby-openssl depends on bouncycastle so our lives don't suck super bad
|
195
|
+
def read_pem_object_from_file(path)
|
196
|
+
fail ::ArgumentError, "Tried to read a PEM key or cert with path nil" if path.nil?
|
197
|
+
|
198
|
+
file_reader = java.io.FileReader.new(path)
|
199
|
+
pem_parser = org.bouncycastle.openssl.PEMParser.new(file_reader)
|
200
|
+
object = pem_parser.readObject
|
201
|
+
pem_parser.close
|
202
|
+
object
|
203
|
+
end
|
204
|
+
|
205
|
+
def create_ssl_context(options)
|
206
|
+
# Create our certs and key converters to go from bouncycastle to java.
|
207
|
+
cert_converter = org.bouncycastle.cert.jcajce.JcaX509CertificateConverter.new
|
208
|
+
key_converter = org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.new
|
209
|
+
|
210
|
+
# Load the certs and keys.
|
211
|
+
tls_ca_cert = cert_converter.getCertificate(read_pem_object_from_file(options[:tls_ca_cert]))
|
212
|
+
tls_client_cert = cert_converter.getCertificate(read_pem_object_from_file(options[:tls_client_cert]))
|
213
|
+
tls_client_key = key_converter.getKeyPair(read_pem_object_from_file(options[:tls_client_key]))
|
214
|
+
|
215
|
+
# Setup the CA cert.
|
216
|
+
ca_key_store = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType)
|
217
|
+
ca_key_store.load(nil, nil)
|
218
|
+
ca_key_store.setCertificateEntry("ca-certificate", tls_ca_cert)
|
219
|
+
trust_manager = javax.net.ssl.TrustManagerFactory.getInstance(javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm)
|
220
|
+
trust_manager.init(ca_key_store)
|
221
|
+
|
222
|
+
# Setup the cert / key pair.
|
223
|
+
client_key_store = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType)
|
224
|
+
client_key_store.load(nil, nil)
|
225
|
+
client_key_store.setCertificateEntry("certificate", tls_client_cert)
|
226
|
+
certificate_java_array = [tls_client_cert].to_java(java.security.cert.Certificate)
|
227
|
+
empty_password = [].to_java(:char)
|
228
|
+
client_key_store.setKeyEntry("private-key", tls_client_key.getPrivate, empty_password, certificate_java_array)
|
229
|
+
key_manager = javax.net.ssl.KeyManagerFactory.getInstance(javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm)
|
230
|
+
key_manager.init(client_key_store, empty_password)
|
231
|
+
|
232
|
+
# Create ssl context.
|
233
|
+
context = javax.net.ssl.SSLContext.getInstance("TLSv1.2")
|
234
|
+
context.init(key_manager.getKeyManagers, trust_manager.getTrustManagers, nil)
|
235
|
+
context
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
data/lib/protobuf/nats/server.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "active_support/core_ext/class/subclasses"
|
2
2
|
require "protobuf/rpc/server"
|
3
3
|
require "protobuf/rpc/service"
|
4
|
+
require "protobuf/nats/thread_pool"
|
4
5
|
|
5
6
|
module Protobuf
|
6
7
|
module Nats
|
@@ -15,10 +16,10 @@ module Protobuf
|
|
15
16
|
@running = true
|
16
17
|
@stopped = false
|
17
18
|
|
18
|
-
@nats = options[:client] || ::
|
19
|
+
@nats = options[:client] || ::Protobuf::Nats::NatsClient.new
|
19
20
|
@nats.connect(::Protobuf::Nats.config.connection_options)
|
20
21
|
|
21
|
-
@thread_pool = ::
|
22
|
+
@thread_pool = ::Protobuf::Nats::ThreadPool.new(options[:threads], :max_queue => options[:threads])
|
22
23
|
|
23
24
|
@subscriptions = []
|
24
25
|
end
|
@@ -27,29 +28,22 @@ module Protobuf
|
|
27
28
|
::Protobuf::Rpc::Service.implemented_services.map(&:safe_constantize)
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
def enqueue_request(request_data, reply_id)
|
32
|
+
was_enqueued = thread_pool.push do
|
33
|
+
begin
|
34
|
+
# Process request.
|
35
|
+
response_data = handle_request(request_data)
|
36
|
+
# Publish response.
|
37
|
+
nats.publish(reply_id, response_data)
|
38
|
+
rescue => error
|
39
|
+
::Protobuf::Nats.log_error(error)
|
40
|
+
end
|
41
|
+
end
|
39
42
|
|
40
43
|
# Publish an ACK to signal the server has picked up the work.
|
41
|
-
nats.publish(reply_id, ::Protobuf::Nats::Messages::ACK)
|
44
|
+
nats.publish(reply_id, ::Protobuf::Nats::Messages::ACK) if was_enqueued
|
42
45
|
|
43
|
-
|
44
|
-
rescue ::Concurrent::RejectedExecutionError
|
45
|
-
nil
|
46
|
-
end
|
47
|
-
|
48
|
-
def log_error(error)
|
49
|
-
logger.error error.to_s
|
50
|
-
if error.respond_to?(:backtrace) && error.backtrace.is_a?(::Array)
|
51
|
-
logger.error error.backtrace.join("\n")
|
52
|
-
end
|
46
|
+
was_enqueued
|
53
47
|
end
|
54
48
|
|
55
49
|
def subscribe_to_services
|
@@ -64,7 +58,7 @@ module Protobuf
|
|
64
58
|
logger.info " - #{subscription_key_and_queue}"
|
65
59
|
|
66
60
|
subscriptions << nats.subscribe(subscription_key_and_queue, :queue => subscription_key_and_queue) do |request_data, reply_id, _subject|
|
67
|
-
unless
|
61
|
+
unless enqueue_request(request_data, reply_id)
|
68
62
|
logger.error { "Thread pool is full! Dropping message for: #{subscription_key_and_queue}" }
|
69
63
|
end
|
70
64
|
end
|
@@ -82,7 +76,7 @@ module Protobuf
|
|
82
76
|
end
|
83
77
|
|
84
78
|
nats.on_error do |error|
|
85
|
-
log_error(error)
|
79
|
+
::Protobuf::Nats.log_error(error)
|
86
80
|
end
|
87
81
|
|
88
82
|
nats.on_close do
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Protobuf
|
2
|
+
module Nats
|
3
|
+
class ThreadPool
|
4
|
+
|
5
|
+
def initialize(size, opts = {})
|
6
|
+
@queue = ::Queue.new
|
7
|
+
@active_work = 0
|
8
|
+
|
9
|
+
# Callbacks
|
10
|
+
@error_cb = lambda {|_error|}
|
11
|
+
|
12
|
+
# Synchronization
|
13
|
+
@mutex = ::Mutex.new
|
14
|
+
@cb_mutex = ::Mutex.new
|
15
|
+
|
16
|
+
# Let's get this party started
|
17
|
+
queue_size = opts[:max_queue].to_i || 0
|
18
|
+
@max_size = size + queue_size
|
19
|
+
@max_workers = size
|
20
|
+
@shutting_down = false
|
21
|
+
@workers = []
|
22
|
+
supervise_workers
|
23
|
+
end
|
24
|
+
|
25
|
+
def full?
|
26
|
+
@active_work >= @max_size
|
27
|
+
end
|
28
|
+
|
29
|
+
# This method is not thread safe by design since our IO model is a single producer thread
|
30
|
+
# with multiple consumer threads.
|
31
|
+
def push(&work_cb)
|
32
|
+
return false if full?
|
33
|
+
return false if @shutting_down
|
34
|
+
@queue << [:work, work_cb]
|
35
|
+
@mutex.synchronize { @active_work += 1 }
|
36
|
+
supervise_workers
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
# This method is not thread safe by design since our IO model is a single producer thread
|
41
|
+
# with multiple consumer threads.
|
42
|
+
def shutdown
|
43
|
+
@shutting_down = true
|
44
|
+
@max_workers.times { @queue << [:stop, nil] }
|
45
|
+
end
|
46
|
+
|
47
|
+
def kill
|
48
|
+
@shutting_down = true
|
49
|
+
@workers.map(&:kill)
|
50
|
+
end
|
51
|
+
|
52
|
+
# This method is not thread safe by design since our IO model is a single producer thread
|
53
|
+
# with multiple consumer threads.
|
54
|
+
def wait_for_termination(seconds = nil)
|
55
|
+
started_at = ::Time.now
|
56
|
+
loop do
|
57
|
+
sleep 0.1
|
58
|
+
break if seconds && (::Time.now - started_at) >= seconds
|
59
|
+
break if @workers.empty?
|
60
|
+
prune_dead_workers
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# This callback is executed in a thread safe manner.
|
65
|
+
def on_error(&cb)
|
66
|
+
@error_cb = cb
|
67
|
+
end
|
68
|
+
|
69
|
+
def size
|
70
|
+
@active_work
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def prune_dead_workers
|
76
|
+
@workers = @workers.select(&:alive?)
|
77
|
+
end
|
78
|
+
|
79
|
+
def supervise_workers
|
80
|
+
prune_dead_workers
|
81
|
+
missing_worker_count = (@max_workers - @workers.size)
|
82
|
+
missing_worker_count.times do
|
83
|
+
@workers << spawn_worker
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def spawn_worker
|
88
|
+
::Thread.new do
|
89
|
+
loop do
|
90
|
+
type, cb = @queue.pop
|
91
|
+
begin
|
92
|
+
# Break if we're shutting down
|
93
|
+
break if type == :stop
|
94
|
+
# Perform work
|
95
|
+
cb.call
|
96
|
+
# Update stats
|
97
|
+
rescue => error
|
98
|
+
@cb_mutex.synchronize { @error_cb.call(error) }
|
99
|
+
ensure
|
100
|
+
@mutex.synchronize { @active_work -= 1 }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/protobuf-nats.gemspec
CHANGED
@@ -31,7 +31,6 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.require_paths = ["lib"]
|
32
32
|
|
33
33
|
spec.add_runtime_dependency "protobuf", "~> 3.7"
|
34
|
-
spec.add_runtime_dependency "concurrent-ruby"
|
35
34
|
spec.add_runtime_dependency "nats-pure"
|
36
35
|
|
37
36
|
spec.add_development_dependency "bundler", "~> 1.14"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protobuf-nats
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Dewitt
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: protobuf
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.7'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: concurrent-ruby
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: nats-pure
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -144,11 +130,17 @@ files:
|
|
144
130
|
- examples/warehouse/app.rb
|
145
131
|
- examples/warehouse/start_client.sh
|
146
132
|
- examples/warehouse/start_server.sh
|
133
|
+
- ext/jars/gson-2.6.2.jar
|
134
|
+
- ext/jars/jnats-1.1-SNAPSHOT.jar
|
135
|
+
- ext/jars/slf4j-api-1.7.25.jar
|
136
|
+
- ext/jars/slf4j-simple-1.7.25.jar
|
147
137
|
- lib/protobuf/nats.rb
|
148
138
|
- lib/protobuf/nats/client.rb
|
149
139
|
- lib/protobuf/nats/config.rb
|
140
|
+
- lib/protobuf/nats/jnats.rb
|
150
141
|
- lib/protobuf/nats/runner.rb
|
151
142
|
- lib/protobuf/nats/server.rb
|
143
|
+
- lib/protobuf/nats/thread_pool.rb
|
152
144
|
- lib/protobuf/nats/version.rb
|
153
145
|
- protobuf-nats.gemspec
|
154
146
|
homepage:
|