waterdrop 2.6.14 → 2.7.0.alpha2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +3 -14
- data/CHANGELOG.md +80 -0
- data/Gemfile.lock +13 -16
- data/config/locales/errors.yml +0 -1
- data/docker-compose.yml +1 -1
- data/lib/waterdrop/config.rb +14 -11
- data/lib/waterdrop/contracts/config.rb +0 -1
- data/lib/waterdrop/producer/sync.rb +2 -2
- data/lib/waterdrop/producer/transactions.rb +2 -3
- data/lib/waterdrop/producer.rb +23 -18
- data/lib/waterdrop/version.rb +1 -1
- data/waterdrop.gemspec +3 -1
- data.tar.gz.sig +0 -0
- metadata +5 -5
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89426650b3e24dded080b6a0ebb1b9db7f23fd289ced679fcba3527f8c06a06b
|
4
|
+
data.tar.gz: 8163dc40307dae99c2409047a237d58bf1b83923d9d2739de3db4aa7839e5c86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 716da4bb23487cf445dd2f43d3b7a6a69046e87d22c39beb5612f2667130efaed2e8a7b5c49a98f85df3c4cc7673065c87023e0d0b90618f869863dc6b015818
|
7
|
+
data.tar.gz: 1c9f6a97e3675570db5f4e2c258e82286d5ec2417ad172f9bc9d1698a2b88564446f2d5c412cee2205b2e91ca9b08144d210a7d0014e7609aa9b38002cff47ae
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/.github/workflows/ci.yml
CHANGED
@@ -22,7 +22,6 @@ jobs:
|
|
22
22
|
- '3.2'
|
23
23
|
- '3.1'
|
24
24
|
- '3.0'
|
25
|
-
- '2.7'
|
26
25
|
include:
|
27
26
|
- ruby: '3.3'
|
28
27
|
coverage: 'true'
|
@@ -49,25 +48,15 @@ jobs:
|
|
49
48
|
|
50
49
|
- name: Install latest bundler
|
51
50
|
run: |
|
52
|
-
|
53
|
-
|
54
|
-
gem update --system 3.4.22 --no-document
|
55
|
-
else
|
56
|
-
gem install bundler --no-document
|
57
|
-
gem update --system --no-document
|
58
|
-
fi
|
51
|
+
gem install bundler --no-document
|
52
|
+
gem update --system --no-document
|
59
53
|
|
60
54
|
bundle config set without 'tools benchmarks docs'
|
61
55
|
|
62
56
|
- name: Bundle install
|
63
57
|
run: |
|
64
58
|
bundle config set without development
|
65
|
-
|
66
|
-
if [[ "$(ruby -v | awk '{print $2}')" == 2.7.8* ]]; then
|
67
|
-
BUNDLER_VERSION=2.4.22 bundle install --jobs 4 --retry 3
|
68
|
-
else
|
69
|
-
bundle install --jobs 4 --retry 3
|
70
|
-
fi
|
59
|
+
bundle install --jobs 4 --retry 3
|
71
60
|
|
72
61
|
- name: Run all tests
|
73
62
|
env:
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,85 @@
|
|
1
1
|
# WaterDrop changelog
|
2
2
|
|
3
|
+
## 2.7.0 (Unreleased)
|
4
|
+
|
5
|
+
This release contains **BREAKING** changes. Make sure to read and apply upgrade notes.
|
6
|
+
|
7
|
+
- **[Breaking]** Drop Ruby `2.7` support.
|
8
|
+
- **[Breaking]** Change default timeouts so final delivery `message.timeout.ms` is less that `max_wait_time` so we do not end up with not final verdict.
|
9
|
+
- **[Breaking]** Update all the time related configuration settings to be in `ms` and not mixed.
|
10
|
+
- **[Breaking]** Remove no longer needed `wait_timeout` configuration option.
|
11
|
+
- [Enhancement] Introduce `instrument_on_wait_queue_full` flag (defaults to `true`) to be able to configure whether non critical (retryable) queue full errors should be instrumented in the error pipeline. Useful when building high-performance pipes with WaterDrop queue retry backoff as a throttler.
|
12
|
+
|
13
|
+
### Upgrade Notes
|
14
|
+
|
15
|
+
**PLEASE MAKE SURE TO READ AND APPLY THEM!**
|
16
|
+
|
17
|
+
#### Time Settings Format Alignment
|
18
|
+
|
19
|
+
**All** time-related values are now configured in milliseconds instead of some being in seconds and some in milliseconds.
|
20
|
+
|
21
|
+
The values that were changed from seconds to milliseconds are:
|
22
|
+
|
23
|
+
- `max_wait_timeout`
|
24
|
+
- `wait_timeout`
|
25
|
+
- `wait_backoff_on_queue_full`
|
26
|
+
- `wait_timeout_on_queue_full`
|
27
|
+
- `wait_backoff_on_transaction_command, default`
|
28
|
+
|
29
|
+
If you have configured any of those yourself, please replace the seconds representation with milliseconds:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
producer = WaterDrop::Producer.new
|
33
|
+
|
34
|
+
producer.setup do |config|
|
35
|
+
config.deliver = true
|
36
|
+
|
37
|
+
# Replace this:
|
38
|
+
config.wait_timeout = 30
|
39
|
+
|
40
|
+
# With
|
41
|
+
config.wait_timeout = 30_000
|
42
|
+
# ...
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
#### Defaults Alignment
|
47
|
+
|
48
|
+
In this release, we've updated our default settings to address a crucial issue: previous defaults could lead to inconclusive outcomes in synchronous operations due to wait timeout errors. Users often mistakenly believed that a message dispatch was halted because of these errors when, in fact, the timeout was related to awaiting the final dispatch verdict, not the dispatch action itself.
|
49
|
+
|
50
|
+
The new defaults in WaterDrop 2.7.0 eliminate this confusion by ensuring synchronous operation results are always transparent and conclusive. This change aims to provide a straightforward understanding of wait timeout errors, reinforcing that they reflect the wait state, not the dispatch success.
|
51
|
+
|
52
|
+
Below, you can find a table with what has changed, the new defaults, and the current ones in case you want to retain the previous behavior:
|
53
|
+
|
54
|
+
<table>
|
55
|
+
<thead>
|
56
|
+
<tr>
|
57
|
+
<th>Config</th>
|
58
|
+
<th>Previous Default</th>
|
59
|
+
<th>New Default</th>
|
60
|
+
</tr>
|
61
|
+
</thead>
|
62
|
+
<tbody>
|
63
|
+
<tr>
|
64
|
+
<td>root <code>max_wait_timeout</code></td>
|
65
|
+
<td>5000 ms (5 seconds)</td>
|
66
|
+
<td>60000 ms (60 seconds)</td>
|
67
|
+
</tr>
|
68
|
+
<tr>
|
69
|
+
<td>kafka <code>message.timeout.ms</code></td>
|
70
|
+
<td>300000 ms (5 minutes)</td>
|
71
|
+
<td>55000 ms (55 seconds)</td>
|
72
|
+
</tr>
|
73
|
+
<tr>
|
74
|
+
<td>kafka <code>transaction.timeout.ms</code></td>
|
75
|
+
<td>60000 ms (1 minute)</td>
|
76
|
+
<td>45000 ms (45 seconds)</td>
|
77
|
+
</tr>
|
78
|
+
</tbody>
|
79
|
+
</table>
|
80
|
+
|
81
|
+
This alignment ensures that when using sync operations or invoking `#wait`, any exception you get should give you a conclusive and final delivery verdict.
|
82
|
+
|
3
83
|
## 2.6.14 (2024-02-06)
|
4
84
|
- [Enhancement] Instrument `producer.connected` and `producer.closing` lifecycle events.
|
5
85
|
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
waterdrop (2.
|
5
|
-
karafka-core (>= 2.
|
4
|
+
waterdrop (2.7.0.alpha2)
|
5
|
+
karafka-core (>= 2.4.0.alpha1, < 3.0.0)
|
6
6
|
zeitwerk (~> 2.3)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activesupport (7.1.3)
|
11
|
+
activesupport (7.1.3.2)
|
12
12
|
base64
|
13
13
|
bigdecimal
|
14
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
@@ -25,22 +25,20 @@ GEM
|
|
25
25
|
connection_pool (2.4.1)
|
26
26
|
diff-lcs (1.5.1)
|
27
27
|
docile (1.4.0)
|
28
|
-
drb (2.2.
|
29
|
-
|
30
|
-
factory_bot (6.4.5)
|
28
|
+
drb (2.2.1)
|
29
|
+
factory_bot (6.4.6)
|
31
30
|
activesupport (>= 5.0.0)
|
32
31
|
ffi (1.16.3)
|
33
|
-
i18n (1.14.
|
32
|
+
i18n (1.14.4)
|
34
33
|
concurrent-ruby (~> 1.0)
|
35
|
-
karafka-core (2.
|
36
|
-
|
37
|
-
|
38
|
-
karafka-rdkafka (0.14.7)
|
34
|
+
karafka-core (2.4.0.alpha1)
|
35
|
+
karafka-rdkafka (>= 0.15.0.alpha1, < 0.16.0)
|
36
|
+
karafka-rdkafka (0.15.0.alpha1)
|
39
37
|
ffi (~> 1.15)
|
40
38
|
mini_portile2 (~> 2.6)
|
41
39
|
rake (> 12)
|
42
40
|
mini_portile2 (2.8.5)
|
43
|
-
minitest (5.
|
41
|
+
minitest (5.22.2)
|
44
42
|
mutex_m (0.2.0)
|
45
43
|
rake (13.1.0)
|
46
44
|
rspec (3.13.0)
|
@@ -55,8 +53,7 @@ GEM
|
|
55
53
|
rspec-mocks (3.13.0)
|
56
54
|
diff-lcs (>= 1.2.0, < 2.0)
|
57
55
|
rspec-support (~> 3.13.0)
|
58
|
-
rspec-support (3.13.
|
59
|
-
ruby2_keywords (0.0.5)
|
56
|
+
rspec-support (3.13.1)
|
60
57
|
simplecov (0.22.0)
|
61
58
|
docile (~> 1.1)
|
62
59
|
simplecov-html (~> 0.11)
|
@@ -65,10 +62,10 @@ GEM
|
|
65
62
|
simplecov_json_formatter (0.1.4)
|
66
63
|
tzinfo (2.0.6)
|
67
64
|
concurrent-ruby (~> 1.0)
|
68
|
-
zeitwerk (2.6.
|
65
|
+
zeitwerk (2.6.13)
|
69
66
|
|
70
67
|
PLATFORMS
|
71
|
-
|
68
|
+
arm64-darwin-22
|
72
69
|
x86_64-linux
|
73
70
|
|
74
71
|
DEPENDENCIES
|
data/config/locales/errors.yml
CHANGED
@@ -6,7 +6,6 @@ en:
|
|
6
6
|
deliver_format: must be boolean
|
7
7
|
id_format: must be a non-empty string
|
8
8
|
max_payload_size_format: must be an integer that is equal or bigger than 1
|
9
|
-
wait_timeout_format: must be a numeric that is bigger than 0
|
10
9
|
max_wait_timeout_format: must be an integer that is equal or bigger than 0
|
11
10
|
kafka_format: must be a hash with symbol based keys
|
12
11
|
kafka_key_must_be_a_symbol: All keys under the kafka settings scope need to be symbols
|
data/docker-compose.yml
CHANGED
data/lib/waterdrop/config.rb
CHANGED
@@ -12,7 +12,12 @@ module WaterDrop
|
|
12
12
|
'client.id': 'waterdrop',
|
13
13
|
# emit librdkafka statistics every five seconds. This is used in instrumentation.
|
14
14
|
# When disabled, part of metrics will not be published and available.
|
15
|
-
'statistics.interval.ms': 5_000
|
15
|
+
'statistics.interval.ms': 5_000,
|
16
|
+
# We set it to a value that is lower than `max_wait_time` to have a final verdict upon sync
|
17
|
+
# delivery
|
18
|
+
'message.timeout.ms': 55_000,
|
19
|
+
# Must be less or equal to `message.timeout.ms` defaults
|
20
|
+
'transaction.timeout.ms': 45_000
|
16
21
|
}.freeze
|
17
22
|
|
18
23
|
private_constant :KAFKA_DEFAULTS
|
@@ -44,12 +49,8 @@ module WaterDrop
|
|
44
49
|
# option [Integer] max payload size allowed for delivery to Kafka
|
45
50
|
setting :max_payload_size, default: 1_000_012
|
46
51
|
# option [Integer] Wait that long for the delivery report or raise an error if this takes
|
47
|
-
# longer than the timeout.
|
48
|
-
setting :max_wait_timeout, default:
|
49
|
-
# option [Numeric] how long should we wait between re-checks on the availability of the
|
50
|
-
# delivery report. In a really robust systems, this describes the min-delivery time
|
51
|
-
# for a single sync message when produced in isolation
|
52
|
-
setting :wait_timeout, default: 0.005 # 5 milliseconds
|
52
|
+
# longer than the timeout ms.
|
53
|
+
setting :max_wait_timeout, default: 60_000
|
53
54
|
# option [Boolean] should we upon detecting full librdkafka queue backoff and retry or should
|
54
55
|
# we raise an exception.
|
55
56
|
# When this is set to `true`, upon full queue, we won't raise an error. There will be error
|
@@ -60,12 +61,14 @@ module WaterDrop
|
|
60
61
|
# option [Integer] how long (in seconds) should we backoff before a retry when queue is full
|
61
62
|
# The retry will happen with the same message and backoff should give us some time to
|
62
63
|
# dispatch previously buffered messages.
|
63
|
-
setting :wait_backoff_on_queue_full, default:
|
64
|
-
# option [Numeric] how many
|
64
|
+
setting :wait_backoff_on_queue_full, default: 100
|
65
|
+
# option [Numeric] how many ms should we wait with the backoff on queue having space for
|
65
66
|
# more messages before re-raising the error.
|
66
|
-
setting :wait_timeout_on_queue_full, default:
|
67
|
+
setting :wait_timeout_on_queue_full, default: 10_000
|
68
|
+
# option [Boolean] should we instrument non-critical, retryable queue full errors
|
69
|
+
setting :instrument_on_wait_queue_full, default: true
|
67
70
|
# option [Numeric] How long to wait before retrying a retryable transaction related error
|
68
|
-
setting :wait_backoff_on_transaction_command, default:
|
71
|
+
setting :wait_backoff_on_transaction_command, default: 500
|
69
72
|
# option [Numeric] How many times to retry a retryable transaction related error before
|
70
73
|
# giving up
|
71
74
|
setting :max_attempts_on_transaction_command, default: 5
|
@@ -17,7 +17,6 @@ module WaterDrop
|
|
17
17
|
required(:deliver) { |val| [true, false].include?(val) }
|
18
18
|
required(:max_payload_size) { |val| val.is_a?(Integer) && val >= 1 }
|
19
19
|
required(:max_wait_timeout) { |val| val.is_a?(Numeric) && val >= 0 }
|
20
|
-
required(:wait_timeout) { |val| val.is_a?(Numeric) && val.positive? }
|
21
20
|
required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
|
22
21
|
required(:wait_on_queue_full) { |val| [true, false].include?(val) }
|
23
22
|
required(:wait_backoff_on_queue_full) { |val| val.is_a?(Numeric) && val >= 0 }
|
@@ -52,8 +52,8 @@ module WaterDrop
|
|
52
52
|
# @return [Array<Rdkafka::Producer::DeliveryReport>] delivery reports
|
53
53
|
#
|
54
54
|
# @raise [Rdkafka::RdkafkaError] When adding the messages to rdkafka's queue failed
|
55
|
-
# @raise [Rdkafka::Producer::WaitTimeoutError] When the timeout has been reached and
|
56
|
-
#
|
55
|
+
# @raise [Rdkafka::Producer::WaitTimeoutError] When the timeout has been reached and some
|
56
|
+
# handles are still pending
|
57
57
|
# @raise [Errors::MessageInvalidError] When any of the provided messages details are invalid
|
58
58
|
# and the message could not be sent to Kafka
|
59
59
|
def produce_many_sync(messages)
|
@@ -132,8 +132,7 @@ module WaterDrop
|
|
132
132
|
client.send_offsets_to_transaction(
|
133
133
|
consumer,
|
134
134
|
tpl,
|
135
|
-
|
136
|
-
@config.max_wait_timeout * 1_000
|
135
|
+
@config.max_wait_timeout
|
137
136
|
)
|
138
137
|
end
|
139
138
|
end
|
@@ -197,7 +196,7 @@ module WaterDrop
|
|
197
196
|
|
198
197
|
if do_retry
|
199
198
|
# Backoff more and more before retries
|
200
|
-
sleep(config.wait_backoff_on_transaction_command * attempt)
|
199
|
+
sleep((config.wait_backoff_on_transaction_command / 1_000.0) * attempt)
|
201
200
|
|
202
201
|
retry
|
203
202
|
end
|
data/lib/waterdrop/producer.rb
CHANGED
@@ -188,8 +188,7 @@ module WaterDrop
|
|
188
188
|
# The linger.ms time will be ignored for the duration of the call,
|
189
189
|
# queued messages will be sent to the broker as soon as possible.
|
190
190
|
begin
|
191
|
-
|
192
|
-
@client.flush(@config.max_wait_timeout * 1_000) unless @client.closed?
|
191
|
+
@client.flush(@config.max_wait_timeout) unless @client.closed?
|
193
192
|
# We can safely ignore timeouts here because any left outstanding requests
|
194
193
|
# will anyhow force wait on close if not forced.
|
195
194
|
# If forced, we will purge the queue and just close
|
@@ -250,8 +249,8 @@ module WaterDrop
|
|
250
249
|
# @param handler [Rdkafka::Producer::DeliveryHandle]
|
251
250
|
def wait(handler)
|
252
251
|
handler.wait(
|
253
|
-
max_wait_timeout
|
254
|
-
|
252
|
+
# rdkafka max_wait_timeout is in seconds and we use ms
|
253
|
+
max_wait_timeout: @config.max_wait_timeout / 1_000.0
|
255
254
|
)
|
256
255
|
end
|
257
256
|
|
@@ -286,7 +285,7 @@ module WaterDrop
|
|
286
285
|
# If we're running for longer than the timeout, we need to re-raise the queue full.
|
287
286
|
# This will prevent from situation where cluster is down forever and we just retry and retry
|
288
287
|
# in an infinite loop, effectively hanging the processing
|
289
|
-
raise unless monotonic_now - produce_time < @config.wait_timeout_on_queue_full
|
288
|
+
raise unless monotonic_now - produce_time < @config.wait_timeout_on_queue_full
|
290
289
|
|
291
290
|
label = caller_locations(2, 1)[0].label.split(' ').last
|
292
291
|
|
@@ -297,22 +296,28 @@ module WaterDrop
|
|
297
296
|
begin
|
298
297
|
raise Errors::ProduceError, e.inspect
|
299
298
|
rescue Errors::ProduceError => e
|
300
|
-
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
299
|
+
# Users can configure this because in pipe-like flows with high throughput, queue full with
|
300
|
+
# retry may be used as a throttling system that will backoff and wait.
|
301
|
+
# In such scenarios this error notification can be removed and until queue full is
|
302
|
+
# retryable, it will not be raised as an error.
|
303
|
+
if @config.instrument_on_wait_queue_full
|
304
|
+
# We want to instrument on this event even when we restart it.
|
305
|
+
# The reason is simple: instrumentation and visibility.
|
306
|
+
# We can recover from this, but despite that we should be able to instrument this.
|
307
|
+
# If this type of event happens too often, it may indicate that the buffer settings are
|
308
|
+
# not well configured.
|
309
|
+
@monitor.instrument(
|
310
|
+
'error.occurred',
|
311
|
+
producer_id: id,
|
312
|
+
message: message,
|
313
|
+
error: e,
|
314
|
+
type: "message.#{label}"
|
315
|
+
)
|
316
|
+
end
|
312
317
|
|
313
318
|
# We do not poll the producer because polling happens in a background thread
|
314
319
|
# It also should not be a frequent case (queue full), hence it's ok to just throttle.
|
315
|
-
sleep @config.wait_backoff_on_queue_full
|
320
|
+
sleep @config.wait_backoff_on_queue_full / 1_000.0
|
316
321
|
end
|
317
322
|
|
318
323
|
@operations_in_progress.decrement
|
data/lib/waterdrop/version.rb
CHANGED
data/waterdrop.gemspec
CHANGED
@@ -16,9 +16,11 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.description = spec.summary
|
17
17
|
spec.license = 'MIT'
|
18
18
|
|
19
|
-
spec.add_dependency 'karafka-core', '>= 2.
|
19
|
+
spec.add_dependency 'karafka-core', '>= 2.4.0.alpha1', '< 3.0.0'
|
20
20
|
spec.add_dependency 'zeitwerk', '~> 2.3'
|
21
21
|
|
22
|
+
spec.required_ruby_version = '>= 3.0.0'
|
23
|
+
|
22
24
|
if $PROGRAM_NAME.end_with?('gem')
|
23
25
|
spec.signing_key = File.expand_path('~/.ssh/gem-private_key.pem')
|
24
26
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waterdrop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.0.alpha2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Mensfeld
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
AnG1dJU+yL2BK7vaVytLTstJME5mepSZ46qqIJXMuWob/YPDmVaBF39TDSG9e34s
|
36
36
|
msG3BiCqgOgHAnL23+CN3Rt8MsuRfEtoTKpJVcCfoEoNHOkc
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2024-
|
38
|
+
date: 2024-03-17 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: karafka-core
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 2.
|
46
|
+
version: 2.4.0.alpha1
|
47
47
|
- - "<"
|
48
48
|
- !ruby/object:Gem::Version
|
49
49
|
version: 3.0.0
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
requirements:
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: 2.
|
56
|
+
version: 2.4.0.alpha1
|
57
57
|
- - "<"
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: 3.0.0
|
@@ -144,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
144
|
requirements:
|
145
145
|
- - ">="
|
146
146
|
- !ruby/object:Gem::Version
|
147
|
-
version:
|
147
|
+
version: 3.0.0
|
148
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
150
|
- - ">="
|
metadata.gz.sig
CHANGED
Binary file
|