activerecord-mysql-reconnect-before-retry 0.5.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 +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
|
+
[](http://badge.fury.io/rb/activerecord-mysql-reconnect)
|
6
|
+
[](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
|