waterdrop 2.6.14 → 2.7.0.alpha2
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
- 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
|