sequel-replica-failover 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d6591f26230c08ccc58cecb7b05646362451251
4
- data.tar.gz: e708c054f4068721c8478a6e0e25cc8da79621ed
3
+ metadata.gz: a4e500a26ca783691cec34ed9d48921bf2261348
4
+ data.tar.gz: 19ad51a3a2bcbfb02dea42034ccb902ceeeb6c77
5
5
  SHA512:
6
- metadata.gz: 35432ccbcda3fa45526c08b308234f755335a912a0d5d80215be7d60c822a97af66e46871c3eea23319c0210d772df80329c49075ae8612b178f548df17690ff
7
- data.tar.gz: 3d022a85dc1f8deaea5683bf28fd193c35ed47b5037526750f7363bd2eb976a5db25d628a1884cb316f7edfcf2df22f9034699976e225cfd0d8d3a8743ffee59
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
 
@@ -1,5 +1,5 @@
1
1
  module Sequel
2
2
  module ReplicaFailover
3
- VERSION = '1.0.1'
3
+ VERSION = '2.0.0'
4
4
  end
5
5
  end
@@ -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 = opts[:pool_retry_count] || 5
8
+ @pool_retry_count = opts[:pool_retry_count] || 5
9
+ @failing_over = false
9
10
  end
10
11
 
11
- @on_disconnect = []
12
- @on_unstick = []
12
+ @on_retry = []
13
+ @on_reset = []
13
14
 
14
15
  class << self
15
- attr_accessor :on_disconnect, :on_unstick
16
+ attr_accessor :on_retry, :on_reset
16
17
 
17
- def register_on_disconnect_callback(callback)
18
- @on_disconnect << callback
18
+ def register_on_retry_callback(callback)
19
+ @on_retry << callback
19
20
  end
20
21
 
21
- def clear_on_disconnect_callbacks
22
- @on_disconnect.clear
22
+ def clear_on_retry_callbacks
23
+ @on_retry.clear
23
24
  end
24
25
 
25
- def register_on_unstick_callback(callback)
26
- @on_unstick << callback
26
+ def register_on_reset_callback(callback)
27
+ @on_reset << callback
27
28
  end
28
29
 
29
- def clear_on_unstick_callbacks
30
- @on_unstick.clear
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 == :read_only &&
39
- @stuck_at &&
40
- Time.now.to_i - @stuck_at.to_i >= @pool_stick_timeout
41
- unstick(:read_only)
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
- server = pick_server(server)
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 unstick(server)
71
- unless self.class.on_unstick.empty?
72
- self.class.on_unstick.each { |callback| callback.call(self) }
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
- @stuck_at = nil
78
- @stuck_times = nil
83
+ @failing_over = false
84
+ @failed_at = nil
85
+ @retry_count = nil
79
86
  end
80
87
 
81
88
  private
82
89
 
83
- def stick
84
- @stuck_times ||= 0
85
- probe(@stuck_times) { |p| p.stick }
86
- @stuck_at ||= Time.now
87
- @stuck_times += 1
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.clear_on_disconnect_callbacks
23
- Sequel::ShardedSingleFailoverConnectionPool.clear_on_unstick_callbacks
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 on_disconnect callback' do
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.register_on_disconnect_callback callback
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 '#unstick' do
96
- it 'calls on_unstick callbacks' do
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.register_on_unstick_callback callback
99
- connection_pool.unstick(:read_only)
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 '.register_on_disconnect_callback' do
105
- it 'adds to the on_disconnect attribute' do
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.register_on_disconnect_callback callback
108
- expect(Sequel::ShardedSingleFailoverConnectionPool.on_disconnect).to eq([callback])
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 '.register_on_disconnect_callback' do
113
- it 'adds to the on_unstick attribute' do
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.register_on_unstick_callback callback
116
- expect(Sequel::ShardedSingleFailoverConnectionPool.on_unstick).to eq([callback])
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-replica-failover
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Henry