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: 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