ebisu_connection 2.3.1 → 2.4.0.rc1
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 +4 -4
- data/.travis.yml +4 -7
- data/Appraisals +13 -2
- data/ebisu_connection.gemspec +2 -2
- data/gemfiles/rails42.gemfile +3 -1
- data/gemfiles/rails50.gemfile +3 -1
- data/gemfiles/rails51.gemfile +10 -0
- data/lib/ebisu_connection/conf_file.rb +29 -75
- data/lib/ebisu_connection/connection_manager.rb +38 -44
- data/lib/ebisu_connection/railtie.rb +9 -0
- data/lib/ebisu_connection/replica.rb +25 -16
- data/lib/ebisu_connection/version.rb +1 -1
- data/lib/ebisu_connection.rb +4 -11
- metadata +12 -11
- data/lib/ebisu_connection/replica_group.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c6241ca58f74a40f35eeabbead0c1a09b0123b5
|
4
|
+
data.tar.gz: 69e07131acae59f06805441cc3e79b8f6b370776
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41281a706b834dd6c92d97774019a871f9bc416c33f701b5fa10935cd41e4c1e6c5b6bcdc311c873e2da40202022d00170467b73a09e14c1f9c269eecf43f1c8
|
7
|
+
data.tar.gz: 831357fd7126ee7765c92240aa5e65397e0f14e358cb096b98b48147a97a1f327311428f3d1fbf68bbe7446ba3eef19031b746082f6831f2dcd7849ae77a6977
|
data/.travis.yml
CHANGED
@@ -11,16 +11,13 @@ before_script:
|
|
11
11
|
- psql -c 'create database ebisu_connection_test_replica;' -U postgres
|
12
12
|
cache: bundler
|
13
13
|
rvm:
|
14
|
-
- 2.
|
15
|
-
- 2.
|
16
|
-
- 2.
|
17
|
-
- 2.4.0
|
14
|
+
- 2.2.7
|
15
|
+
- 2.3.4
|
16
|
+
- 2.4.1
|
18
17
|
gemfile:
|
19
18
|
- gemfiles/rails42.gemfile
|
20
19
|
- gemfiles/rails50.gemfile
|
20
|
+
- gemfiles/rails51.gemfile
|
21
21
|
matrix:
|
22
|
-
exclude:
|
23
|
-
- rvm: 2.1.10
|
24
|
-
gemfile: gemfiles/rails50.gemfile
|
25
22
|
fast_finish: true
|
26
23
|
bundler_args: --jobs 3 --retry 3
|
data/Appraisals
CHANGED
@@ -1,9 +1,20 @@
|
|
1
1
|
appraise "rails42" do
|
2
2
|
gem "activerecord", "~> 4.2.0"
|
3
|
-
gem '
|
3
|
+
gem 'activesupport', '~> 4.2.0'
|
4
|
+
gem 'mysql2', '>= 0.3.13', '< 0.5'
|
5
|
+
gem 'pg', '~> 0.15'
|
4
6
|
end
|
5
7
|
|
6
8
|
appraise "rails50" do
|
7
|
-
gem "activerecord", "5.0.0"
|
9
|
+
gem "activerecord", "~> 5.0.0"
|
10
|
+
gem 'activesupport', '~> 5.0.0'
|
8
11
|
gem 'mysql2', '>= 0.3.18', '< 0.5'
|
12
|
+
gem 'pg', '~> 0.18'
|
13
|
+
end
|
14
|
+
|
15
|
+
appraise "rails51" do
|
16
|
+
gem 'activerecord', '~> 5.1.0'
|
17
|
+
gem 'activesupport', '~> 5.1.0'
|
18
|
+
gem 'mysql2', '>= 0.3.18', '< 0.5'
|
19
|
+
gem 'pg', '~> 0.18'
|
9
20
|
end
|
data/ebisu_connection.gemspec
CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.required_ruby_version = '>= 2.
|
22
|
+
spec.required_ruby_version = '>= 2.2'
|
23
23
|
|
24
|
-
spec.add_dependency 'fresh_connection', '
|
24
|
+
spec.add_dependency 'fresh_connection', '2.4.0.rc3'
|
25
25
|
spec.add_dependency 'concurrent-ruby', '~> 1.0.0'
|
26
26
|
|
27
27
|
spec.add_development_dependency 'mysql2', '>= 0.3.13', '< 0.5'
|
data/gemfiles/rails42.gemfile
CHANGED
data/gemfiles/rails50.gemfile
CHANGED
@@ -4,106 +4,60 @@ require 'active_support/deprecation'
|
|
4
4
|
module EbisuConnection
|
5
5
|
class ConfFile
|
6
6
|
class << self
|
7
|
-
attr_writer :replica_file
|
7
|
+
attr_writer :replica_file
|
8
8
|
|
9
9
|
def slaves_file=(file)
|
10
10
|
ActiveSupport::Deprecation.warn(
|
11
|
-
"'slaves_file=' is deprecated and will removed from version 2.
|
11
|
+
"'slaves_file=' is deprecated and will removed from version 2.5.0. use 'replica_file=' instead."
|
12
12
|
)
|
13
13
|
|
14
14
|
self.replica_file = file
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
|
19
|
-
yield
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def conf_clear!
|
24
|
-
@replica_conf = nil
|
25
|
-
end
|
26
|
-
|
27
|
-
def replica_conf(replica_group)
|
28
|
-
@replica_conf ||= get_replica_conf
|
17
|
+
def replica_conf(spec_name)
|
18
|
+
return config unless config.is_a?(Hash)
|
29
19
|
|
30
|
-
|
31
|
-
|
20
|
+
c = config[spec_name]
|
21
|
+
return c if c
|
32
22
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
23
|
+
if spec_name == "replica" && config.key?("slave")
|
24
|
+
ActiveSupport::Deprecation.warn(
|
25
|
+
"'slave' in replica.yml is deprecated and will ignored from version 2.5.0. use 'replica' insted."
|
26
|
+
)
|
37
27
|
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
c || @replica_conf
|
42
|
-
else
|
43
|
-
@replica_conf
|
28
|
+
c = config["slave"]
|
44
29
|
end
|
30
|
+
|
31
|
+
c || config
|
45
32
|
end
|
46
33
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
)
|
34
|
+
private
|
35
|
+
|
36
|
+
def config
|
37
|
+
return @config if defined?(@config)
|
51
38
|
|
52
|
-
|
39
|
+
conf = YAML.load_file(replica_file)
|
40
|
+
@config = conf[EbisuConnection.env.to_s] || {}
|
53
41
|
end
|
54
42
|
|
55
43
|
def replica_file
|
56
44
|
return @replica_file if @replica_file
|
45
|
+
|
57
46
|
raise "nothing replica_file. You have to set a file path using EbisuConnection.replica_file= method" unless defined?(Rails)
|
58
47
|
|
59
48
|
file = %w(yml yaml).map{|ext| Rails.root.join("config/replica.#{ext}").to_s }.detect {|f| File.exist?(f) }
|
49
|
+
return file if file
|
60
50
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
raise "nothing replica_file. You have to put a config/replica.yml file" unless file
|
71
|
-
|
72
|
-
@replica_file = file
|
73
|
-
end
|
51
|
+
file = %w(yml yaml).map{|ext| Rails.root.join("config/slave.#{ext}").to_s }.detect {|f| File.exist?(f) }
|
52
|
+
if file
|
53
|
+
ActiveSupport::Deprecation.warn(
|
54
|
+
"file name 'config/#{file}' is deprecated and will ignored from version 2.5.0. use 'config/replica.yml' insted."
|
55
|
+
)
|
74
56
|
|
75
|
-
|
76
|
-
|
77
|
-
"'slaves_file' is deprecated and will removed from version 2.4.0. use 'replica_file' insted."
|
78
|
-
)
|
79
|
-
|
80
|
-
replica_file
|
81
|
-
end
|
82
|
-
|
83
|
-
def check_interval
|
84
|
-
@check_interval || 1.minute
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
def time_to_check?
|
90
|
-
now = Time.now
|
91
|
-
@check_time ||= now
|
92
|
-
|
93
|
-
return false if now - @check_time < check_interval
|
94
|
-
|
95
|
-
@check_time = now
|
96
|
-
true
|
97
|
-
end
|
98
|
-
|
99
|
-
def modify?
|
100
|
-
@file_mtime != File.mtime(replica_file)
|
101
|
-
end
|
57
|
+
return file
|
58
|
+
end
|
102
59
|
|
103
|
-
|
104
|
-
@file_mtime = File.mtime(replica_file)
|
105
|
-
conf = YAML.load_file(replica_file)
|
106
|
-
conf[EbisuConnection.env.to_s] || {}
|
60
|
+
raise "nothing replica_file. You have to put a config/replica.yml file"
|
107
61
|
end
|
108
62
|
end
|
109
63
|
end
|
@@ -1,76 +1,70 @@
|
|
1
1
|
require "concurrent"
|
2
|
-
require "ebisu_connection/
|
2
|
+
require "ebisu_connection/replica"
|
3
|
+
require "ebisu_connection/greatest_common_divisor"
|
3
4
|
|
4
5
|
module EbisuConnection
|
5
6
|
class ConnectionManager < FreshConnection::AbstractConnectionManager
|
6
|
-
|
7
|
+
class AllReplicaHasGoneError < StandardError; end
|
8
|
+
|
9
|
+
def initialize(spec_name = nil)
|
7
10
|
super
|
8
|
-
|
11
|
+
|
12
|
+
@replicas = Concurrent::Array.new
|
13
|
+
|
14
|
+
replica_conf.each do |conf|
|
15
|
+
@replicas << Replica.new(conf, spec_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
recalc_roulette
|
9
19
|
end
|
10
20
|
|
11
21
|
def replica_connection
|
12
|
-
replicas.
|
22
|
+
raise AllReplicaHasGoneError if @replicas.empty?
|
23
|
+
@replicas[@roulette.sample].connection
|
13
24
|
end
|
14
25
|
|
15
26
|
def put_aside!
|
16
|
-
|
17
|
-
|
18
|
-
ConfFile.if_modify do
|
19
|
-
reserve_release_all_connection
|
20
|
-
check_own_connection
|
27
|
+
@replicas.each_value do |pool|
|
28
|
+
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
24
32
|
def clear_all_connections!
|
25
|
-
@replicas.each_value do |
|
26
|
-
|
33
|
+
@replicas.each_value do |pool|
|
34
|
+
pool.disconnect!
|
27
35
|
end
|
28
|
-
|
29
|
-
@replicas.clear
|
30
|
-
ConfFile.conf_clear!
|
31
36
|
end
|
32
37
|
|
33
38
|
def recovery?
|
34
|
-
replicas.
|
35
|
-
|
39
|
+
dead_replicas = @replicas.select{|pool| !pool.connection.active? }
|
40
|
+
return false if dead_replicas.empty?
|
36
41
|
|
37
|
-
|
42
|
+
dead_replicas.each do |pool|
|
43
|
+
pool.disconnect!
|
44
|
+
@replicas.delete(pool)
|
45
|
+
end
|
38
46
|
|
39
|
-
|
40
|
-
s = @replicas[current_thread_id]
|
47
|
+
raise AllReplicaHasGoneError if @replicas.empty?
|
41
48
|
|
42
|
-
|
43
|
-
|
44
|
-
@replicas.delete(current_thread_id)
|
45
|
-
true
|
46
|
-
else
|
47
|
-
false
|
48
|
-
end
|
49
|
+
recalc_roulette
|
50
|
+
true
|
49
51
|
end
|
50
52
|
|
51
|
-
|
52
|
-
@replicas.each_value do |s|
|
53
|
-
s.reserve_release_connection!
|
54
|
-
end
|
55
|
-
ConfFile.conf_clear!
|
56
|
-
end
|
53
|
+
private
|
57
54
|
|
58
|
-
def
|
59
|
-
@replicas.
|
60
|
-
get_replicas
|
61
|
-
end
|
62
|
-
end
|
55
|
+
def recalc_roulette
|
56
|
+
weight_list = @replicas.map {|pool| pool.weight }
|
63
57
|
|
64
|
-
|
65
|
-
|
58
|
+
@roulette = []
|
59
|
+
gcd = GreatestCommonDivisor.calc(weight_list)
|
60
|
+
weight_list.each_with_index do |w, index|
|
61
|
+
weight = w / gcd
|
62
|
+
@roulette.concat([index] * weight)
|
63
|
+
end
|
66
64
|
end
|
67
65
|
|
68
66
|
def replica_conf
|
69
|
-
ConfFile.replica_conf(
|
70
|
-
end
|
71
|
-
|
72
|
-
def current_thread_id
|
73
|
-
Thread.current.object_id
|
67
|
+
ConfFile.replica_conf(spec_name)
|
74
68
|
end
|
75
69
|
end
|
76
70
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module EbisuConnection
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "fresh_connection.initialize_database", after: "active_record.initialize_database" do
|
4
|
+
ActiveSupport.on_load(:active_record) do
|
5
|
+
ActiveRecord::Base.establish_fresh_connection
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -1,45 +1,54 @@
|
|
1
|
+
require 'fresh_connection/connection_specification'
|
2
|
+
|
1
3
|
module EbisuConnection
|
2
4
|
class Replica
|
3
5
|
attr_reader :hostname, :weight
|
4
6
|
|
5
|
-
def initialize(conf,
|
7
|
+
def initialize(conf, spec_name)
|
6
8
|
case conf
|
7
9
|
when String
|
8
10
|
host, weight = conf.split(/\s*,\s*/)
|
9
|
-
@hostname, port = host.split(/\s*:\s*/)
|
11
|
+
@hostname, @port = host.split(/\s*:\s*/)
|
10
12
|
when Hash
|
11
13
|
@hostname = conf["host"] || conf[:host]
|
12
14
|
weight = conf["weight"] || conf[:weight]
|
13
|
-
port = conf["port"] || conf[:port]
|
15
|
+
@port = conf["port"] || conf[:port]
|
14
16
|
else
|
15
17
|
raise ArgumentError, "replica config is invalid"
|
16
18
|
end
|
17
19
|
|
18
|
-
spec =
|
19
|
-
|
20
|
+
spec = FreshConnection::ConnectionSpecification.new(
|
21
|
+
spec_name, modify_spec: modify_spec
|
22
|
+
).spec
|
23
|
+
|
24
|
+
@pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
|
20
25
|
@weight = (weight || 1).to_i
|
21
26
|
end
|
22
27
|
|
23
28
|
def connection
|
24
|
-
@connection
|
29
|
+
@pool.connection
|
30
|
+
end
|
31
|
+
|
32
|
+
def active_connection?
|
33
|
+
@pool.active_connection?
|
25
34
|
end
|
26
35
|
|
27
|
-
def
|
28
|
-
|
36
|
+
def release_connection
|
37
|
+
@pool.release_connection
|
29
38
|
end
|
30
39
|
|
31
40
|
def disconnect!
|
32
|
-
|
33
|
-
@connection.disconnect!
|
34
|
-
@connection = nil
|
35
|
-
end
|
36
|
-
rescue
|
41
|
+
@pool.disconnect!
|
37
42
|
end
|
38
43
|
|
39
|
-
|
44
|
+
private
|
45
|
+
|
46
|
+
def modify_spec
|
40
47
|
modify_spec = {"host" => @hostname}
|
41
|
-
return modify_spec
|
42
|
-
modify_spec
|
48
|
+
return modify_spec if !defined?(@port) || @port.nil?
|
49
|
+
return modify_spec if @port.respond_to?(:empty?) && @port.empty?
|
50
|
+
|
51
|
+
modify_spec["port"] = @port.to_i
|
43
52
|
modify_spec
|
44
53
|
end
|
45
54
|
end
|
data/lib/ebisu_connection.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'active_support
|
1
|
+
require 'active_support'
|
2
2
|
require "fresh_connection"
|
3
3
|
require "ebisu_connection/conf_file"
|
4
4
|
|
@@ -11,15 +11,7 @@ module EbisuConnection
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def slaves_file=(file)
|
14
|
-
|
15
|
-
"'slaves_file=' is deprecated and will removed from version 2.4.0. use 'replica_file=' insted."
|
16
|
-
)
|
17
|
-
|
18
|
-
self.replica_file = file
|
19
|
-
end
|
20
|
-
|
21
|
-
def check_interval=(interval)
|
22
|
-
ConfFile.check_interval = interval
|
14
|
+
ConfFile.slaves_file = file
|
23
15
|
end
|
24
16
|
|
25
17
|
def env
|
@@ -30,4 +22,5 @@ end
|
|
30
22
|
|
31
23
|
require "ebisu_connection/connection_manager"
|
32
24
|
FreshConnection.connection_manager = EbisuConnection::ConnectionManager
|
33
|
-
|
25
|
+
|
26
|
+
require "ebisu_connection/railtie" if defined?(Rails)
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ebisu_connection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tsukasaoishi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fresh_connection
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
19
|
+
version: 2.4.0.rc3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
26
|
+
version: 2.4.0.rc3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: concurrent-ruby
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -169,12 +169,13 @@ files:
|
|
169
169
|
- ebisu_connection.gemspec
|
170
170
|
- gemfiles/rails42.gemfile
|
171
171
|
- gemfiles/rails50.gemfile
|
172
|
+
- gemfiles/rails51.gemfile
|
172
173
|
- lib/ebisu_connection.rb
|
173
174
|
- lib/ebisu_connection/conf_file.rb
|
174
175
|
- lib/ebisu_connection/connection_manager.rb
|
175
176
|
- lib/ebisu_connection/greatest_common_divisor.rb
|
177
|
+
- lib/ebisu_connection/railtie.rb
|
176
178
|
- lib/ebisu_connection/replica.rb
|
177
|
-
- lib/ebisu_connection/replica_group.rb
|
178
179
|
- lib/ebisu_connection/version.rb
|
179
180
|
homepage: https://github.com/tsukasaoishi/ebisu_connection
|
180
181
|
licenses:
|
@@ -188,15 +189,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
188
189
|
requirements:
|
189
190
|
- - ">="
|
190
191
|
- !ruby/object:Gem::Version
|
191
|
-
version: '2.
|
192
|
+
version: '2.2'
|
192
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
194
|
requirements:
|
194
|
-
- - "
|
195
|
+
- - ">"
|
195
196
|
- !ruby/object:Gem::Version
|
196
|
-
version:
|
197
|
+
version: 1.3.1
|
197
198
|
requirements: []
|
198
199
|
rubyforge_project:
|
199
|
-
rubygems_version: 2.
|
200
|
+
rubygems_version: 2.6.11
|
200
201
|
signing_key:
|
201
202
|
specification_version: 4
|
202
203
|
summary: EbisuConnection supports connections with configured replica servers.
|
@@ -1,62 +0,0 @@
|
|
1
|
-
require "ebisu_connection/replica"
|
2
|
-
require "ebisu_connection/greatest_common_divisor"
|
3
|
-
|
4
|
-
module EbisuConnection
|
5
|
-
class ReplicaGroup
|
6
|
-
class AllReplicaHasGoneError < StandardError; end
|
7
|
-
|
8
|
-
def initialize(replica_conf, replica_group)
|
9
|
-
@replicas = replica_conf.map do |conf|
|
10
|
-
Replica.new(conf, replica_group)
|
11
|
-
end
|
12
|
-
|
13
|
-
recalc_roulette
|
14
|
-
end
|
15
|
-
|
16
|
-
def sample
|
17
|
-
raise AllReplicaHasGoneError if @replicas.empty?
|
18
|
-
@replicas[@roulette.sample]
|
19
|
-
end
|
20
|
-
|
21
|
-
def recovery_connection?
|
22
|
-
dead_replicas = @replicas.select{|s| !s.active? }
|
23
|
-
return false if dead_replicas.empty?
|
24
|
-
|
25
|
-
dead_replicas.each do |s|
|
26
|
-
s.disconnect!
|
27
|
-
@replicas.delete(s)
|
28
|
-
end
|
29
|
-
|
30
|
-
raise AllReplicaHasGoneError if @replicas.empty?
|
31
|
-
|
32
|
-
recalc_roulette
|
33
|
-
true
|
34
|
-
end
|
35
|
-
|
36
|
-
def all_disconnect!
|
37
|
-
@reserve_release = nil
|
38
|
-
@replicas.each {|s| s.disconnect!}
|
39
|
-
end
|
40
|
-
|
41
|
-
def reserve_release_connection!
|
42
|
-
@reserve_release = true
|
43
|
-
end
|
44
|
-
|
45
|
-
def reserved_release?
|
46
|
-
!!@reserve_release
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def recalc_roulette
|
52
|
-
weight_list = @replicas.map {|s| s.weight }
|
53
|
-
|
54
|
-
@roulette = []
|
55
|
-
gcd = GreatestCommonDivisor.calc(weight_list)
|
56
|
-
weight_list.each_with_index do |w, index|
|
57
|
-
weight = w / gcd
|
58
|
-
@roulette.concat([index] * weight)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|