protobuf-nats 0.11.0.pre1 → 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 +5 -5
- data/.circleci/config.yml +84 -0
- data/.travis.yml +8 -4
- data/CHANGELOG.md +7 -0
- data/README.md +10 -1
- 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/ext/jars/jnats-1.1-SNAPSHOT.jar +0 -0
- data/lib/protobuf/nats/client.rb +83 -105
- data/lib/protobuf/nats/config.rb +9 -2
- data/lib/protobuf/nats/errors.rb +6 -16
- 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 +86 -34
- 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 -10
- data/protobuf-nats.gemspec +10 -4
- data/scripts/install_gnatsd.sh +20 -0
- metadata +70 -41
- data/ext/jars/jnats-2.6.0.jar +0 -0
- data/lib/protobuf/nats/jnats.rb +0 -281
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
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/.travis.yml
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
sudo: false
|
|
2
2
|
language: ruby
|
|
3
|
-
jdk:
|
|
3
|
+
jdk:
|
|
4
|
+
- openjdk8
|
|
4
5
|
rvm:
|
|
5
6
|
- 2.3.0
|
|
7
|
+
- 2.7.0
|
|
6
8
|
- jruby-9.1.7.0
|
|
9
|
+
- jruby-9.2.13.0
|
|
7
10
|
before_install:
|
|
11
|
+
# Install and start gnatsd
|
|
12
|
+
- ./scripts/install_gnatsd.sh
|
|
13
|
+
- $HOME/nats-server/nats-server &
|
|
14
|
+
# Install deps for project
|
|
8
15
|
- gem install bundler
|
|
9
16
|
- gem update --system
|
|
10
|
-
- wget https://github.com/nats-io/gnatsd/releases/download/v1.3.0/gnatsd-v1.3.0-linux-amd64.zip
|
|
11
|
-
- unzip gnatsd-v1.3.0-linux-amd64.zip
|
|
12
|
-
- ./gnatsd-v1.3.0-linux-amd64/gnatsd &
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
|
@@ -148,15 +148,24 @@ And we can see the message was sent to the server and the server replied with a
|
|
|
148
148
|
If we were to add another service endpoint called `search` to the `UserService` but fail to define an instance method
|
|
149
149
|
`search`, then `protobuf-nats` will not subscribe to that route.
|
|
150
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
|
+
|
|
151
159
|
## Development
|
|
152
160
|
|
|
153
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.
|
|
154
162
|
|
|
155
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).
|
|
156
164
|
|
|
165
|
+
|
|
157
166
|
## Contributing
|
|
158
167
|
|
|
159
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
168
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mxenabled/protobuf-nats.
|
|
160
169
|
|
|
161
170
|
|
|
162
171
|
## License
|
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
|
|
|
Binary file
|
data/lib/protobuf/nats/client.rb
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
require 'securerandom'
|
|
1
2
|
require "connection_pool"
|
|
2
3
|
require "protobuf/nats"
|
|
3
4
|
require "protobuf/rpc/connectors/base"
|
|
4
5
|
require "monitor"
|
|
5
6
|
|
|
7
|
+
# Load this independently because we store the class singleton in a const.
|
|
8
|
+
require "protobuf/nats/response_muxer"
|
|
9
|
+
|
|
6
10
|
module Protobuf
|
|
7
11
|
module Nats
|
|
8
12
|
class Client < ::Protobuf::Rpc::Connectors::Base
|
|
13
|
+
|
|
14
|
+
RESPONSE_MUXER = ::Protobuf::Nats::ResponseMuxer.new
|
|
15
|
+
|
|
16
|
+
@subscription_key_cache = {}
|
|
17
|
+
@subscription_pool_lock = ::Mutex.new
|
|
18
|
+
|
|
9
19
|
# Structure to hold subscription and inbox to use within pool
|
|
10
20
|
SubscriptionInbox = ::Struct.new(:subscription, :inbox) do
|
|
11
21
|
def swap(sub_inbox)
|
|
@@ -14,11 +24,26 @@ module Protobuf
|
|
|
14
24
|
end
|
|
15
25
|
end
|
|
16
26
|
|
|
27
|
+
def logger
|
|
28
|
+
::Protobuf::Logging.logger
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def response_muxer
|
|
32
|
+
RESPONSE_MUXER
|
|
33
|
+
end
|
|
34
|
+
|
|
17
35
|
def self.subscription_pool
|
|
18
|
-
@subscription_pool
|
|
19
|
-
|
|
36
|
+
return @subscription_pool if @subscription_pool
|
|
37
|
+
|
|
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
|
|
20
42
|
|
|
21
|
-
|
|
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
|
|
22
47
|
end
|
|
23
48
|
end
|
|
24
49
|
|
|
@@ -36,6 +61,9 @@ module Protobuf
|
|
|
36
61
|
|
|
37
62
|
# This will ensure the client is started.
|
|
38
63
|
::Protobuf::Nats.start_client_nats_connection
|
|
64
|
+
|
|
65
|
+
# Ensure the response muxer is started
|
|
66
|
+
RESPONSE_MUXER.start
|
|
39
67
|
end
|
|
40
68
|
|
|
41
69
|
def new_subscription_inbox
|
|
@@ -69,7 +97,7 @@ module Protobuf
|
|
|
69
97
|
end
|
|
70
98
|
|
|
71
99
|
def self.subscription_key_cache
|
|
72
|
-
@subscription_key_cache
|
|
100
|
+
@subscription_key_cache
|
|
73
101
|
end
|
|
74
102
|
|
|
75
103
|
def ack_timeout
|
|
@@ -151,12 +179,12 @@ module Protobuf
|
|
|
151
179
|
when :ack_timeout
|
|
152
180
|
::ActiveSupport::Notifications.instrument "client.request_timeout.protobuf-nats"
|
|
153
181
|
next if (retries -= 1) > 0
|
|
154
|
-
raise ::Protobuf::Nats::Errors::RequestTimeout
|
|
182
|
+
raise ::Protobuf::Nats::Errors::RequestTimeout, formatted_service_and_method_name
|
|
155
183
|
when :nack
|
|
156
184
|
::ActiveSupport::Notifications.instrument "client.request_nack.protobuf-nats"
|
|
157
185
|
interval = nack_backoff_intervals[nack_retry]
|
|
158
186
|
nack_retry += 1
|
|
159
|
-
raise ::Protobuf::Nats::Errors::RequestTimeout if interval.nil?
|
|
187
|
+
raise ::Protobuf::Nats::Errors::RequestTimeout, formatted_service_and_method_name if interval.nil?
|
|
160
188
|
sleep((interval + nack_backoff_splay)/1000.0)
|
|
161
189
|
next
|
|
162
190
|
end
|
|
@@ -165,11 +193,11 @@ module Protobuf
|
|
|
165
193
|
end
|
|
166
194
|
|
|
167
195
|
parse_response
|
|
168
|
-
rescue ::Protobuf::Nats::Errors::IOException
|
|
196
|
+
rescue ::Protobuf::Nats::Errors::IOException => error
|
|
169
197
|
::Protobuf::Nats.log_error(error)
|
|
170
198
|
|
|
171
199
|
delay = reconnect_delay
|
|
172
|
-
logger.warn "An
|
|
200
|
+
logger.warn "An IOException was raised. We are going to sleep for #{delay} seconds."
|
|
173
201
|
sleep delay
|
|
174
202
|
|
|
175
203
|
retry if (retries -= 1) > 0
|
|
@@ -186,115 +214,65 @@ module Protobuf
|
|
|
186
214
|
end
|
|
187
215
|
end
|
|
188
216
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
# This is a request that expects two responses.
|
|
195
|
-
# 1. An ACK from the server. We use a shorter timeout.
|
|
196
|
-
# 2. A PB message from the server. We use a longer timoeut.
|
|
197
|
-
def nats_request_with_two_responses(subject, data, opts)
|
|
198
|
-
# Wait for the ACK from the server
|
|
199
|
-
ack_timeout = opts[:ack_timeout] || 5
|
|
200
|
-
# Wait for the protobuf response
|
|
201
|
-
timeout = opts[:timeout] || 60
|
|
202
|
-
|
|
203
|
-
nats = ::Protobuf::Nats.client_nats_connection
|
|
204
|
-
|
|
205
|
-
# Publish to server
|
|
206
|
-
with_subscription do |sub_inbox|
|
|
207
|
-
begin
|
|
208
|
-
completed_request = false
|
|
209
|
-
|
|
210
|
-
if !sub_inbox.subscription.is_active # replace the subscription if is has been pooled but is no longer valid (maybe a reconnect)
|
|
211
|
-
nats.unsubscribe(sub_inbox.subscription)
|
|
212
|
-
sub_inbox.swap(new_subscription_inbox) # this line replaces the sub_inbox in the connection pool if necessary
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
nats.publish(subject, data, sub_inbox.inbox)
|
|
216
|
-
|
|
217
|
-
# Wait for reply
|
|
218
|
-
first_message = nats.next_message(sub_inbox.subscription, ack_timeout)
|
|
219
|
-
return :ack_timeout if first_message.nil?
|
|
220
|
-
|
|
221
|
-
first_message_data = first_message.data
|
|
222
|
-
return :nack if first_message_data == ::Protobuf::Nats::Messages::NACK
|
|
217
|
+
def formatted_service_and_method_name
|
|
218
|
+
klass = @options[:service]
|
|
219
|
+
method_name = @options[:method]
|
|
220
|
+
"#{klass}##{method_name}"
|
|
221
|
+
end
|
|
223
222
|
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
226
228
|
|
|
227
|
-
|
|
228
|
-
response = case ::Protobuf::Nats::Messages::ACK
|
|
229
|
-
when first_message_data then second_message_data
|
|
230
|
-
when second_message_data then first_message_data
|
|
231
|
-
else return :ack_timeout
|
|
232
|
-
end
|
|
229
|
+
nats = Protobuf::Nats.client_nats_connection
|
|
233
230
|
|
|
234
|
-
|
|
231
|
+
# Publish message with the reply topic pointed at the response muxer.
|
|
232
|
+
req = RESPONSE_MUXER.new_request
|
|
233
|
+
req.publish(subject, data)
|
|
235
234
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
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
|
|
245
241
|
end
|
|
246
242
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def nats_request_with_two_responses(subject, data, opts)
|
|
250
|
-
nats = Protobuf::Nats.client_nats_connection
|
|
251
|
-
inbox = nats.new_inbox
|
|
252
|
-
lock = ::Monitor.new
|
|
253
|
-
received = lock.new_cond
|
|
254
|
-
messages = []
|
|
255
|
-
first_message = nil
|
|
256
|
-
second_message = nil
|
|
257
|
-
response = nil
|
|
258
|
-
|
|
259
|
-
sid = nats.subscribe(inbox, :max => 2) do |message, _, _|
|
|
260
|
-
lock.synchronize do
|
|
261
|
-
messages << message
|
|
262
|
-
received.signal
|
|
263
|
-
end
|
|
264
|
-
end
|
|
243
|
+
# Check for a NACK
|
|
244
|
+
return :nack if first_message.data == ::Protobuf::Nats::Messages::NACK
|
|
265
245
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
received.wait(ack_timeout) if messages.empty?
|
|
273
|
-
first_message = messages.shift
|
|
274
|
-
|
|
275
|
-
return :ack_timeout if first_message.nil?
|
|
276
|
-
return :nack if first_message == ::Protobuf::Nats::Messages::NACK
|
|
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
|
|
277
252
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
received.wait(timeout) if messages.empty?
|
|
281
|
-
second_message = messages.shift
|
|
282
|
-
end
|
|
253
|
+
# NOTE: This might be nil, so be careful checking the data value
|
|
254
|
+
second_message_data = second_message&.data
|
|
283
255
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
289
262
|
|
|
290
|
-
|
|
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
|
|
291
269
|
|
|
292
|
-
|
|
293
|
-
ensure
|
|
294
|
-
# Ensure we don't leave a subscription sitting around.
|
|
295
|
-
nats.unsubscribe(sid) if response.nil?
|
|
296
|
-
end
|
|
270
|
+
fail(::Protobuf::Nats::Errors::ResponseTimeout, formatted_service_and_method_name) unless response
|
|
297
271
|
|
|
272
|
+
response
|
|
273
|
+
ensure
|
|
274
|
+
# cleanup the token from the request map
|
|
275
|
+
req.cleanup if req
|
|
298
276
|
end
|
|
299
277
|
|
|
300
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,8 +40,14 @@ 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
|
-
|
|
43
|
+
if ::File.exist?(absolute_config_path)
|
|
44
|
+
yaml_string = ::ERB.new(::File.read(absolute_config_path)).result
|
|
45
|
+
# Psych 4 and newer requires unsafe_load_file in order for aliases to be used
|
|
46
|
+
yaml_config = if ::YAML.respond_to?(:unsafe_load_file)
|
|
47
|
+
::YAML.unsafe_load(yaml_string)[env]
|
|
48
|
+
else
|
|
49
|
+
::YAML.load(yaml_string)[env]
|
|
50
|
+
end
|
|
44
51
|
end
|
|
45
52
|
|
|
46
53
|
DEFAULTS.each_pair do |key, value|
|
data/lib/protobuf/nats/errors.rb
CHANGED
|
@@ -1,32 +1,22 @@
|
|
|
1
1
|
module Protobuf
|
|
2
2
|
module Nats
|
|
3
3
|
module Errors
|
|
4
|
-
class
|
|
4
|
+
class ClientError < ::StandardError
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
class RequestTimeout <
|
|
7
|
+
class RequestTimeout < ClientError
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
class ResponseTimeout <
|
|
10
|
+
class ResponseTimeout < ClientError
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class ResponseMuxer < ClientError
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
class
|
|
16
|
+
class MriIOException < ::StandardError
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
java.lang.IllegalStateException
|
|
21
|
-
else
|
|
22
|
-
MriIllegalStateException
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
IOException = if defined? JRUBY_VERSION
|
|
26
|
-
java.io.IOException
|
|
27
|
-
else
|
|
28
|
-
MriIOException
|
|
29
|
-
end
|
|
19
|
+
IOException = MriIOException
|
|
30
20
|
end
|
|
31
21
|
end
|
|
32
22
|
end
|