sequel-replica-failover 1.0.1 → 2.0.0
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4e500a26ca783691cec34ed9d48921bf2261348
|
4
|
+
data.tar.gz: 19ad51a3a2bcbfb02dea42034ccb902ceeeb6c77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8653ca08e58c4c55de95b139932d084eb4090c03f262261cd4753c964234afecb13cf5ab4149fe2b09519ee0d0c9834dcf8d43e44a40ce2670dc8364165158ba
|
7
|
+
data.tar.gz: 2ef5acbe7cc1d8c060ca0c1923203740b66b5f36b75fe220da0ad17ea4436dc99317ca08d2a86c3a38c53c21334e025196bf0a124ec845a1b1b097d1fb798d56
|
data/README.md
CHANGED
@@ -48,7 +48,34 @@ DB = Sequel.connect({
|
|
48
48
|
})
|
49
49
|
```
|
50
50
|
|
51
|
+
Please note that if you were to open a transaction on a `:read_only` connection,
|
52
|
+
queries could be retried on a different connection. Why one would open a transaction
|
53
|
+
on a `:read_only` connection is unclear to us, but it is possible and a concern to
|
54
|
+
be aware of.
|
51
55
|
|
56
|
+
## Callbacks
|
57
|
+
|
58
|
+
The connection pool allows for user-defined callbacks when a query is retried as well
|
59
|
+
as when the retry logic is reset (on timeout or on retry count). Callbacks should be
|
60
|
+
a `Proc`, or something that ducktypes to one.
|
61
|
+
|
62
|
+
Callbacks to retry take an `error` object and a `pool`, as follows:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_retry_callback ->(error, pool) {
|
66
|
+
ReportingTool.notify(error)
|
67
|
+
puts pool.inspect
|
68
|
+
puts pool.failing_over?
|
69
|
+
}
|
70
|
+
```
|
71
|
+
|
72
|
+
Callbacks on reset take a `pool` object, as follows:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_reset_callback ->(pool) {
|
76
|
+
puts pool.inspect
|
77
|
+
}
|
78
|
+
```
|
52
79
|
|
53
80
|
## Contributing
|
54
81
|
|
@@ -5,29 +5,30 @@ class Sequel::ShardedSingleFailoverConnectionPool < Sequel::ShardedSingleConnect
|
|
5
5
|
def initialize(db, opts = OPTS)
|
6
6
|
super
|
7
7
|
@pool_stick_timeout = opts[:pool_stick_timeout] || 15
|
8
|
-
@pool_retry_count
|
8
|
+
@pool_retry_count = opts[:pool_retry_count] || 5
|
9
|
+
@failing_over = false
|
9
10
|
end
|
10
11
|
|
11
|
-
@
|
12
|
-
@
|
12
|
+
@on_retry = []
|
13
|
+
@on_reset = []
|
13
14
|
|
14
15
|
class << self
|
15
|
-
attr_accessor :
|
16
|
+
attr_accessor :on_retry, :on_reset
|
16
17
|
|
17
|
-
def
|
18
|
-
@
|
18
|
+
def register_on_retry_callback(callback)
|
19
|
+
@on_retry << callback
|
19
20
|
end
|
20
21
|
|
21
|
-
def
|
22
|
-
@
|
22
|
+
def clear_on_retry_callbacks
|
23
|
+
@on_retry.clear
|
23
24
|
end
|
24
25
|
|
25
|
-
def
|
26
|
-
@
|
26
|
+
def register_on_reset_callback(callback)
|
27
|
+
@on_reset << callback
|
27
28
|
end
|
28
29
|
|
29
|
-
def
|
30
|
-
@
|
30
|
+
def clear_on_reset_callbacks
|
31
|
+
@on_reset.clear
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
@@ -35,56 +36,69 @@ class Sequel::ShardedSingleFailoverConnectionPool < Sequel::ShardedSingleConnect
|
|
35
36
|
# Yields the connection to the supplied block for the given server.
|
36
37
|
# This method simulates the ConnectionPool#hold API.
|
37
38
|
def hold(server=:default, &block)
|
38
|
-
if server
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
reset_retries(:read_only) if failover_timed_out?(server)
|
40
|
+
|
41
|
+
loop do
|
42
|
+
begin
|
43
|
+
@response = super(server, &block)
|
44
|
+
break
|
45
|
+
rescue Sequel::DatabaseDisconnectError, Sequel::DatabaseConnectionError => e
|
46
|
+
raise if server != :read_only
|
47
|
+
|
48
|
+
increment_retries
|
49
|
+
|
50
|
+
unless self.class.on_retry.empty?
|
51
|
+
self.class.on_retry.each { |callback| callback.call(e, self) }
|
52
|
+
end
|
53
|
+
|
54
|
+
if @retry_count >= @pool_retry_count
|
55
|
+
reset_retries(server)
|
56
|
+
raise
|
57
|
+
end
|
58
|
+
end
|
42
59
|
end
|
43
60
|
|
44
|
-
|
45
|
-
block.call(@conns[server] ||= make_new(server))
|
46
|
-
rescue Sequel::DatabaseDisconnectError, Sequel::DatabaseConnectionError => e
|
47
|
-
raise if server != :read_only
|
48
|
-
raise if @db.in_transaction?(server: :read_only)
|
49
|
-
|
50
|
-
unless self.class.on_disconnect.empty?
|
51
|
-
self.class.on_disconnect.each { |callback| callback.call(e, self) }
|
52
|
-
end
|
53
|
-
disconnect_server(server)
|
54
|
-
@conns[server] = nil
|
55
|
-
|
56
|
-
stick
|
57
|
-
|
58
|
-
if @stuck_times >= @pool_retry_count
|
59
|
-
unstick(server)
|
60
|
-
raise
|
61
|
-
end
|
62
|
-
|
63
|
-
hold(server, &block)
|
61
|
+
@response
|
64
62
|
end
|
65
63
|
|
66
64
|
def pool_type
|
67
65
|
:sharded_single_failover
|
68
66
|
end
|
69
67
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
68
|
+
def failover!
|
69
|
+
@failing_over = true
|
70
|
+
end
|
71
|
+
|
72
|
+
def failing_over?
|
73
|
+
!!@failing_over
|
74
|
+
end
|
75
|
+
|
76
|
+
def reset_retries(server)
|
77
|
+
unless self.class.on_reset.empty?
|
78
|
+
self.class.on_reset.each { |callback| callback.call(self) }
|
73
79
|
end
|
74
80
|
probe(server.to_s) { |p| p.unstick }
|
75
81
|
disconnect_server(server)
|
76
82
|
@conns[server] = nil
|
77
|
-
@
|
78
|
-
@
|
83
|
+
@failing_over = false
|
84
|
+
@failed_at = nil
|
85
|
+
@retry_count = nil
|
79
86
|
end
|
80
87
|
|
81
88
|
private
|
82
89
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
90
|
+
def failover_timed_out?(server)
|
91
|
+
server == :read_only &&
|
92
|
+
@failed_at &&
|
93
|
+
Time.now.to_i - @failed_at.to_i >= @pool_stick_timeout
|
94
|
+
end
|
95
|
+
|
96
|
+
def increment_retries
|
97
|
+
failover!
|
98
|
+
@retry_count ||= 0
|
99
|
+
probe(@retry_count) { |p| p.stick }
|
100
|
+
@failed_at ||= Time.now
|
101
|
+
@retry_count += 1
|
88
102
|
end
|
89
103
|
|
90
104
|
def probe(*args)
|
@@ -19,8 +19,8 @@ describe Sequel::ShardedSingleFailoverConnectionPool do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
after do
|
22
|
-
Sequel::ShardedSingleFailoverConnectionPool.
|
23
|
-
Sequel::ShardedSingleFailoverConnectionPool.
|
22
|
+
Sequel::ShardedSingleFailoverConnectionPool.clear_on_retry_callbacks
|
23
|
+
Sequel::ShardedSingleFailoverConnectionPool.clear_on_reset_callbacks
|
24
24
|
end
|
25
25
|
|
26
26
|
let(:msp) { proc { @max_size=3 } }
|
@@ -29,6 +29,13 @@ describe Sequel::ShardedSingleFailoverConnectionPool do
|
|
29
29
|
describe '#hold' do
|
30
30
|
context 'with read_only server' do
|
31
31
|
context 'when block raises a database connection error' do
|
32
|
+
it 'fails over' do
|
33
|
+
call_count = 0
|
34
|
+
allow(connection_pool).to receive(:failover!)
|
35
|
+
connection_pool.hold(:read_only) { call_count += 1; raise Sequel::DatabaseDisconnectError if call_count == 1 }
|
36
|
+
expect(connection_pool).to have_received(:failover!)
|
37
|
+
end
|
38
|
+
|
32
39
|
it 'retries until the a successful connection is made' do
|
33
40
|
call_count = 0
|
34
41
|
expect {
|
@@ -59,10 +66,10 @@ describe Sequel::ShardedSingleFailoverConnectionPool do
|
|
59
66
|
connection_pool.hold(:read_only) {}
|
60
67
|
end
|
61
68
|
|
62
|
-
context 'with an
|
69
|
+
context 'with an on_retry callback' do
|
63
70
|
it 'calls the callback with the error' do
|
64
71
|
callback = double("callback")
|
65
|
-
Sequel::ShardedSingleFailoverConnectionPool.
|
72
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_retry_callback callback
|
66
73
|
|
67
74
|
expect(callback).to receive(:call).with(an_instance_of(Sequel::DatabaseDisconnectError), connection_pool)
|
68
75
|
call_count = 0
|
@@ -92,28 +99,47 @@ describe Sequel::ShardedSingleFailoverConnectionPool do
|
|
92
99
|
end
|
93
100
|
end
|
94
101
|
|
95
|
-
describe '#
|
96
|
-
it '
|
102
|
+
describe '#failover!' do
|
103
|
+
it 'changes failing_over? to true' do
|
104
|
+
expect {
|
105
|
+
connection_pool.failover!
|
106
|
+
}.to change {
|
107
|
+
connection_pool.failing_over?
|
108
|
+
}.from(false).to(true)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#reset_retries' do
|
113
|
+
it 'calls on_reset callbacks' do
|
97
114
|
callback = double(call: true)
|
98
|
-
Sequel::ShardedSingleFailoverConnectionPool.
|
99
|
-
connection_pool.
|
115
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_reset_callback callback
|
116
|
+
connection_pool.reset_retries(:read_only)
|
100
117
|
expect(callback).to have_received(:call).with(connection_pool)
|
101
118
|
end
|
119
|
+
|
120
|
+
it 'changes failing_over? to false' do
|
121
|
+
connection_pool.failover!
|
122
|
+
expect {
|
123
|
+
connection_pool.reset_retries(:read_only)
|
124
|
+
}.to change {
|
125
|
+
connection_pool.failing_over?
|
126
|
+
}.from(true).to(false)
|
127
|
+
end
|
102
128
|
end
|
103
129
|
|
104
|
-
describe '.
|
105
|
-
it 'adds to the
|
130
|
+
describe '.register_on_retry_callback' do
|
131
|
+
it 'adds to the on_retry attribute' do
|
106
132
|
callback = Proc.new{ puts "woo" }
|
107
|
-
Sequel::ShardedSingleFailoverConnectionPool.
|
108
|
-
expect(Sequel::ShardedSingleFailoverConnectionPool.
|
133
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_retry_callback callback
|
134
|
+
expect(Sequel::ShardedSingleFailoverConnectionPool.on_retry).to eq([callback])
|
109
135
|
end
|
110
136
|
end
|
111
137
|
|
112
|
-
describe '.
|
113
|
-
it 'adds to the
|
138
|
+
describe '.register_on_retry_callback' do
|
139
|
+
it 'adds to the on_reset attribute' do
|
114
140
|
callback = Proc.new{ puts "woo" }
|
115
|
-
Sequel::ShardedSingleFailoverConnectionPool.
|
116
|
-
expect(Sequel::ShardedSingleFailoverConnectionPool.
|
141
|
+
Sequel::ShardedSingleFailoverConnectionPool.register_on_reset_callback callback
|
142
|
+
expect(Sequel::ShardedSingleFailoverConnectionPool.on_reset).to eq([callback])
|
117
143
|
end
|
118
144
|
end
|
119
145
|
end
|