r4r 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +110 -0
- data/.gitignore +12 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +21 -0
- data/README.md +15 -0
- data/Rakefile +38 -0
- data/bin/console +14 -0
- data/bin/ghpages +19 -0
- data/bin/rake +2 -0
- data/bin/setup +9 -0
- data/ext/r4r/ring_bits_ext/extconf.rb +7 -0
- data/ext/r4r/ring_bits_ext/ring_bits_ext.c +173 -0
- data/ext/r4r/system_clock_ext/extconf.rb +8 -0
- data/ext/r4r/system_clock_ext/system_clock_ext.c +31 -0
- data/lib/r4r.rb +17 -0
- data/lib/r4r/clock.rb +40 -0
- data/lib/r4r/retry.rb +136 -0
- data/lib/r4r/retry_budget.rb +150 -0
- data/lib/r4r/retry_policy.rb +55 -0
- data/lib/r4r/ring_bits.rb +59 -0
- data/lib/r4r/token_bucket.rb +129 -0
- data/lib/r4r/version.rb +3 -0
- data/lib/r4r/windowed_adder.rb +106 -0
- data/r4r.gemspec +43 -0
- metadata +170 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <sys/time.h>
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Returns current system time in milliseconds.
|
6
|
+
*/
|
7
|
+
static VALUE
|
8
|
+
system_clock_call(VALUE self) {
|
9
|
+
struct timeval tv;
|
10
|
+
long long millis;
|
11
|
+
|
12
|
+
gettimeofday(&tv, NULL);
|
13
|
+
|
14
|
+
millis = ((long long)tv.tv_sec) * 1000;
|
15
|
+
millis = millis + (tv.tv_usec / 1000);
|
16
|
+
|
17
|
+
return LONG2NUM(millis);
|
18
|
+
}
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Module entry point.
|
22
|
+
*/
|
23
|
+
void
|
24
|
+
Init_system_clock_ext() {
|
25
|
+
VALUE mR4r;
|
26
|
+
VALUE cSystemClock;
|
27
|
+
|
28
|
+
mR4r = rb_define_module("R4r");
|
29
|
+
cSystemClock = rb_define_class_under(mR4r, "SystemClockExt", rb_cObject);
|
30
|
+
rb_define_method(cSystemClock, "call", system_clock_call, 0);
|
31
|
+
}
|
data/lib/r4r.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "r4r/version"
|
2
|
+
|
3
|
+
require "r4r/system_clock_ext"
|
4
|
+
require "r4r/clock"
|
5
|
+
|
6
|
+
require "r4r/ring_bits_ext"
|
7
|
+
require "r4r/ring_bits"
|
8
|
+
|
9
|
+
require "r4r/windowed_adder"
|
10
|
+
require "r4r/token_bucket"
|
11
|
+
|
12
|
+
require "r4r/retry_budget"
|
13
|
+
require "r4r/retry_policy"
|
14
|
+
require "r4r/retry"
|
15
|
+
|
16
|
+
module R4r
|
17
|
+
end
|
data/lib/r4r/clock.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module R4r
|
2
|
+
# A system clock
|
3
|
+
#
|
4
|
+
# @abstract
|
5
|
+
class Clock
|
6
|
+
# Returns current system time in milliseconds
|
7
|
+
def call
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# A frozen clock for testing
|
13
|
+
class FrozenClock < Clock
|
14
|
+
# Creates a new instance of frozen clock.
|
15
|
+
#
|
16
|
+
# @param [R4r::Clock] parent an initial time clock
|
17
|
+
def initialize(parent: nil)
|
18
|
+
@time = (parent || R4r.clock).call
|
19
|
+
end
|
20
|
+
|
21
|
+
# @see R4r::Clock#call
|
22
|
+
def call
|
23
|
+
@time
|
24
|
+
end
|
25
|
+
|
26
|
+
# Increase clock time by given seconds.
|
27
|
+
#
|
28
|
+
# @param [Fixnum] seconds a number of seconds to increase time
|
29
|
+
def advance(seconds:)
|
30
|
+
@time += (seconds.to_i * 1_000)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@@clock = R4r::SystemClockExt.new
|
35
|
+
|
36
|
+
# Default {R4r::Clock} instance.
|
37
|
+
def self.clock
|
38
|
+
@@clock
|
39
|
+
end
|
40
|
+
end
|
data/lib/r4r/retry.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
module R4r
|
2
|
+
|
3
|
+
# An error raises when retry was failed.
|
4
|
+
#
|
5
|
+
class NonRetriableError < RuntimeError
|
6
|
+
attr_reader :cause
|
7
|
+
|
8
|
+
# @param [String] message an error message
|
9
|
+
# @param [Exception] cause a error cause
|
10
|
+
def initialize(message:, cause:)
|
11
|
+
super(message)
|
12
|
+
@cause = cause
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Decorator that wrap blocks and call it within retries.
|
17
|
+
#
|
18
|
+
# @attr [Array[Float]] backoff
|
19
|
+
# @attr [R4r::RetryBudget] budget
|
20
|
+
#
|
21
|
+
# @example constant backoff, it will never pause between retries and will try 3 times.
|
22
|
+
# retry = R4r::Retry.constant_backoff(num_retries: 3)
|
23
|
+
# retry.call { get_http_request }
|
24
|
+
#
|
25
|
+
# @example exponential backoff, it will pause between invocations using given backoff invtervals and will try 4 times
|
26
|
+
# retry = R4r::Retry.backoff(backoff: [0.1, 0.3, 1, 5])
|
27
|
+
# retry.call { get_http_request }
|
28
|
+
#
|
29
|
+
class Retry
|
30
|
+
|
31
|
+
attr_reader :backoff
|
32
|
+
attr_reader :budget
|
33
|
+
|
34
|
+
# Creates a new retries dectorator.
|
35
|
+
#
|
36
|
+
# @param [Array[Float]] backoff an array with backoff intervals (in seconds)
|
37
|
+
# @param [R4r::RetryPolicy] policy a policy used for error filtiring
|
38
|
+
# @param [R4r::RetryBudget] budget a retry budget
|
39
|
+
#
|
40
|
+
# @raise [ArgumentError] when backoff is empty
|
41
|
+
# @raise [ArgumentError] when backoff has negative values
|
42
|
+
def initialize(backoff:, policy: nil, budget: nil)
|
43
|
+
@policy = (policy || R4r::RetryPolicy.always)
|
44
|
+
@backoff = Array.new(backoff).map { |i| i.to_f }
|
45
|
+
@budget = budget != nil ? budget : R4r::RetryBudget.create
|
46
|
+
|
47
|
+
raise ArgumentError, "backoff cannot be empty" if @backoff.empty?
|
48
|
+
raise ArgumentError, "backoff values cannot be negative" unless @backoff.all? {|i| i.to_f >= 0.0 }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Decorates a given block within retries.
|
52
|
+
#
|
53
|
+
# @return [Proc]
|
54
|
+
def decorate(&block)
|
55
|
+
->() { call { yield } }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Calls given block within retries.
|
59
|
+
#
|
60
|
+
# @raise [NonRetriableError]
|
61
|
+
def call(&block)
|
62
|
+
return unless block_given?
|
63
|
+
|
64
|
+
num_retry = 0
|
65
|
+
@budget.deposit
|
66
|
+
|
67
|
+
while num_retry < @backoff.size
|
68
|
+
|
69
|
+
begin
|
70
|
+
return yield(num_retry)
|
71
|
+
rescue => err
|
72
|
+
raise err if err.is_a?(NonRetriableError)
|
73
|
+
|
74
|
+
if (num_retry + 1 == @backoff.size)
|
75
|
+
raise NonRetriableError.new(message: "Retry limit [#{@backoff.size}] reached: #{err}", cause: err)
|
76
|
+
end
|
77
|
+
|
78
|
+
unless @policy.call(error: err, num_retry: num_retry)
|
79
|
+
raise NonRetriableError.new(message: "An error was rejected by policy: #{err}", cause: err)
|
80
|
+
end
|
81
|
+
|
82
|
+
unless @budget.try_withdraw
|
83
|
+
raise NonRetriableError.new(message: "Budget was exhausted: #{err}", cause: err)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
sleep @backoff[num_retry]
|
88
|
+
num_retry += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Creates a {R4r::Retry} with fixed backoff rates.
|
93
|
+
#
|
94
|
+
# @param [R4r::RetryPolicy] policy a policy used for error filtiring
|
95
|
+
# @param [R4r::RetryBudget] budget a retry budget
|
96
|
+
# @return [R4r::Retry]
|
97
|
+
#
|
98
|
+
# @raise [ArgumentError] when num_retries is negative
|
99
|
+
# @raise [ArgumentError] when backoff is negative
|
100
|
+
#
|
101
|
+
# @example without sleep between invocations
|
102
|
+
# R4r::Retry.constant_backoff(num_retries:3)
|
103
|
+
#
|
104
|
+
# @example with sleep 1s between invocations
|
105
|
+
# R4r::Retry.constant_backoff(num_retries: 3, backoff: 1)
|
106
|
+
def self.constant_backoff(num_retries:, backoff: 0.0, policy: nil, budget: nil)
|
107
|
+
raise ArgumentError, "num_retries cannot be negative" unless num_retries.to_i >= 0
|
108
|
+
raise ArgumentError, "backoff cannot be negative" unless backoff.to_f >= 0.0
|
109
|
+
|
110
|
+
backoff = Array.new(num_retries.to_i) { backoff.to_f }
|
111
|
+
R4r::Retry.new(backoff: backoff, policy: policy, budget: budget)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Creates a {R4r::Retry} with backoff intervals.
|
115
|
+
#
|
116
|
+
# @param [Array[Float]] backoff a list of sleep intervals (in seconds)
|
117
|
+
# @param [R4r::RetryPolicy] policy a policy used for error filtiring
|
118
|
+
# @param [R4r::RetryBudget] budget a retry budget
|
119
|
+
# @return [R4r::Retry]
|
120
|
+
#
|
121
|
+
# @raise [ArgumentError] when backoff is nil
|
122
|
+
# @raise [ArgumentError] when backoff isn't array
|
123
|
+
# @raise [ArgumentError] when backoff has negative values
|
124
|
+
#
|
125
|
+
# @example exponential backoff between invocations
|
126
|
+
# R4r::Retry.backoff(backoff: [0.1, 0.5, 1, 3])
|
127
|
+
def self.backoff(backoff:, policy: nil, budget: nil)
|
128
|
+
raise ArgumentError, "backoff cannot be nil" if backoff.nil?
|
129
|
+
raise ArgumentError, "backoff must be an array" unless backoff.is_a?(Array)
|
130
|
+
raise ArgumentError, "backoff values cannot be negative" unless backoff.all? {|i| i.to_f >= 0.0 }
|
131
|
+
|
132
|
+
R4r::Retry.new(backoff: backoff, policy: policy, budget: budget)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module R4r
|
2
|
+
# Represents a budget for retrying requests.
|
3
|
+
#
|
4
|
+
# A retry budget is useful for attenuating the amplifying effects
|
5
|
+
# of many clients within a process retrying requests multiple
|
6
|
+
# times. This acts as a form of coordination between those retries.
|
7
|
+
#
|
8
|
+
# A Ruby port of the finagle's RetryBudget.
|
9
|
+
#
|
10
|
+
# @abstract
|
11
|
+
# @see https://github.com/twitter/finagle/blob/master/finagle-core/src/main/scala/com/twitter/finagle/service/RetryBudget.scala
|
12
|
+
class RetryBudget
|
13
|
+
|
14
|
+
# Indicates a deposit, or credit, which will typically
|
15
|
+
# permit future withdrawals.
|
16
|
+
def deposit
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
# Check whether or not there is enough balance remaining
|
21
|
+
# to issue a retry, or make a withdrawal.
|
22
|
+
#
|
23
|
+
# @return [Boolean]
|
24
|
+
# `true`, if the retry is allowed and a withdrawal will take place.
|
25
|
+
# `false`, the balance should remain untouched.
|
26
|
+
def try_withdraw
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
# The balance or number of retries that can be made now.
|
31
|
+
def balance
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates a {R4r::RetryBudget} that allows for about `percent_can_retry` percent
|
36
|
+
# of the total {R4r::RetryBudget#deposit} requests to be retried.
|
37
|
+
#
|
38
|
+
# @param [Fixnum] ttl_ms deposits created by {R4r::RetryBudget#deposit} expire after
|
39
|
+
# approximately `ttl_ms` time has passed. Must be `>= 1 second`
|
40
|
+
# and `<= 60 seconds`.
|
41
|
+
# @param [Fixnum] min_retries_per_second the minimum rate of retries allowed in order to
|
42
|
+
# accommodate clients that have just started issuing requests as well as clients
|
43
|
+
# that do not issue many requests per window.
|
44
|
+
# Must be non-negative and if `0`, then no reserve is given.
|
45
|
+
# @param [Float] percent_can_retry the percentage of calls to `deposit()` that can be
|
46
|
+
# retried. This is in addition to any retries allowed for via `min_retries_per_second`.
|
47
|
+
# Must be >= 0 and <= 1000. As an example, if `0.1` is used, then for every
|
48
|
+
# 10 calls to `deposit()`, 1 retry will be allowed. If `2.0` is used then every
|
49
|
+
# `deposit` allows for 2 retries.
|
50
|
+
# @param [R4r::Clock] clock the current time for testing
|
51
|
+
#
|
52
|
+
# @raise [ArgumentError]
|
53
|
+
def self.create(ttl_ms: nil, min_retries_per_second: nil, percent_can_retry: nil, clock: nil)
|
54
|
+
ttl_ms = (ttl_ms || R4r::TokenRetryBudget::DEFAULT_TTL_MS).to_i
|
55
|
+
min_retries_per_second = (min_retries_per_second || 10).to_i
|
56
|
+
percent_can_retry = (percent_can_retry.to_f || 0.2).to_f
|
57
|
+
|
58
|
+
unless ttl_ms >= 0 && ttl_ms <= 60 * 1000
|
59
|
+
raise ArgumentError, "ttl_ms must be in [1.second, 60.seconds], got #{ttl_ms}"
|
60
|
+
end
|
61
|
+
unless min_retries_per_second >= 0
|
62
|
+
raise ArgumentError, "min_retries_per_second cannot be nagative, got #{min_retries_per_second}"
|
63
|
+
end
|
64
|
+
unless percent_can_retry >= 0.0
|
65
|
+
raise ArgumentError, "percent_can_retry cannot be negative, got #{percent_can_retry}"
|
66
|
+
end
|
67
|
+
unless percent_can_retry <= R4r::TokenRetryBudget::SCALE_FACTOR
|
68
|
+
raise ArgumentError, "percent_can_retry cannot be greater then #{R4r::TokenRetryBudget::SCALE_FACTOR}, got #{percent_can_retry}"
|
69
|
+
end
|
70
|
+
|
71
|
+
if min_retries_per_second == 0 && percent_can_retry == 0.0
|
72
|
+
return R4r::RetryBudget.empty
|
73
|
+
end
|
74
|
+
|
75
|
+
deposit_amount = percent_can_retry == 0.0 ? 0 : R4r::TokenRetryBudget::SCALE_FACTOR.to_i
|
76
|
+
withdrawal_amount = percent_can_retry == 0.0 ? 1 : (R4r::TokenRetryBudget::SCALE_FACTOR / percent_can_retry).to_i
|
77
|
+
reserve = min_retries_per_second * (ttl_ms / 1000) * withdrawal_amount
|
78
|
+
bucket = R4r::LeakyTokenBucket.new(ttl_ms: ttl_ms, reserve: reserve, clock: clock)
|
79
|
+
R4r::TokenRetryBudget.new(bucket: bucket, deposit_amount: deposit_amount, withdrawal_amount: withdrawal_amount)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Creates an empty retry budget.
|
83
|
+
def self.empty
|
84
|
+
EmptyRetryBudget.new
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates an infinite retry budget.
|
88
|
+
def self.infinite
|
89
|
+
InfiniteRetryBudget.new
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# A {R4r::RetryBudget} that never has a balance,
|
94
|
+
# and as such, will never allow a retry.
|
95
|
+
class EmptyRetryBudget < RetryBudget
|
96
|
+
def deposit ; end
|
97
|
+
def try_withdraw ; false ; end
|
98
|
+
def balance ; 0 ; end
|
99
|
+
end
|
100
|
+
|
101
|
+
# A {R4r::RetryBudget} that always has a balance of `100`,
|
102
|
+
# and as such, will always allow a retry.
|
103
|
+
class InfiniteRetryBudget < RetryBudget
|
104
|
+
def deposit ; end
|
105
|
+
def try_withdraw ; true end
|
106
|
+
def balance ; 100 end
|
107
|
+
end
|
108
|
+
|
109
|
+
class TokenRetryBudget < RetryBudget
|
110
|
+
# This scaling factor allows for `percent_can_retry` > 1 without
|
111
|
+
# having to use floating points (as the underlying mechanism
|
112
|
+
# here is a {R4r::TokenBucket} which is not floating point based).
|
113
|
+
SCALE_FACTOR = 1000.0
|
114
|
+
DEFAULT_TTL_MS = 60 * 1000
|
115
|
+
|
116
|
+
# Creates a new {R4r::TokenRetryBudget}.
|
117
|
+
#
|
118
|
+
# @param [R4r::TokenBucket] bucket
|
119
|
+
# @param [Fixnum] deposit_amount
|
120
|
+
# @param [Fixnum] withdrawal_amount
|
121
|
+
def initialize(bucket:, deposit_amount:, withdrawal_amount:)
|
122
|
+
raise ArgumentError, "bucket cannot be nil" if bucket.nil?
|
123
|
+
raise ArgumentError, "deposit_amount cannot be nil" if deposit_amount.nil?
|
124
|
+
raise ArgumentError, "withdrawal_amount cannot be nil" if withdrawal_amount.nil?
|
125
|
+
|
126
|
+
@bucket = bucket
|
127
|
+
@deposit_amount = deposit_amount.to_i
|
128
|
+
@withdrawal_amount = withdrawal_amount.to_i
|
129
|
+
end
|
130
|
+
|
131
|
+
# @see R4r::RetryBudget#deposit
|
132
|
+
def deposit
|
133
|
+
@bucket.put(@deposit_amount)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @see R4r::RetryBudget#try_withdraw
|
137
|
+
def try_withdraw
|
138
|
+
@bucket.try_get(@withdrawal_amount)
|
139
|
+
end
|
140
|
+
|
141
|
+
# @see R4r::RetryBudget#balance
|
142
|
+
def balance
|
143
|
+
@bucket.count / @withdrawal_amount
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_s
|
147
|
+
"R4r::TokenRetryBudget{deposit=#{@deposit_amount}, withdrawal=#{@withdrawal_amount}, balance=#{balance}}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module R4r
|
2
|
+
|
3
|
+
# Pluggable retry strategy.
|
4
|
+
#
|
5
|
+
# @abstract
|
6
|
+
class RetryPolicy
|
7
|
+
# Check that given error can be retried or not.
|
8
|
+
#
|
9
|
+
# @param [Exception] error an error was occured
|
10
|
+
# @param [Fixnum] num_retry a number of current retry, started from zero
|
11
|
+
#
|
12
|
+
# @return [Boolean] true if retry can be recovered
|
13
|
+
def call(error:, num_retry:)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a policy that always recover from any kind of errors.
|
18
|
+
#
|
19
|
+
# @return [R4r::RetryPolicy]
|
20
|
+
def self.always
|
21
|
+
->(error:, num_retry:) { true }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Creates a policy that never recover from any kind of errors.
|
25
|
+
#
|
26
|
+
# @return [R4r::RetryPolicy]
|
27
|
+
def self.never
|
28
|
+
->(error:, num_retry:) { false }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Creates a policy that recover from specified kind of errors
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# R4r::RetryPolicy.instance_of(Some::Error, Service::Error)
|
35
|
+
#
|
36
|
+
# @return [R4r::RetryPolicy]
|
37
|
+
def self.instance_of(*klass)
|
38
|
+
R4r::InstanceOfRetryPolicy.new(klass: klass)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# A retry policy that catch specified kind of errors
|
43
|
+
class InstanceOfRetryPolicy < RetryPolicy
|
44
|
+
# @param [Array[Class]] klass an error classes list that used for filtering
|
45
|
+
def initialize(klass:)
|
46
|
+
@klass = klass
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Boolean]
|
50
|
+
# @see R4r::RetryPolicy#call
|
51
|
+
def call(error:, num_retry:)
|
52
|
+
@klass.any? { |kind| error.is_a?(kind) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module R4r ; class RingBits
|
2
|
+
attr_reader :index, :length, :cardinality
|
3
|
+
|
4
|
+
# Creates a ring bit set whose size is large enough to explicitly
|
5
|
+
# represent bits with indices in the range 0 through
|
6
|
+
# size-1. All bits are initially set to false.
|
7
|
+
#
|
8
|
+
# @param [Fixnum] size the size of ring bits buffer
|
9
|
+
# @raise [ArgumentError] if the specified size is negitive
|
10
|
+
def initialize(size:, bit_set_class: nil)
|
11
|
+
@size = size
|
12
|
+
@bit_set = (bit_set_class || RingBitsExt).new(size.to_i)
|
13
|
+
@is_full = false
|
14
|
+
@index = -1
|
15
|
+
@length = 0
|
16
|
+
@cardinality = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
# Current ring bits buffer size.
|
20
|
+
def size
|
21
|
+
@size
|
22
|
+
end
|
23
|
+
|
24
|
+
# An actual ring bits buffer capacity.
|
25
|
+
def bit_set_size
|
26
|
+
@bit_set.size
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets the bit at the next index to the specified value.
|
30
|
+
#
|
31
|
+
# @param [Boolean] value is a boolean value to set
|
32
|
+
# @return [Fixnum] the number of bits set to true
|
33
|
+
def set_next(value)
|
34
|
+
increase_length
|
35
|
+
|
36
|
+
new_index = (@index + 1) % @size
|
37
|
+
previous = @bit_set.set(new_index, value == true) ? 1 : 0
|
38
|
+
current = value == true ? 1 : 0
|
39
|
+
|
40
|
+
|
41
|
+
@index = new_index
|
42
|
+
@cardinality = @cardinality - previous + current
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def increase_length
|
48
|
+
return if @is_full
|
49
|
+
|
50
|
+
next_length = @length + 1
|
51
|
+
if (next_length < @size)
|
52
|
+
@length = next_length
|
53
|
+
else
|
54
|
+
@length = size
|
55
|
+
@is_full = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end ; end
|