protobuf-nats 0.12.0.pre0 → 0.13.0.pre0
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/.circleci/config.yml +84 -0
- data/CHANGELOG.md +7 -0
- data/README.md +8 -3
- data/bench/bench.md +16 -0
- data/bench/console.rb +17 -0
- data/bench/real_client.rb +2 -2
- data/bench/real_client.sh +13 -0
- data/bench/real_client_threaded.rb +19 -0
- data/bench/real_client_threaded.sh +10 -0
- data/bench/real_server.sh +16 -1
- data/examples/warehouse/app.rb +8 -2
- data/lib/protobuf/nats/client.rb +69 -219
- data/lib/protobuf/nats/config.rb +5 -3
- data/lib/protobuf/nats/errors.rb +4 -5
- data/lib/protobuf/nats/response_muxer.rb +400 -0
- data/lib/protobuf/nats/response_muxer_request.rb +28 -0
- data/lib/protobuf/nats/server.rb +80 -73
- data/lib/protobuf/nats/super_subscription_manager.rb +143 -0
- data/lib/protobuf/nats/thread_pool.rb +48 -19
- data/lib/protobuf/nats/uuidv7_helper.rb +37 -0
- data/lib/protobuf/nats/version.rb +1 -1
- data/lib/protobuf/nats.rb +10 -11
- data/protobuf-nats.gemspec +8 -2
- metadata +49 -15
- data/lib/protobuf/nats/jnats.rb +0 -251
- data/lib/protobuf/nats/platform.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34ffc46779be7a7549af169386c7212ed7210997dba1c1af19326a10aa92f98f
|
|
4
|
+
data.tar.gz: 9ca966e67099fc4332bcd4dc9270032f0238cf8afa7f18cb57a6c81e4307ed6e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d11137ae46ebfe8a1f003fda41aeba78d4a23893efafb38ad803969ba44df594dd3b0d0b5289e6bface579458baea1327749919f92ecbbbf6e3acbd2235c020
|
|
7
|
+
data.tar.gz: 5f6b0a643ea4d85063232800dff9fefba05ac70b8787666253244afa474f8314ae6de67b2f10efb970884c47150ce404a78dbf9d18b8ac425727f2172196f836
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
version: 2.1
|
|
2
|
+
|
|
3
|
+
jobs:
|
|
4
|
+
build_and_test:
|
|
5
|
+
parameters:
|
|
6
|
+
docker_image:
|
|
7
|
+
type: string
|
|
8
|
+
description: "The Ruby or JRuby Docker image to test against"
|
|
9
|
+
|
|
10
|
+
docker:
|
|
11
|
+
# 1. The Primary Container (where your code actually runs)
|
|
12
|
+
- image: << parameters.docker_image >>
|
|
13
|
+
environment:
|
|
14
|
+
JRUBY_OPTS: "-J-Xmx1024m"
|
|
15
|
+
RAILS_ENV: test
|
|
16
|
+
|
|
17
|
+
# 2. The Service Container (runs in the background)
|
|
18
|
+
- image: nats:2.14-linux
|
|
19
|
+
|
|
20
|
+
working_directory: ~/project
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- run:
|
|
24
|
+
name: Install System Dependencies
|
|
25
|
+
command: |
|
|
26
|
+
if [ "$(id -u)" = "0" ]; then
|
|
27
|
+
apt-get update && apt-get install -y build-essential git
|
|
28
|
+
else
|
|
29
|
+
sudo apt-get update && sudo apt-get install -y build-essential git
|
|
30
|
+
fi
|
|
31
|
+
- checkout
|
|
32
|
+
# Note: We added the docker_image parameter to the cache key
|
|
33
|
+
# so MRI and JRuby gems don't conflict.
|
|
34
|
+
- restore_cache:
|
|
35
|
+
keys:
|
|
36
|
+
- v1-gems-<< parameters.docker_image >>-{{ checksum "Gemfile.lock" }}
|
|
37
|
+
- v1-gems-<< parameters.docker_image >>-
|
|
38
|
+
|
|
39
|
+
- run:
|
|
40
|
+
name: Install Ruby Dependencies
|
|
41
|
+
command: |
|
|
42
|
+
gem install bundler
|
|
43
|
+
bundle config set --local path 'vendor/bundle'
|
|
44
|
+
bundle install --jobs=4 --retry=3
|
|
45
|
+
|
|
46
|
+
- save_cache:
|
|
47
|
+
paths:
|
|
48
|
+
- ./vendor/bundle
|
|
49
|
+
key: v1-gems-<< parameters.docker_image >>-{{ checksum "Gemfile.lock" }}
|
|
50
|
+
|
|
51
|
+
# Wait for NATS to be ready before running tests.
|
|
52
|
+
# Service containers can sometimes take a few seconds to boot up.
|
|
53
|
+
- run:
|
|
54
|
+
name: Wait for NATS
|
|
55
|
+
command: |
|
|
56
|
+
if [ "$(id -u)" = "0" ]; then
|
|
57
|
+
apt-get install -y netcat-openbsd
|
|
58
|
+
else
|
|
59
|
+
sudo apt-get install -y netcat-openbsd
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
echo "Waiting for NATS to start..."
|
|
63
|
+
while ! nc -z localhost 4222; do
|
|
64
|
+
sleep 1
|
|
65
|
+
done
|
|
66
|
+
echo "NATS is ready!"
|
|
67
|
+
|
|
68
|
+
- run:
|
|
69
|
+
name: Run Tests
|
|
70
|
+
command: bundle exec rspec
|
|
71
|
+
|
|
72
|
+
workflows:
|
|
73
|
+
version: 2
|
|
74
|
+
ruby_compatibility_matrix:
|
|
75
|
+
jobs:
|
|
76
|
+
- build_and_test:
|
|
77
|
+
name: test-<< matrix.docker_image >>
|
|
78
|
+
matrix:
|
|
79
|
+
parameters:
|
|
80
|
+
docker_image:
|
|
81
|
+
- "cimg/ruby:3.1"
|
|
82
|
+
- "cimg/ruby:3.4"
|
|
83
|
+
- "jruby:9.4"
|
|
84
|
+
- "jruby:10.0"
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
|
@@ -49,8 +49,6 @@ used to allow JVM based servers to warm-up slowly to prevent jolts in runtime pe
|
|
|
49
49
|
|
|
50
50
|
`PB_NATS_CLIENT_SUBSCRIPTION_POOL_SIZE` - If subscription pooling is desired for the request/response cycle then the pool size maximum should be set; the pool is lazy and therefore will only start new subscriptions as necessary (default: 0)
|
|
51
51
|
|
|
52
|
-
`PB_NATS_DISABLE_JNATS` - Disable the default jruby jnats client on the jruby platform, use the nats-pure.rb client instead (default: false).
|
|
53
|
-
|
|
54
52
|
`PROTOBUF_NATS_CONFIG_PATH` - Custom path to the config yaml (default: "config/protobuf_nats.yml").
|
|
55
53
|
|
|
56
54
|
### YAML Config
|
|
@@ -150,13 +148,20 @@ And we can see the message was sent to the server and the server replied with a
|
|
|
150
148
|
If we were to add another service endpoint called `search` to the `UserService` but fail to define an instance method
|
|
151
149
|
`search`, then `protobuf-nats` will not subscribe to that route.
|
|
152
150
|
|
|
151
|
+
## Future Improvements (locked behind ruby version)
|
|
152
|
+
- Migrate to native `Random.new.uuid_v7`
|
|
153
|
+
```ruby
|
|
154
|
+
@prng_lock.synchronize { @prng.uuid_v7(extra_timestamp_bits: 12) }
|
|
155
|
+
```
|
|
156
|
+
- Change ResponseMuxer to use `.pop()` with a timeout.
|
|
157
|
+
|
|
158
|
+
|
|
153
159
|
## Development
|
|
154
160
|
|
|
155
161
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
156
162
|
|
|
157
163
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
158
164
|
|
|
159
|
-
The java-nats client is temporarily forked to support jruby > 9.2.10.0. The living branch for that is here: https://github.com/film42/java-nats/tree/jruby-compat. This will be removed when we upgrade to the new nats.java client.
|
|
160
165
|
|
|
161
166
|
## Contributing
|
|
162
167
|
|
data/bench/bench.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
Notes:
|
|
3
|
+
`-Xjit.threshold=0` - Setting the threshold to 0 forces JRuby to compile every method into Java bytecode immediately before its very first execution. This is particularly useful for debugging or bypassing warm-up times during profiling
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
`-Xjit.threshold=10 -J-XX:CompileThreshold=10` - If you are running benchmarks and want both JRuby and the JVM to aggressively optimize early, you can lower both thresholds simultaneously
|
|
7
|
+
|
|
8
|
+
`bundle; bx ruby -I lib bench/real_client.rb`
|
|
9
|
+
|
|
10
|
+
Start local nats server so details can be monitored.
|
|
11
|
+
`/opt/homebrew/opt/nats-server/bin/nats-server -DV -m 8222 -p 4222`
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
export JRUBY_OPTS="--disable:did_you_mean -J-Djava.security.egd=file:/dev/./urandom -J-Xmx2g -J-Xms1024m -J-Xmn512m -Xjit.threshold=10 -J-XX:CompileThreshold=10"
|
|
16
|
+
```
|
data/bench/console.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "protobuf/nats"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
# ENV["PB_CLIENT_TYPE"] = "protobuf/nats/client"
|
|
14
|
+
# ENV["PB_SERVER_TYPE"] = "protobuf/nats/runner"
|
|
15
|
+
|
|
16
|
+
require "irb"
|
|
17
|
+
IRB.start(__FILE__)
|
data/bench/real_client.rb
CHANGED
|
@@ -7,8 +7,8 @@ require "./examples/warehouse/app"
|
|
|
7
7
|
Protobuf::Logging.logger = ::Logger.new(nil)
|
|
8
8
|
|
|
9
9
|
Benchmark.ips do |config|
|
|
10
|
-
config.warmup =
|
|
11
|
-
config.time =
|
|
10
|
+
config.warmup = 30
|
|
11
|
+
config.time = 30
|
|
12
12
|
|
|
13
13
|
config.report("single threaded performance") do
|
|
14
14
|
req = Warehouse::Shipment.new(:guid => SecureRandom.uuid)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
export JRUBY_OPTS="-J-server --disable:did_you_mean -Xcompile.invokedynamic=true -Xjit.threshold=10 -J-Djava.security.egd=file:/dev/./urandom -J-Xms2g -J-Xmx2g -J-XX:+UseG1GC -J-XX:MaxGCPauseMillis=100"
|
|
4
|
+
|
|
5
|
+
# export JRUBY_OPTS="-J-server --disable:did_you_mean -Xcompile.invokedynamic=true -Xjit.threshold=0 -J-Djruby.jit.max=0 -J-Djruby.jit.background=false -J-Djava.security.egd=file:/dev/./urandom -J-Xms2g -J-Xmx2g -J-XX:+UseG1GC -J-XX:MaxGCPauseMillis=100"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export PB_SERVER_TYPE="protobuf/nats/runner"
|
|
9
|
+
export PB_CLIENT_TYPE="protobuf/nats/client"
|
|
10
|
+
|
|
11
|
+
echo "$PWD"
|
|
12
|
+
|
|
13
|
+
bundle exec ruby -I lib bench/real_client.rb
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
ENV["PB_CLIENT_TYPE"] = "protobuf/nats/client"
|
|
2
|
+
ENV["PB_SERVER_TYPE"] = "protobuf/nats/runner"
|
|
3
|
+
|
|
4
|
+
require "./examples/warehouse/app"
|
|
5
|
+
|
|
6
|
+
THREAD_COUNT = ENV.fetch("CLIENT_THREADS",4).to_i
|
|
7
|
+
|
|
8
|
+
puts "THREAD_COUNT = #{THREAD_COUNT}"
|
|
9
|
+
|
|
10
|
+
::Protobuf::Logging.logger = ::Logger.new(nil)
|
|
11
|
+
|
|
12
|
+
while true
|
|
13
|
+
THREAD_COUNT.times.map do |i|
|
|
14
|
+
Thread.new do
|
|
15
|
+
req = Warehouse::Shipment.new(:guid => SecureRandom.uuid, :sleep_time_ms => 5)
|
|
16
|
+
Warehouse::ShipmentService.client.create(req)
|
|
17
|
+
end
|
|
18
|
+
end.each(&:join)
|
|
19
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
export JRUBY_OPTS="-J-server --disable:did_you_mean -Xcompile.invokedynamic=true -Xjit.threshold=10 -J-Djava.security.egd=file:/dev/./urandom -J-Xms2g -J-Xmx2g -J-XX:+UseG1GC -J-XX:MaxGCPauseMillis=100"
|
|
4
|
+
|
|
5
|
+
export PB_SERVER_TYPE="protobuf/nats/runner"
|
|
6
|
+
export PB_CLIENT_TYPE="protobuf/nats/client"
|
|
7
|
+
|
|
8
|
+
export THREAD_COUNT=4
|
|
9
|
+
|
|
10
|
+
bundle exec ruby -I lib bench/real_client_threaded.rb
|
data/bench/real_server.sh
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# export JRUBY_OPTS="-J-server --disable:did_you_mean -Xcompile.invokedynamic=true -Xjit.threshold=0 -J-Djruby.jit.max=0 -J-Djruby.jit.background=false -J-Djava.security.egd=file:/dev/./urandom -J-Xms2g -J-Xmx2g -J-XX:+UseG1GC -J-XX:MaxGCPauseMillis=100"
|
|
4
|
+
|
|
5
|
+
export JRUBY_OPTS="-J-server -J-Xms4g -J-Xmx4g -J-XX:+AlwaysPreTouch -J-XX:+UseParallelGC -J-XX:ReservedCodeCacheSize=768m -J-XX:MaxInlineLevel=18 -J-XX:MaxInlineSize=100 -J-XX:FreqInlineSize=500 -J-XX:LoopUnrollLimit=250 -J-XX:+UseSuperWord -J-Djruby.jit.threshold=0 -J-Djruby.jit.max=0 -J-Djruby.jit.background=false -Xcompile.invokedynamic=true"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export PB_SERVER_TYPE="protobuf/nats/runner"
|
|
9
|
+
export PB_CLIENT_TYPE="protobuf/nats/client"
|
|
10
|
+
|
|
11
|
+
export PB_NATS_SERVER_SLOW_START_DELAY=1
|
|
12
|
+
|
|
13
|
+
export PB_NATS_SERVER_MAX_QUEUE_SIZE=6
|
|
14
|
+
|
|
15
|
+
bundle exec rpc_server start --threads=10 ./examples/warehouse/app.rb
|
|
16
|
+
|
data/examples/warehouse/app.rb
CHANGED
|
@@ -12,7 +12,6 @@ module Warehouse
|
|
|
12
12
|
class ShipmentRequest < ::Protobuf::Message; end
|
|
13
13
|
class Shipments < ::Protobuf::Message; end
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
##
|
|
17
16
|
# Message Fields
|
|
18
17
|
#
|
|
@@ -21,6 +20,8 @@ module Warehouse
|
|
|
21
20
|
optional :string, :address, 2
|
|
22
21
|
optional :double, :price, 3
|
|
23
22
|
optional :string, :package_guid, 4
|
|
23
|
+
|
|
24
|
+
optional :int64, :sleep_time_ms, 100
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
class ShipmentRequest
|
|
@@ -33,7 +34,6 @@ module Warehouse
|
|
|
33
34
|
repeated ::Warehouse::Shipment, :records, 1
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
|
|
37
37
|
##
|
|
38
38
|
# Service Classes
|
|
39
39
|
#
|
|
@@ -43,6 +43,12 @@ module Warehouse
|
|
|
43
43
|
rpc :search, ::Warehouse::ShipmentRequest, ::Warehouse::Shipments
|
|
44
44
|
|
|
45
45
|
def create
|
|
46
|
+
# Allows for easier testing of multiple threads
|
|
47
|
+
if request.sleep_time_ms > 0
|
|
48
|
+
sleep(request.sleep_time_ms / 1000.0)
|
|
49
|
+
puts "sleep_time:#{request.sleep_time_ms}"
|
|
50
|
+
end
|
|
51
|
+
|
|
46
52
|
respond_with request
|
|
47
53
|
end
|
|
48
54
|
|
data/lib/protobuf/nats/client.rb
CHANGED
|
@@ -1,132 +1,20 @@
|
|
|
1
|
+
require 'securerandom'
|
|
1
2
|
require "connection_pool"
|
|
2
3
|
require "protobuf/nats"
|
|
3
|
-
require "protobuf/nats/platform"
|
|
4
4
|
require "protobuf/rpc/connectors/base"
|
|
5
5
|
require "monitor"
|
|
6
6
|
|
|
7
|
+
# Load this independently because we store the class singleton in a const.
|
|
8
|
+
require "protobuf/nats/response_muxer"
|
|
9
|
+
|
|
7
10
|
module Protobuf
|
|
8
11
|
module Nats
|
|
9
|
-
class ResponseMuxerRequest
|
|
10
|
-
def initialize(muxer, token)
|
|
11
|
-
@muxer = muxer
|
|
12
|
-
@token = token
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def publish(subject, data)
|
|
16
|
-
@muxer.publish(subject, data, @token)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def next_message(timeout)
|
|
20
|
-
@muxer.next_message(@token, timeout)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def cleanup
|
|
24
|
-
@muxer.cleanup(@token)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
class ResponseMuxer
|
|
29
|
-
LOCK = ::Mutex.new
|
|
30
|
-
|
|
31
|
-
def initialize
|
|
32
|
-
@resp_map = Hash.new { |h,k| h[k] = { } }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def cleanup(token)
|
|
36
|
-
@resp_sub.synchronize { @resp_map.delete(token) }
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def next_message(token, timeout)
|
|
40
|
-
::NATS::MonotonicTime::with_nats_timeout(timeout) do
|
|
41
|
-
@resp_sub.synchronize do
|
|
42
|
-
break if @resp_map[token].key?(:response) &&
|
|
43
|
-
!@resp_map[token][:response].empty?
|
|
44
|
-
|
|
45
|
-
@resp_map[token][:signal].wait(timeout)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
@resp_sub.synchronize { @resp_map[token][:response].shift }
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def new_request
|
|
53
|
-
nats = Protobuf::Nats.client_nats_connection
|
|
54
|
-
token = nats.new_inbox.split('.').last
|
|
55
|
-
@resp_sub.synchronize do
|
|
56
|
-
@resp_map[token][:signal] = @resp_sub.new_cond
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
ResponseMuxerRequest.new(self, token)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def publish(subject, data, token)
|
|
63
|
-
nats = Protobuf::Nats.client_nats_connection
|
|
64
|
-
reply_to = "#{@resp_inbox_prefix}.#{token}"
|
|
65
|
-
nats.publish(subject, data, reply_to)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def restart
|
|
69
|
-
start unless started?
|
|
70
|
-
|
|
71
|
-
LOCK.synchronize do
|
|
72
|
-
@resp_handler&.kill
|
|
73
|
-
@started = false
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
start
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def start
|
|
80
|
-
return if started?
|
|
81
|
-
LOCK.synchronize do
|
|
82
|
-
# We check this twice in case another thread was waiting for the lock to
|
|
83
|
-
# start this party.
|
|
84
|
-
return if started?
|
|
85
|
-
|
|
86
|
-
nats = ::Protobuf::Nats.client_nats_connection
|
|
87
|
-
return if nats.nil?
|
|
88
|
-
|
|
89
|
-
@resp_inbox_prefix = nats.new_inbox
|
|
90
|
-
@resp_sub = nats.subscribe("#{@resp_inbox_prefix}.*")
|
|
91
|
-
@started = true
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
@resp_handler = Thread.new do
|
|
95
|
-
begin
|
|
96
|
-
loop do
|
|
97
|
-
msg = @resp_sub.pending_queue.pop
|
|
98
|
-
next if msg.nil?
|
|
99
|
-
@resp_sub.synchronize do
|
|
100
|
-
# Decrease pending size since consumed already
|
|
101
|
-
@resp_sub.pending_size -= msg.data.size
|
|
102
|
-
end
|
|
103
|
-
token = msg.subject.split('.').last
|
|
104
|
-
|
|
105
|
-
@resp_sub.synchronize do
|
|
106
|
-
# Reject if the token is missing from the request map
|
|
107
|
-
break unless @resp_map.key?(token)
|
|
108
|
-
|
|
109
|
-
signal = @resp_map[token][:signal]
|
|
110
|
-
@resp_map[token][:response] ||= []
|
|
111
|
-
@resp_map[token][:response] << msg
|
|
112
|
-
signal.signal
|
|
113
|
-
end
|
|
114
|
-
rescue => error
|
|
115
|
-
::Protobuf::Nats.notify_error_callbacks(error)
|
|
116
|
-
LOCK.synchronize { @started = false }
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def started?
|
|
123
|
-
!!@started
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
12
|
class Client < ::Protobuf::Rpc::Connectors::Base
|
|
128
13
|
|
|
129
|
-
RESPONSE_MUXER = ResponseMuxer.new
|
|
14
|
+
RESPONSE_MUXER = ::Protobuf::Nats::ResponseMuxer.new
|
|
15
|
+
|
|
16
|
+
@subscription_key_cache = {}
|
|
17
|
+
@subscription_pool_lock = ::Mutex.new
|
|
130
18
|
|
|
131
19
|
# Structure to hold subscription and inbox to use within pool
|
|
132
20
|
SubscriptionInbox = ::Struct.new(:subscription, :inbox) do
|
|
@@ -136,11 +24,26 @@ module Protobuf
|
|
|
136
24
|
end
|
|
137
25
|
end
|
|
138
26
|
|
|
27
|
+
def logger
|
|
28
|
+
::Protobuf::Logging.logger
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def response_muxer
|
|
32
|
+
RESPONSE_MUXER
|
|
33
|
+
end
|
|
34
|
+
|
|
139
35
|
def self.subscription_pool
|
|
140
|
-
@subscription_pool
|
|
141
|
-
inbox = ::Protobuf::Nats.client_nats_connection.new_inbox
|
|
36
|
+
return @subscription_pool if @subscription_pool
|
|
142
37
|
|
|
143
|
-
|
|
38
|
+
@subscription_pool_lock.synchronize do
|
|
39
|
+
# The double-check ensures we don't create a new pool if another
|
|
40
|
+
# thread created one while we were waiting for the lock.
|
|
41
|
+
return @subscription_pool if @subscription_pool
|
|
42
|
+
|
|
43
|
+
@subscription_pool = ::ConnectionPool.new(:size => subscription_pool_size, :timeout => 0.1) do
|
|
44
|
+
inbox = ::Protobuf::Nats.client_nats_connection.new_inbox
|
|
45
|
+
SubscriptionInbox.new(::Protobuf::Nats.client_nats_connection.subscribe(inbox), inbox)
|
|
46
|
+
end
|
|
144
47
|
end
|
|
145
48
|
end
|
|
146
49
|
|
|
@@ -194,7 +97,7 @@ module Protobuf
|
|
|
194
97
|
end
|
|
195
98
|
|
|
196
99
|
def self.subscription_key_cache
|
|
197
|
-
@subscription_key_cache
|
|
100
|
+
@subscription_key_cache
|
|
198
101
|
end
|
|
199
102
|
|
|
200
103
|
def ack_timeout
|
|
@@ -317,112 +220,59 @@ module Protobuf
|
|
|
317
220
|
"#{klass}##{method_name}"
|
|
318
221
|
end
|
|
319
222
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
# This is a request that expects two responses.
|
|
326
|
-
# 1. An ACK from the server. We use a shorter timeout.
|
|
327
|
-
# 2. A PB message from the server. We use a longer timoeut.
|
|
328
|
-
def nats_request_with_two_responses(subject, data, opts)
|
|
329
|
-
# Wait for the ACK from the server
|
|
330
|
-
ack_timeout = opts[:ack_timeout] || 5
|
|
331
|
-
# Wait for the protobuf response
|
|
332
|
-
timeout = opts[:timeout] || 60
|
|
333
|
-
|
|
334
|
-
nats = ::Protobuf::Nats.client_nats_connection
|
|
335
|
-
|
|
336
|
-
# Publish to server
|
|
337
|
-
with_subscription do |sub_inbox|
|
|
338
|
-
begin
|
|
339
|
-
completed_request = false
|
|
340
|
-
|
|
341
|
-
if !sub_inbox.subscription.is_valid # replace the subscription if is has been pooled but is no longer valid (maybe a reconnect)
|
|
342
|
-
nats.unsubscribe(sub_inbox.subscription)
|
|
343
|
-
sub_inbox.swap(new_subscription_inbox) # this line replaces the sub_inbox in the connection pool if necessary
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
nats.publish(subject, data, sub_inbox.inbox)
|
|
347
|
-
|
|
348
|
-
# Wait for reply
|
|
349
|
-
first_message = nats.next_message(sub_inbox.subscription, ack_timeout)
|
|
350
|
-
return :ack_timeout if first_message.nil?
|
|
351
|
-
|
|
352
|
-
first_message_data = first_message.data
|
|
353
|
-
return :nack if first_message_data == ::Protobuf::Nats::Messages::NACK
|
|
354
|
-
|
|
355
|
-
second_message = nats.next_message(sub_inbox.subscription, timeout)
|
|
356
|
-
second_message_data = second_message.nil? ? nil : second_message.data
|
|
223
|
+
def nats_request_with_two_responses(subject, data, opts)
|
|
224
|
+
# Wait for the ACK from the server
|
|
225
|
+
ack_timeout = opts[:ack_timeout] || 5
|
|
226
|
+
# Wait for the protobuf response
|
|
227
|
+
timeout = opts[:timeout] || 60
|
|
357
228
|
|
|
358
|
-
|
|
359
|
-
response = case ::Protobuf::Nats::Messages::ACK
|
|
360
|
-
when first_message_data then second_message_data
|
|
361
|
-
when second_message_data then first_message_data
|
|
362
|
-
else return :ack_timeout
|
|
363
|
-
end
|
|
229
|
+
nats = Protobuf::Nats.client_nats_connection
|
|
364
230
|
|
|
365
|
-
|
|
231
|
+
# Publish message with the reply topic pointed at the response muxer.
|
|
232
|
+
req = RESPONSE_MUXER.new_request
|
|
233
|
+
req.publish(subject, data)
|
|
366
234
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
end
|
|
235
|
+
# Receive the first message
|
|
236
|
+
begin
|
|
237
|
+
first_message = req.next_message(ack_timeout)
|
|
238
|
+
logger.debug { "received message with subject:#{first_message.subject}" } if logger.debug?
|
|
239
|
+
rescue ::NATS::Timeout => e
|
|
240
|
+
return :ack_timeout
|
|
376
241
|
end
|
|
377
242
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def nats_request_with_two_responses(subject, data, opts)
|
|
381
|
-
# Wait for the ACK from the server
|
|
382
|
-
ack_timeout = opts[:ack_timeout] || 5
|
|
383
|
-
# Wait for the protobuf response
|
|
384
|
-
timeout = opts[:timeout] || 60
|
|
243
|
+
# Check for a NACK
|
|
244
|
+
return :nack if first_message.data == ::Protobuf::Nats::Messages::NACK
|
|
385
245
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
# Receive the first message
|
|
393
|
-
begin
|
|
394
|
-
first_message = req.next_message(ack_timeout)
|
|
395
|
-
rescue ::NATS::Timeout => e
|
|
396
|
-
return :ack_timeout
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
# Check for a NACK
|
|
400
|
-
return :nack if first_message.data == ::Protobuf::Nats::Messages::NACK
|
|
401
|
-
|
|
402
|
-
# Receive the second message
|
|
403
|
-
begin
|
|
404
|
-
second_message = req.next_message(timeout)
|
|
405
|
-
rescue ::NATS::Timeout
|
|
406
|
-
# ignore to raise a repsonse timeout below
|
|
407
|
-
end
|
|
246
|
+
# Receive the second message
|
|
247
|
+
begin
|
|
248
|
+
second_message = req.next_message(timeout)
|
|
249
|
+
rescue ::NATS::Timeout
|
|
250
|
+
# ignore to raise a repsonse timeout below
|
|
251
|
+
end
|
|
408
252
|
|
|
409
|
-
|
|
410
|
-
|
|
253
|
+
# NOTE: This might be nil, so be careful checking the data value
|
|
254
|
+
second_message_data = second_message&.data
|
|
411
255
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
256
|
+
# This should never happen, if it does, then return an :ack_timeout because something went wrong
|
|
257
|
+
if first_message&.data == ::Protobuf::Nats::Messages::ACK &&
|
|
258
|
+
second_message&.data == ::Protobuf::Nats::Messages::ACK
|
|
259
|
+
logger.warn "received ACK/ACK message."
|
|
260
|
+
return :ack_timeout
|
|
261
|
+
end
|
|
418
262
|
|
|
419
|
-
|
|
263
|
+
# Check messages
|
|
264
|
+
response = case ::Protobuf::Nats::Messages::ACK
|
|
265
|
+
when first_message&.data then second_message_data
|
|
266
|
+
when second_message&.data then first_message&.data
|
|
267
|
+
else return :ack_timeout
|
|
268
|
+
end
|
|
420
269
|
|
|
421
|
-
|
|
422
|
-
ensure
|
|
423
|
-
req.cleanup if req
|
|
424
|
-
end
|
|
270
|
+
fail(::Protobuf::Nats::Errors::ResponseTimeout, formatted_service_and_method_name) unless response
|
|
425
271
|
|
|
272
|
+
response
|
|
273
|
+
ensure
|
|
274
|
+
# cleanup the token from the request map
|
|
275
|
+
req.cleanup if req
|
|
426
276
|
end
|
|
427
277
|
|
|
428
278
|
end
|
data/lib/protobuf/nats/config.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "erb"
|
|
1
2
|
require "openssl"
|
|
2
3
|
require "yaml"
|
|
3
4
|
|
|
@@ -39,12 +40,13 @@ module Protobuf
|
|
|
39
40
|
yaml_config = {}
|
|
40
41
|
config_path = ENV["PROTOBUF_NATS_CONFIG_PATH"] || ::File.join("config", "protobuf_nats.yml")
|
|
41
42
|
absolute_config_path = ::File.expand_path(config_path)
|
|
42
|
-
if ::File.
|
|
43
|
+
if ::File.exist?(absolute_config_path)
|
|
44
|
+
yaml_string = ::ERB.new(::File.read(absolute_config_path)).result
|
|
43
45
|
# Psych 4 and newer requires unsafe_load_file in order for aliases to be used
|
|
44
46
|
yaml_config = if ::YAML.respond_to?(:unsafe_load_file)
|
|
45
|
-
::YAML.
|
|
47
|
+
::YAML.unsafe_load(yaml_string)[env]
|
|
46
48
|
else
|
|
47
|
-
::YAML.
|
|
49
|
+
::YAML.load(yaml_string)[env]
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
|
data/lib/protobuf/nats/errors.rb
CHANGED
|
@@ -10,14 +10,13 @@ module Protobuf
|
|
|
10
10
|
class ResponseTimeout < ClientError
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
class ResponseMuxer < ClientError
|
|
14
|
+
end
|
|
15
|
+
|
|
13
16
|
class MriIOException < ::StandardError
|
|
14
17
|
end
|
|
15
18
|
|
|
16
|
-
IOException =
|
|
17
|
-
java.io.IOException
|
|
18
|
-
else
|
|
19
|
-
MriIOException
|
|
20
|
-
end
|
|
19
|
+
IOException = MriIOException
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
22
|
end
|