protobuf-nats 0.13.1 → 0.13.2.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/protobuf/nats/response_muxer.rb +34 -6
- data/lib/protobuf/nats/super_subscription_manager.rb +38 -14
- data/lib/protobuf/nats/version.rb +1 -1
- metadata +1 -3
- data/.github/workflows/ci.yml +0 -64
- data/.travis.yml +0 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89549ac1d866b16970644740b1da683f985b150db9c03ed5a06d5c20eb81d77b
|
|
4
|
+
data.tar.gz: 3908346e349fb6e8c95ba13e89b5fa31872dc38be7a6103ad5bd327a19806062
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78886bd247470672da944c13f90b538e6913bf5378677bb3da5dc0ac48ee3f501e582a40d6501c8ae0b41ef66dc6368626a79d852a17929ca6739acdc27bd378
|
|
7
|
+
data.tar.gz: ba529ee4a32f3c4360d22bbd52a8bb04a175b97684fe8c5ca8b8e65b5a11d6f3a04fceaf5415872b951cc373f284146b76422bbe7eff467fa52c264af878f47d
|
|
@@ -13,6 +13,16 @@ module Protobuf
|
|
|
13
13
|
MAX_RESPONSES_PER_TOKEN = 10
|
|
14
14
|
TOKEN_TTL_SECONDS = 600 # 10 minutes
|
|
15
15
|
|
|
16
|
+
# Sentinel pushed onto a token's queue to wake a waiter blocked in
|
|
17
|
+
# next_message. We cannot rely on Queue#close alone: on JRuby, close does
|
|
18
|
+
# NOT wake a pop() that is blocked with a timeout: -- neither the native
|
|
19
|
+
# Queue (Ruby >= 3.2 / JRuby 10) nor concurrent-ruby's RubyTimeoutQueue
|
|
20
|
+
# (Ruby < 3.2 / JRuby 9.4, whose timed pop only wakes on push) signals a
|
|
21
|
+
# timed waiter on close. CRuby's Queue#close does wake it, which is why
|
|
22
|
+
# this only ever bit JRuby. Pushing an explicit sentinel wakes the waiter
|
|
23
|
+
# immediately on every engine; next_message treats it as a timeout.
|
|
24
|
+
QUEUE_WAKE = ::Object.new
|
|
25
|
+
|
|
16
26
|
def initialize
|
|
17
27
|
# Per-token response queues for lock-free message delivery. @resp_map is a
|
|
18
28
|
# Concurrent::Map so request threads and dispatcher threads can insert,
|
|
@@ -66,9 +76,24 @@ module Protobuf
|
|
|
66
76
|
end
|
|
67
77
|
|
|
68
78
|
def cleanup(token)
|
|
69
|
-
# Atomic remove-and-return; close the queue to
|
|
79
|
+
# Atomic remove-and-return; wake+close the queue to release any waiter.
|
|
70
80
|
entry = @resp_map.delete(token)
|
|
71
|
-
entry[:queue]
|
|
81
|
+
wake_and_close_queue(entry[:queue]) if entry
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Wake any waiter blocked in next_message on this queue, then close it.
|
|
85
|
+
# Pushing QUEUE_WAKE is what actually wakes a timed pop on JRuby (see the
|
|
86
|
+
# QUEUE_WAKE comment); close alone is insufficient there. Safe to call on
|
|
87
|
+
# an already-closed queue.
|
|
88
|
+
def wake_and_close_queue(queue)
|
|
89
|
+
return unless queue
|
|
90
|
+
begin
|
|
91
|
+
queue.push(QUEUE_WAKE)
|
|
92
|
+
rescue ::ClosedQueueError, ::ThreadError
|
|
93
|
+
# Already closed by another path; a plain (untimed) waiter, if any,
|
|
94
|
+
# was already woken by that close. Nothing more to do.
|
|
95
|
+
end
|
|
96
|
+
queue.close
|
|
72
97
|
end
|
|
73
98
|
|
|
74
99
|
def next_message(token, timeout)
|
|
@@ -101,7 +126,10 @@ module Protobuf
|
|
|
101
126
|
# Queue.pop returns nil when:
|
|
102
127
|
# 1. The queue is closed
|
|
103
128
|
# 2. The timeout expires
|
|
104
|
-
|
|
129
|
+
# QUEUE_WAKE is the sentinel pushed by wake_and_close_queue to wake a
|
|
130
|
+
# timed pop on JRuby (where close alone does not); treat it as a
|
|
131
|
+
# timeout so the caller fails over instead of returning garbage.
|
|
132
|
+
if msg.nil? || msg.equal?(QUEUE_WAKE)
|
|
105
133
|
logger.warn "Queue closed or timeout for token #{token} during next_message"
|
|
106
134
|
raise ::NATS::Timeout
|
|
107
135
|
end
|
|
@@ -293,8 +321,8 @@ module Protobuf
|
|
|
293
321
|
next unless data
|
|
294
322
|
stale_count += 1
|
|
295
323
|
logger.warn "Cleaning up stale token #{token} created at #{data[:created_at]}"
|
|
296
|
-
#
|
|
297
|
-
data[:queue]
|
|
324
|
+
# Wake any waiting thread, then close the queue.
|
|
325
|
+
wake_and_close_queue(data[:queue])
|
|
298
326
|
end
|
|
299
327
|
|
|
300
328
|
if stale_count > 0
|
|
@@ -348,7 +376,7 @@ module Protobuf
|
|
|
348
376
|
# Must be called while holding LOCK (only from drop_subscription_locked).
|
|
349
377
|
def fail_inflight_requests
|
|
350
378
|
@resp_map.each_pair do |_token, entry|
|
|
351
|
-
entry[:queue]
|
|
379
|
+
wake_and_close_queue(entry[:queue])
|
|
352
380
|
end
|
|
353
381
|
end
|
|
354
382
|
|
|
@@ -89,14 +89,12 @@ module Protobuf
|
|
|
89
89
|
break
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
@pending_queue << msg
|
|
96
|
-
end
|
|
92
|
+
# Push with a deadline (see push_with_deadline: no Timeout.timeout,
|
|
93
|
+
# which corrupts the SizedQueue mutex on JRuby).
|
|
94
|
+
if push_with_deadline(msg, 1)
|
|
97
95
|
migrated_count += 1
|
|
98
96
|
logger.warn "Migrated message #{migrated_count} from old queue to central queue"
|
|
99
|
-
|
|
97
|
+
else
|
|
100
98
|
logger.error "Failed to migrate message to central queue (queue full), dropping message"
|
|
101
99
|
break
|
|
102
100
|
end
|
|
@@ -117,15 +115,15 @@ module Protobuf
|
|
|
117
115
|
|
|
118
116
|
# Wake every handler with its own poison pill.
|
|
119
117
|
handlers.size.times do
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
end
|
|
118
|
+
# Clear some space if the queue is full so the shutdown signal fits.
|
|
119
|
+
if @pending_queue.num_waiting.zero? && @pending_queue.size >= @pending_queue.max
|
|
120
|
+
logger.warn "Queue full during shutdown, clearing to make room for shutdown signal"
|
|
121
|
+
@pending_queue.clear rescue nil
|
|
122
|
+
end
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
# Push with a deadline (see push_with_deadline: no Timeout.timeout,
|
|
125
|
+
# which corrupts the SizedQueue mutex on JRuby).
|
|
126
|
+
unless push_with_deadline(:shutdown, 1)
|
|
129
127
|
logger.error "Failed to send shutdown signal (queue blocked); will force-kill remaining handlers"
|
|
130
128
|
break
|
|
131
129
|
end
|
|
@@ -178,6 +176,32 @@ module Protobuf
|
|
|
178
176
|
::Protobuf::Nats.monotonic_time
|
|
179
177
|
end
|
|
180
178
|
|
|
179
|
+
# Push onto the shared SizedQueue with a deadline, WITHOUT Timeout.timeout.
|
|
180
|
+
# Timeout uses an asynchronous Thread#raise, which is unsafe around the
|
|
181
|
+
# mutex SizedQueue#push takes internally: on JRuby (10.x in particular) a
|
|
182
|
+
# timeout firing mid-push unwinds through the held mutex and raises
|
|
183
|
+
# "ThreadError: Attempt to unlock a mutex which is locked by another
|
|
184
|
+
# thread/fiber" instead of Timeout::Error -- so the rescue :Timeout::Error
|
|
185
|
+
# never fires and shutdown/migration blow up. CRuby happens to unwind
|
|
186
|
+
# cleanly, which is why this only bit JRuby. Poll a non-blocking push
|
|
187
|
+
# against a monotonic deadline instead: no async raise, safe on every
|
|
188
|
+
# engine. Returns true if pushed, false if the deadline passed (queue
|
|
189
|
+
# still full) or the queue was closed.
|
|
190
|
+
def push_with_deadline(obj, timeout)
|
|
191
|
+
deadline = monotonic + timeout
|
|
192
|
+
loop do
|
|
193
|
+
begin
|
|
194
|
+
@pending_queue.push(obj, true) # non_block: raises ThreadError when full
|
|
195
|
+
return true
|
|
196
|
+
rescue ::ClosedQueueError
|
|
197
|
+
return false
|
|
198
|
+
rescue ::ThreadError
|
|
199
|
+
return false if monotonic >= deadline
|
|
200
|
+
sleep 0.01
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
181
205
|
# Spawn one intake handler. Each thread owns its own crash_count so the
|
|
182
206
|
# self-healing exponential backoff is correct under true parallelism (a
|
|
183
207
|
# shared counter would lose updates across handlers on JRuby). The counter
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: protobuf-nats
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.13.
|
|
4
|
+
version: 0.13.2.pre1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brandon Dewitt
|
|
@@ -183,9 +183,7 @@ extensions: []
|
|
|
183
183
|
extra_rdoc_files: []
|
|
184
184
|
files:
|
|
185
185
|
- ".circleci/config.yml"
|
|
186
|
-
- ".github/workflows/ci.yml"
|
|
187
186
|
- ".gitignore"
|
|
188
|
-
- ".travis.yml"
|
|
189
187
|
- CHANGELOG.md
|
|
190
188
|
- Gemfile
|
|
191
189
|
- LICENSE.txt
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
pull_request:
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
test:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
strategy:
|
|
12
|
-
fail-fast: false
|
|
13
|
-
matrix:
|
|
14
|
-
ruby:
|
|
15
|
-
- "3.1"
|
|
16
|
-
- "3.4"
|
|
17
|
-
- "jruby-9.4"
|
|
18
|
-
- "jruby-10.0"
|
|
19
|
-
|
|
20
|
-
# Real NATS server for the integration specs (spec/integration/); they
|
|
21
|
-
# auto-detect it on localhost:4222 (see spec/spec_helper.rb).
|
|
22
|
-
services:
|
|
23
|
-
nats:
|
|
24
|
-
image: nats:2.14-linux
|
|
25
|
-
ports:
|
|
26
|
-
- 4222:4222
|
|
27
|
-
|
|
28
|
-
env:
|
|
29
|
-
JRUBY_OPTS: "-J-Xmx1024m"
|
|
30
|
-
RAILS_ENV: test
|
|
31
|
-
|
|
32
|
-
steps:
|
|
33
|
-
- uses: actions/checkout@v4
|
|
34
|
-
|
|
35
|
-
- uses: ruby/setup-ruby@v1
|
|
36
|
-
with:
|
|
37
|
-
ruby-version: ${{ matrix.ruby }}
|
|
38
|
-
bundler-cache: true
|
|
39
|
-
|
|
40
|
-
# The cluster failover spec (spec/integration/failover_spec.rb) spawns
|
|
41
|
-
# its own two-node cluster, so it needs the nats-server binary itself --
|
|
42
|
-
# the service container above only covers the single-node specs.
|
|
43
|
-
- name: Install nats-server binary (cluster failover spec)
|
|
44
|
-
run: |
|
|
45
|
-
NATS_VERSION=v2.14.3
|
|
46
|
-
curl -sfL "https://github.com/nats-io/nats-server/releases/download/${NATS_VERSION}/nats-server-${NATS_VERSION}-linux-amd64.tar.gz" | tar xz
|
|
47
|
-
sudo mv "nats-server-${NATS_VERSION}-linux-amd64/nats-server" /usr/local/bin/
|
|
48
|
-
nats-server --version
|
|
49
|
-
|
|
50
|
-
- name: Wait for NATS
|
|
51
|
-
run: |
|
|
52
|
-
echo "Waiting for NATS to start..."
|
|
53
|
-
for i in $(seq 1 30); do
|
|
54
|
-
if nc -z localhost 4222; then
|
|
55
|
-
echo "NATS is ready!"
|
|
56
|
-
exit 0
|
|
57
|
-
fi
|
|
58
|
-
sleep 1
|
|
59
|
-
done
|
|
60
|
-
echo "NATS did not become ready" >&2
|
|
61
|
-
exit 1
|
|
62
|
-
|
|
63
|
-
- name: Run tests
|
|
64
|
-
run: bundle exec rspec
|
data/.travis.yml
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
sudo: false
|
|
2
|
-
language: ruby
|
|
3
|
-
jdk:
|
|
4
|
-
- openjdk8
|
|
5
|
-
rvm:
|
|
6
|
-
- 2.3.0
|
|
7
|
-
- 2.7.0
|
|
8
|
-
- jruby-9.1.7.0
|
|
9
|
-
- jruby-9.2.13.0
|
|
10
|
-
before_install:
|
|
11
|
-
# Install and start gnatsd
|
|
12
|
-
- ./scripts/install_gnatsd.sh
|
|
13
|
-
- $HOME/nats-server/nats-server &
|
|
14
|
-
# Install deps for project
|
|
15
|
-
- gem install bundler
|
|
16
|
-
- gem update --system
|