activerecord-mysql-reconnect-before-retry 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.travis.yml +45 -0
- data/Appraisals +24 -0
- data/ChangeLog +31 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/Rakefile +5 -0
- data/activerecord-mysql-reconnect.gemspec +28 -0
- data/docker-compose.yml +6 -0
- data/gemfiles/activerecord_4.2.gemfile +8 -0
- data/gemfiles/activerecord_5.0.gemfile +7 -0
- data/gemfiles/activerecord_5.1.gemfile +7 -0
- data/gemfiles/activerecord_5.2.gemfile +7 -0
- data/gemfiles/activerecord_6.0.gemfile +7 -0
- data/gemfiles/activerecord_master.gemfile +7 -0
- data/lib/activerecord-mysql-reconnect.rb +5 -0
- data/lib/activerecord/mysql/reconnect.rb +282 -0
- data/lib/activerecord/mysql/reconnect/abstract_mysql_adapter_ext.rb +46 -0
- data/lib/activerecord/mysql/reconnect/base_ext.rb +41 -0
- data/lib/activerecord/mysql/reconnect/connection_pool_ext.rb +18 -0
- data/lib/activerecord/mysql/reconnect/mysql2_adapter_ext.rb +13 -0
- data/lib/activerecord/mysql/reconnect/null_transaction_ext.rb +9 -0
- data/lib/activerecord/mysql/reconnect/version.rb +7 -0
- data/spec/activerecord-mysql-reconnect_spec.rb +545 -0
- data/spec/data.sql +1001 -0
- data/spec/employee_model.rb +1 -0
- data/spec/mysql2_ext.rb +5 -0
- data/spec/mysql_helper.rb +84 -0
- data/spec/spec_helper.rb +54 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e74e264eb347ad48e9f32999a598468ff82ef673beeeceb007dc16774faeece4
|
4
|
+
data.tar.gz: 8cb24217590520df18e4cb630a3fd410f086f72d59ea8c138a55f8b984105fc2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e660f5073b7863071bd6722392957b863e2f6df9e301c64b12f78bd349802d799a92f76cf909f29aee7182f3fc355c5171b7c3ef56596254228bbea0f2aef231
|
7
|
+
data.tar.gz: 2aeadc0cb5148069ab8b61f367d5793c0f2879e0c9b5880908f56cbfb65bc34d3d0b26ebb0cc6cee0bedf05bd3b3ff11ede24aa74667d3650ffcea97b7afade5
|
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
test.rb
|
19
|
+
*.bak
|
20
|
+
*.swp
|
21
|
+
/gemfiles/*.lock
|
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
dist: trusty
|
2
|
+
sudo: required
|
3
|
+
language: ruby
|
4
|
+
rvm:
|
5
|
+
- 2.3.8
|
6
|
+
- 2.4.6
|
7
|
+
- 2.5.5
|
8
|
+
- 2.6.3
|
9
|
+
- 2.7.0
|
10
|
+
before_install:
|
11
|
+
- docker-compose up -d
|
12
|
+
- function mysql_ping { mysqladmin -u root -h 127.0.0.1 -ppassword ping > /dev/null 2> /dev/null; }
|
13
|
+
- for i in {1..60}; do mysql_ping && break; sleep 1; done
|
14
|
+
- gem install bundler
|
15
|
+
- docker-compose stop
|
16
|
+
script:
|
17
|
+
- bundle exec rake
|
18
|
+
gemfile:
|
19
|
+
- gemfiles/activerecord_4.2.gemfile
|
20
|
+
- gemfiles/activerecord_5.0.gemfile
|
21
|
+
- gemfiles/activerecord_5.1.gemfile
|
22
|
+
- gemfiles/activerecord_5.2.gemfile
|
23
|
+
- gemfiles/activerecord_6.0.gemfile
|
24
|
+
- gemfiles/activerecord_master.gemfile
|
25
|
+
env:
|
26
|
+
matrix:
|
27
|
+
- ACTIVERECORD_MYSQL_RECONNECT_ENGINE=InnoDB
|
28
|
+
- ACTIVERECORD_MYSQL_RECONNECT_ENGINE=MyISAM
|
29
|
+
jobs:
|
30
|
+
exclude:
|
31
|
+
- rvm: 2.3.8
|
32
|
+
gemfile: gemfiles/activerecord_6.0.gemfile
|
33
|
+
- rvm: 2.3.8
|
34
|
+
gemfile: gemfiles/activerecord_master.gemfile
|
35
|
+
- rvm: 2.4.6
|
36
|
+
gemfile: gemfiles/activerecord_6.0.gemfile
|
37
|
+
- rvm: 2.4.6
|
38
|
+
gemfile: gemfiles/activerecord_master.gemfile
|
39
|
+
- rvm: 2.7.0
|
40
|
+
gemfile: gemfiles/activerecord_4.2.gemfile
|
41
|
+
addons:
|
42
|
+
apt:
|
43
|
+
packages:
|
44
|
+
- mysql-client-core-5.6
|
45
|
+
- mysql-client-5.6
|
data/Appraisals
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
appraise "activerecord-4.2" do
|
2
|
+
gem "activerecord", "~> 4.2.0"
|
3
|
+
gem "mysql2", "< 0.5"
|
4
|
+
end
|
5
|
+
|
6
|
+
appraise "activerecord-5.0" do
|
7
|
+
gem "activerecord", "~> 5.0.0"
|
8
|
+
end
|
9
|
+
|
10
|
+
appraise "activerecord-5.1" do
|
11
|
+
gem "activerecord", "~> 5.1.0"
|
12
|
+
end
|
13
|
+
|
14
|
+
appraise "activerecord-5.2" do
|
15
|
+
gem "activerecord", "~> 5.2.0"
|
16
|
+
end
|
17
|
+
|
18
|
+
appraise "activerecord-6.0" do
|
19
|
+
gem "activerecord", "~> 6.0.2"
|
20
|
+
end
|
21
|
+
|
22
|
+
appraise "activerecord-master" do
|
23
|
+
gem "activerecord", git: "https://github.com/rails/rails.git"
|
24
|
+
end
|
data/ChangeLog
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
activerecord-mysql-reconnect 0.4.1 (Aug 8, 2016)
|
2
|
+
|
3
|
+
* Support AR 5.0 (RP#5 @ssig33)
|
4
|
+
* Fix test (use docker-compose)
|
5
|
+
|
6
|
+
activerecord-mysql-reconnect 0.4.0 (Mar 22, 2016)
|
7
|
+
|
8
|
+
* Remove `retryable_transaction`
|
9
|
+
* Disable support AR 3.x 4.0
|
10
|
+
|
11
|
+
activerecord-mysql-reconnect 0.3.3 (Jan 18, 2014)
|
12
|
+
|
13
|
+
* use BigDecimal for sleep
|
14
|
+
* add handling error ('The MySQL server is running with the --read-only...')
|
15
|
+
|
16
|
+
activerecord-mysql-reconnect 0.3.1 (Jan 9, 2014)
|
17
|
+
|
18
|
+
* Retry mode is added
|
19
|
+
|
20
|
+
activerecord-mysql-reconnect 0.3.0 (Jan 9, 2014)
|
21
|
+
|
22
|
+
* Retry is disabled by default
|
23
|
+
* Read-only mode is added
|
24
|
+
|
25
|
+
activerecord-mysql-reconnect 0.2.0 (Jan 4, 2014)
|
26
|
+
|
27
|
+
* Retry transaction is supported
|
28
|
+
|
29
|
+
activerecord-mysql-reconnect 0.1.0 (Oct 11, 2013)
|
30
|
+
|
31
|
+
* activerecord-mysql-reconnect is released
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Genki Sugawara
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# activerecord-mysql-reconnect
|
2
|
+
|
3
|
+
It is the library to reconnect automatically when ActiveRecord is disconnected from MySQL.
|
4
|
+
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/activerecord-mysql-reconnect.svg)](http://badge.fury.io/rb/activerecord-mysql-reconnect)
|
6
|
+
[![Build Status](https://travis-ci.org/winebarrel/activerecord-mysql-reconnect.svg?branch=master)](https://travis-ci.org/winebarrel/activerecord-mysql-reconnect)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'activerecord-mysql-reconnect'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install activerecord-mysql-reconnect
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'active_record'
|
26
|
+
require 'activerecord-mysql-reconnect'
|
27
|
+
require 'logger'
|
28
|
+
|
29
|
+
ActiveRecord::Base.establish_connection(
|
30
|
+
adapter: 'mysql2',
|
31
|
+
host: '127.0.0.1',
|
32
|
+
username: 'root',
|
33
|
+
database: 'employees',
|
34
|
+
)
|
35
|
+
|
36
|
+
ActiveRecord::Base.logger = Logger.new($stdout)
|
37
|
+
ActiveRecord::Base.logger.formatter = proc {|_, _, _, message| "#{message}\n" }
|
38
|
+
ActiveRecord::Base.enable_retry = true
|
39
|
+
ActiveRecord::Base.execution_tries = 3
|
40
|
+
|
41
|
+
class Employee < ActiveRecord::Base; end
|
42
|
+
|
43
|
+
p Employee.count
|
44
|
+
system('sudo /etc/init.d/mysqld restart')
|
45
|
+
p Employee.count
|
46
|
+
```
|
47
|
+
|
48
|
+
```
|
49
|
+
shell> ruby test.rb
|
50
|
+
(64.1ms) SELECT COUNT(*) FROM `employees`
|
51
|
+
300024
|
52
|
+
Stopping mysqld: [ OK ]
|
53
|
+
Starting mysqld: [ OK ]
|
54
|
+
(0.4ms) SELECT COUNT(*) FROM `employees`
|
55
|
+
Mysql2::Error: MySQL server has gone away: SELECT COUNT(*) FROM `employees`
|
56
|
+
MySQL server has gone away. Trying to reconnect in 0.5 seconds. (cause: Mysql2::Error: MySQL server has gone away: SELECT COUNT(*) FROM `employees` [ActiveRecord::StatementInvalid], connection: host=127.0.0.1;database=employees;username=root)
|
57
|
+
(101.5ms) SELECT COUNT(*) FROM `employees`
|
58
|
+
300024
|
59
|
+
```
|
60
|
+
|
61
|
+
### without_retry
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
ActiveRecord::Base.without_retry do
|
65
|
+
Employee.count
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
### Add a retry error message
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
Activerecord::Mysql::Reconnect.handle_rw_error_messages.update(
|
73
|
+
zapzapzap: 'ZapZapZap'
|
74
|
+
)
|
75
|
+
# or `Activerecord::Mysql::Reconnect.handle_r_error_messages...`
|
76
|
+
```
|
77
|
+
|
78
|
+
## Use on rails
|
79
|
+
|
80
|
+
### Gemfile
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
gem 'activerecord-mysql-reconnect'
|
84
|
+
```
|
85
|
+
|
86
|
+
### environment file
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
MyApp::Application.configure do
|
90
|
+
...
|
91
|
+
config.active_record.enable_retry = true
|
92
|
+
#config.active_record.retry_databases = :employees
|
93
|
+
# e.g. [:employees]
|
94
|
+
# ['employees', 'localhost:test', '192.168.1.1:users']
|
95
|
+
# ['192.168.%:emp\_all']
|
96
|
+
# ['emp%']
|
97
|
+
# retry_databases -> nil: retry all databases (default)
|
98
|
+
config.active_record.execution_tries = 10 # times
|
99
|
+
# execution_tries -> 0: retry indefinitely
|
100
|
+
config.active_record.execution_retry_wait = 1.5 # sec
|
101
|
+
config.active_record.retry_mode = :rw # default: `:r`, valid values: `:r`, `:rw`, `:force`
|
102
|
+
...
|
103
|
+
ene
|
104
|
+
```
|
105
|
+
|
106
|
+
## Retry mode
|
107
|
+
|
108
|
+
* `:r` Retry only SELECT / SHOW / SET
|
109
|
+
* `:rw` Retry in all SQL, but does not retry if `Lost connection` has happened in write SQL
|
110
|
+
* `:force` Retry in all SQL
|
111
|
+
|
112
|
+
## Run tests
|
113
|
+
|
114
|
+
It requires the following:
|
115
|
+
|
116
|
+
* Docker
|
117
|
+
* Docker Compose
|
118
|
+
|
119
|
+
```sh
|
120
|
+
bundle install
|
121
|
+
bundle exec appraisal install
|
122
|
+
bundle exec appraisal activerecord-4.2 rake
|
123
|
+
bundle exec appraisal activerecord-5.0 rake
|
124
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'activerecord/mysql/reconnect/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'activerecord-mysql-reconnect-before-retry'
|
8
|
+
spec.version = Activerecord::Mysql::Reconnect::VERSION
|
9
|
+
spec.authors = ['Genki Sugawara']
|
10
|
+
spec.email = ['sugawara@cookpad.com']
|
11
|
+
spec.description = %q{It is the library to reconnect automatically when ActiveRecord is disconnected from MySQL.}
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = 'https://github.com/winebarrel/activerecord-mysql-reconnect'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
# '~> 4.2.6'
|
22
|
+
spec.add_dependency 'activerecord'
|
23
|
+
spec.add_dependency 'mysql2'
|
24
|
+
spec.add_development_dependency 'bundler'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'rspec', '>= 3.0.0'
|
27
|
+
spec.add_development_dependency "appraisal"
|
28
|
+
end
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
require 'logger'
|
3
|
+
require 'bigdecimal'
|
4
|
+
require 'strscan'
|
5
|
+
|
6
|
+
require 'active_record'
|
7
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
8
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
9
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
10
|
+
require 'active_record/connection_adapters/abstract/connection_pool'
|
11
|
+
require 'active_record/connection_adapters/abstract/transaction'
|
12
|
+
|
13
|
+
require 'activerecord/mysql/reconnect/version'
|
14
|
+
require 'activerecord/mysql/reconnect/base_ext'
|
15
|
+
|
16
|
+
require 'activerecord/mysql/reconnect/abstract_mysql_adapter_ext'
|
17
|
+
require 'activerecord/mysql/reconnect/mysql2_adapter_ext'
|
18
|
+
require 'activerecord/mysql/reconnect/connection_pool_ext'
|
19
|
+
require 'activerecord/mysql/reconnect/null_transaction_ext'
|
20
|
+
|
21
|
+
module Activerecord::Mysql::Reconnect
|
22
|
+
DEFAULT_EXECUTION_TRIES = 3
|
23
|
+
DEFAULT_EXECUTION_RETRY_WAIT = 0.5
|
24
|
+
|
25
|
+
WITHOUT_RETRY_KEY = 'activerecord-mysql-reconnect-without-retry'
|
26
|
+
|
27
|
+
HANDLE_ERROR = [
|
28
|
+
ActiveRecord::StatementInvalid,
|
29
|
+
Mysql2::Error,
|
30
|
+
]
|
31
|
+
|
32
|
+
@@handle_r_error_messages = {
|
33
|
+
lost_connection: 'Lost connection to MySQL server during query',
|
34
|
+
}
|
35
|
+
|
36
|
+
@@handle_rw_error_messages = {
|
37
|
+
gone_away: 'MySQL server has gone away',
|
38
|
+
server_shutdown: 'Server shutdown in progress',
|
39
|
+
closed_connection: 'closed MySQL connection',
|
40
|
+
cannot_connect: "Can't connect to MySQL server",
|
41
|
+
interrupted: 'Query execution was interrupted',
|
42
|
+
access_denied: 'Access denied for user',
|
43
|
+
read_only: 'The MySQL server is running with the --read-only option',
|
44
|
+
cannot_connect_to_local: "Can't connect to local MySQL server", # When running in local sandbox, or using a socket file
|
45
|
+
unknown_host: 'Unknown MySQL server host', # For DNS blips
|
46
|
+
lost_connection: "Lost connection to MySQL server at 'reading initial communication packet'",
|
47
|
+
not_connected: "MySQL client is not connected",
|
48
|
+
killed: 'Connection was killed',
|
49
|
+
}
|
50
|
+
|
51
|
+
READ_SQL_REGEXP = /\A\s*(?:SELECT|SHOW|SET)\b/i
|
52
|
+
|
53
|
+
RETRY_MODES = [:r, :rw, :force]
|
54
|
+
DEFAULT_RETRY_MODE = :r
|
55
|
+
|
56
|
+
class << self
|
57
|
+
def handle_r_error_messages
|
58
|
+
@@handle_r_error_messages
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_rw_error_messages
|
62
|
+
@@handle_rw_error_messages
|
63
|
+
end
|
64
|
+
|
65
|
+
def execution_tries
|
66
|
+
ActiveRecord::Base.execution_tries || DEFAULT_EXECUTION_TRIES
|
67
|
+
end
|
68
|
+
|
69
|
+
def execution_retry_wait
|
70
|
+
wait = ActiveRecord::Base.execution_retry_wait || DEFAULT_EXECUTION_RETRY_WAIT
|
71
|
+
wait.kind_of?(BigDecimal) ? wait : BigDecimal(wait.to_s)
|
72
|
+
end
|
73
|
+
|
74
|
+
def enable_retry
|
75
|
+
!!ActiveRecord::Base.enable_retry
|
76
|
+
end
|
77
|
+
|
78
|
+
def retry_mode=(v)
|
79
|
+
unless RETRY_MODES.include?(v)
|
80
|
+
raise "Invalid retry_mode. Please set one of the following: #{RETRY_MODES.map {|i| i.inspect }.join(', ')}"
|
81
|
+
end
|
82
|
+
|
83
|
+
@activerecord_mysql_reconnect_retry_mode = v
|
84
|
+
end
|
85
|
+
|
86
|
+
def retry_mode
|
87
|
+
@activerecord_mysql_reconnect_retry_mode || DEFAULT_RETRY_MODE
|
88
|
+
end
|
89
|
+
|
90
|
+
def before_retry=(v)
|
91
|
+
@activerecord_mysql_reconnect_before_retry = v
|
92
|
+
end
|
93
|
+
|
94
|
+
def before_retry
|
95
|
+
@activerecord_mysql_reconnect_before_retry
|
96
|
+
end
|
97
|
+
|
98
|
+
def retry_databases=(v)
|
99
|
+
v ||= []
|
100
|
+
|
101
|
+
unless v.kind_of?(Array)
|
102
|
+
v = [v]
|
103
|
+
end
|
104
|
+
|
105
|
+
@activerecord_mysql_reconnect_retry_databases = v.map do |database|
|
106
|
+
if database.instance_of?(Symbol)
|
107
|
+
database = Regexp.escape(database.to_s)
|
108
|
+
[/.*/, /\A#{database}\z/]
|
109
|
+
else
|
110
|
+
host = '%'
|
111
|
+
database = database.to_s
|
112
|
+
|
113
|
+
if database =~ /:/
|
114
|
+
host, database = database.split(':', 2)
|
115
|
+
end
|
116
|
+
|
117
|
+
[create_pattern_match_regex(host), create_pattern_match_regex(database)]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def retry_databases
|
123
|
+
@activerecord_mysql_reconnect_retry_databases || []
|
124
|
+
end
|
125
|
+
|
126
|
+
def retryable(opts)
|
127
|
+
block = opts.fetch(:proc)
|
128
|
+
on_error = opts[:on_error]
|
129
|
+
conn = opts[:connection]
|
130
|
+
sql = opts[:sql]
|
131
|
+
tries = self.execution_tries
|
132
|
+
before_retry = self.before_retry
|
133
|
+
retval = nil
|
134
|
+
|
135
|
+
retryable_loop(tries) do |n|
|
136
|
+
begin
|
137
|
+
retval = block.call
|
138
|
+
break
|
139
|
+
rescue => e
|
140
|
+
if enable_retry and (tries.zero? or n < tries) and should_handle?(e, opts)
|
141
|
+
on_error.call if on_error
|
142
|
+
|
143
|
+
if before_retry.is_a?(Proc)
|
144
|
+
logger.warn("MySQL server has gone away. Running before retry proc")
|
145
|
+
before_retry.call(e, sql, conn)
|
146
|
+
end
|
147
|
+
|
148
|
+
wait = self.execution_retry_wait * n
|
149
|
+
|
150
|
+
logger.warn("MySQL server has gone away. Trying to reconnect in #{wait.to_f} seconds. (#{build_error_message(e, sql, conn)})")
|
151
|
+
sleep(wait)
|
152
|
+
next
|
153
|
+
else
|
154
|
+
if enable_retry and n > 1
|
155
|
+
logger.warn("Query retry failed. (#{build_error_message(e, sql, conn)})")
|
156
|
+
end
|
157
|
+
|
158
|
+
raise e
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
return retval
|
164
|
+
end
|
165
|
+
|
166
|
+
def logger
|
167
|
+
if defined?(Rails)
|
168
|
+
Rails.logger || ActiveRecord::Base.logger || Logger.new($stderr)
|
169
|
+
else
|
170
|
+
ActiveRecord::Base.logger || Logger.new($stderr)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def without_retry
|
175
|
+
begin
|
176
|
+
Thread.current[WITHOUT_RETRY_KEY] = true
|
177
|
+
yield
|
178
|
+
ensure
|
179
|
+
Thread.current[WITHOUT_RETRY_KEY] = nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def without_retry?
|
184
|
+
!!Thread.current[WITHOUT_RETRY_KEY]
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def retryable_loop(n)
|
190
|
+
if n.zero?
|
191
|
+
loop { n += 1 ; yield(n) }
|
192
|
+
else
|
193
|
+
n.times {|i| yield(i + 1) }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def should_handle?(e, opts = {})
|
198
|
+
sql = opts[:sql]
|
199
|
+
retry_mode = opts[:retry_mode]
|
200
|
+
conn = opts[:connection]
|
201
|
+
|
202
|
+
if without_retry?
|
203
|
+
return false
|
204
|
+
end
|
205
|
+
|
206
|
+
if conn and not retry_databases.empty?
|
207
|
+
conn_info = connection_info(conn)
|
208
|
+
|
209
|
+
included = retry_databases.any? do |host, database|
|
210
|
+
host =~ conn_info[:host] and database =~ conn_info[:database]
|
211
|
+
end
|
212
|
+
|
213
|
+
return false unless included
|
214
|
+
end
|
215
|
+
|
216
|
+
unless HANDLE_ERROR.any? {|i| e.kind_of?(i) }
|
217
|
+
return false
|
218
|
+
end
|
219
|
+
|
220
|
+
unless Regexp.union(@@handle_r_error_messages.values + @@handle_rw_error_messages.values) =~ e.message
|
221
|
+
return false
|
222
|
+
end
|
223
|
+
|
224
|
+
if sql and READ_SQL_REGEXP !~ sql
|
225
|
+
if retry_mode == :r
|
226
|
+
return false
|
227
|
+
end
|
228
|
+
|
229
|
+
if retry_mode != :force and Regexp.union(@@handle_r_error_messages.values) =~ e.message
|
230
|
+
return false
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
return true
|
235
|
+
end
|
236
|
+
|
237
|
+
def connection_info(conn)
|
238
|
+
conn_info = {}
|
239
|
+
|
240
|
+
if conn.kind_of?(Mysql2::Client)
|
241
|
+
[:host, :database, :username].each {|k| conn_info[k] = conn.query_options[k] }
|
242
|
+
elsif conn.kind_of?(Hash)
|
243
|
+
conn_info = conn.dup
|
244
|
+
end
|
245
|
+
|
246
|
+
return conn_info
|
247
|
+
end
|
248
|
+
|
249
|
+
def create_pattern_match_regex(str)
|
250
|
+
ss = StringScanner.new(str)
|
251
|
+
buf = []
|
252
|
+
|
253
|
+
until ss.eos?
|
254
|
+
if (tok = ss.scan(/[^\\%_]+/))
|
255
|
+
buf << Regexp.escape(tok)
|
256
|
+
elsif (tok = ss.scan(/\\/))
|
257
|
+
buf << Regexp.escape(ss.getch)
|
258
|
+
elsif (tok = ss.scan(/%/))
|
259
|
+
buf << '.*'
|
260
|
+
elsif (tok = ss.scan(/_/))
|
261
|
+
buf << '.'
|
262
|
+
else
|
263
|
+
raise 'must not happen'
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
/\A#{buf.join}\z/
|
268
|
+
end
|
269
|
+
|
270
|
+
def build_error_message(e, sql, conn)
|
271
|
+
msgs = {cause: "#{e.message} [#{e.class}]"}
|
272
|
+
msgs[:sql] = sql if sql
|
273
|
+
|
274
|
+
if conn
|
275
|
+
conn_info = connection_info(conn)
|
276
|
+
msgs[:connection] = [:host, :database, :username].map {|k| "#{k}=#{conn_info[k]}" }.join(";")
|
277
|
+
end
|
278
|
+
|
279
|
+
msgs.map {|k, v| "#{k}: #{v}" }.join(", ")
|
280
|
+
end
|
281
|
+
end # end of class methods
|
282
|
+
end
|