mysql2 0.3.1 → 0.5.2
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/CHANGELOG.md +1 -151
- data/LICENSE +21 -0
- data/README.md +634 -0
- data/examples/eventmachine.rb +1 -3
- data/examples/threaded.rb +5 -9
- data/ext/mysql2/client.c +1154 -342
- data/ext/mysql2/client.h +20 -33
- data/ext/mysql2/extconf.rb +229 -37
- data/ext/mysql2/infile.c +122 -0
- data/ext/mysql2/infile.h +1 -0
- data/ext/mysql2/mysql2_ext.c +3 -1
- data/ext/mysql2/mysql2_ext.h +18 -16
- data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
- data/ext/mysql2/mysql_enc_to_ruby.h +259 -0
- data/ext/mysql2/result.c +708 -191
- data/ext/mysql2/result.h +15 -6
- data/ext/mysql2/statement.c +602 -0
- data/ext/mysql2/statement.h +17 -0
- data/ext/mysql2/wait_for_single_fd.h +37 -0
- data/lib/mysql2.rb +69 -7
- data/lib/mysql2/client.rb +126 -211
- data/lib/mysql2/console.rb +5 -0
- data/lib/mysql2/em.rb +24 -8
- data/lib/mysql2/error.rb +93 -8
- data/lib/mysql2/field.rb +3 -0
- data/lib/mysql2/result.rb +2 -0
- data/lib/mysql2/statement.rb +11 -0
- data/lib/mysql2/version.rb +2 -2
- data/spec/configuration.yml.example +11 -0
- data/spec/em/em_spec.rb +101 -15
- data/spec/my.cnf.example +9 -0
- data/spec/mysql2/client_spec.rb +874 -232
- data/spec/mysql2/error_spec.rb +55 -46
- data/spec/mysql2/result_spec.rb +306 -154
- data/spec/mysql2/statement_spec.rb +712 -0
- data/spec/spec_helper.rb +103 -57
- data/spec/ssl/ca-cert.pem +17 -0
- data/spec/ssl/ca-key.pem +27 -0
- data/spec/ssl/ca.cnf +22 -0
- data/spec/ssl/cert.cnf +22 -0
- data/spec/ssl/client-cert.pem +17 -0
- data/spec/ssl/client-key.pem +27 -0
- data/spec/ssl/client-req.pem +15 -0
- data/spec/ssl/gen_certs.sh +48 -0
- data/spec/ssl/pkcs8-client-key.pem +28 -0
- data/spec/ssl/pkcs8-server-key.pem +28 -0
- data/spec/ssl/server-cert.pem +17 -0
- data/spec/ssl/server-key.pem +27 -0
- data/spec/ssl/server-req.pem +15 -0
- data/spec/test_data +1 -0
- data/support/5072E1F5.asc +432 -0
- data/support/libmysql.def +219 -0
- data/support/mysql_enc_to_ruby.rb +81 -0
- data/support/ruby_enc_to_mysql.rb +61 -0
- metadata +82 -188
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.rvmrc +0 -1
- data/Gemfile +0 -3
- data/MIT-LICENSE +0 -20
- data/README.rdoc +0 -257
- data/Rakefile +0 -5
- data/benchmark/active_record.rb +0 -51
- data/benchmark/active_record_threaded.rb +0 -42
- data/benchmark/allocations.rb +0 -33
- data/benchmark/escape.rb +0 -36
- data/benchmark/query_with_mysql_casting.rb +0 -80
- data/benchmark/query_without_mysql_casting.rb +0 -47
- data/benchmark/sequel.rb +0 -37
- data/benchmark/setup_db.rb +0 -119
- data/benchmark/threaded.rb +0 -44
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +0 -64
- data/lib/active_record/fiber_patches.rb +0 -104
- data/lib/mysql2/em_fiber.rb +0 -31
- data/mysql2.gemspec +0 -32
- data/spec/em/em_fiber_spec.rb +0 -22
- data/tasks/benchmarks.rake +0 -20
- data/tasks/compile.rake +0 -71
- data/tasks/rspec.rake +0 -16
- data/tasks/vendor_mysql.rake +0 -40
@@ -1,80 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'benchmark'
|
6
|
-
require 'mysql'
|
7
|
-
require 'mysql2'
|
8
|
-
require 'do_mysql'
|
9
|
-
|
10
|
-
number_of = 100
|
11
|
-
database = 'test'
|
12
|
-
sql = "SELECT * FROM mysql2_test LIMIT 100"
|
13
|
-
|
14
|
-
class Mysql
|
15
|
-
include Enumerable
|
16
|
-
end
|
17
|
-
|
18
|
-
def mysql_cast(type, value)
|
19
|
-
case type
|
20
|
-
when Mysql::Field::TYPE_NULL
|
21
|
-
nil
|
22
|
-
when Mysql::Field::TYPE_TINY, Mysql::Field::TYPE_SHORT, Mysql::Field::TYPE_LONG,
|
23
|
-
Mysql::Field::TYPE_INT24, Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_YEAR
|
24
|
-
value.to_i
|
25
|
-
when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_NEWDECIMAL
|
26
|
-
BigDecimal.new(value)
|
27
|
-
when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT
|
28
|
-
value.to_f
|
29
|
-
when Mysql::Field::TYPE_DATE
|
30
|
-
Date.parse(value)
|
31
|
-
when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP
|
32
|
-
Time.parse(value)
|
33
|
-
when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING,
|
34
|
-
Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET
|
35
|
-
Mysql::Field::TYPE_ENUM
|
36
|
-
value
|
37
|
-
else
|
38
|
-
value
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
Benchmark.bmbm do |x|
|
43
|
-
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
44
|
-
mysql2.query "USE #{database}"
|
45
|
-
x.report "Mysql2" do
|
46
|
-
number_of.times do
|
47
|
-
mysql2_result = mysql2.query sql, :symbolize_keys => true
|
48
|
-
mysql2_result.each do |res|
|
49
|
-
# puts res.inspect
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
mysql = Mysql.new("localhost", "root")
|
55
|
-
mysql.query "USE #{database}"
|
56
|
-
x.report "Mysql" do
|
57
|
-
number_of.times do
|
58
|
-
mysql_result = mysql.query sql
|
59
|
-
fields = mysql_result.fetch_fields
|
60
|
-
mysql_result.each do |row|
|
61
|
-
row_hash = {}
|
62
|
-
row.each_with_index do |f, j|
|
63
|
-
row_hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, row[j])
|
64
|
-
end
|
65
|
-
# puts row_hash.inspect
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
|
71
|
-
command = do_mysql.create_command sql
|
72
|
-
x.report "do_mysql" do
|
73
|
-
number_of.times do
|
74
|
-
do_result = command.execute_reader
|
75
|
-
do_result.each do |res|
|
76
|
-
# puts res.inspect
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'benchmark'
|
6
|
-
require 'mysql'
|
7
|
-
require 'mysql2'
|
8
|
-
require 'do_mysql'
|
9
|
-
|
10
|
-
number_of = 100
|
11
|
-
database = 'test'
|
12
|
-
sql = "SELECT * FROM mysql2_test LIMIT 100"
|
13
|
-
|
14
|
-
Benchmark.bmbm do |x|
|
15
|
-
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
16
|
-
mysql2.query "USE #{database}"
|
17
|
-
x.report "Mysql2" do
|
18
|
-
number_of.times do
|
19
|
-
mysql2_result = mysql2.query sql, :symbolize_keys => true
|
20
|
-
mysql2_result.each do |res|
|
21
|
-
# puts res.inspect
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
mysql = Mysql.new("localhost", "root")
|
27
|
-
mysql.query "USE #{database}"
|
28
|
-
x.report "Mysql" do
|
29
|
-
number_of.times do
|
30
|
-
mysql_result = mysql.query sql
|
31
|
-
mysql_result.each_hash do |res|
|
32
|
-
# puts res.inspect
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
|
38
|
-
command = DataObjects::Mysql::Command.new do_mysql, sql
|
39
|
-
x.report "do_mysql" do
|
40
|
-
number_of.times do
|
41
|
-
do_result = command.execute_reader
|
42
|
-
do_result.each do |res|
|
43
|
-
# puts res.inspect
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
data/benchmark/sequel.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'benchmark'
|
6
|
-
require 'mysql2'
|
7
|
-
require 'sequel'
|
8
|
-
require 'sequel/adapters/do'
|
9
|
-
|
10
|
-
number_of = 10
|
11
|
-
mysql2_opts = "mysql2://localhost/test"
|
12
|
-
mysql_opts = "mysql://localhost/test"
|
13
|
-
do_mysql_opts = "do:mysql://localhost/test"
|
14
|
-
|
15
|
-
class Mysql2Model < Sequel::Model(Sequel.connect(mysql2_opts)[:mysql2_test]); end
|
16
|
-
class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end
|
17
|
-
class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); end
|
18
|
-
|
19
|
-
Benchmark.bmbm do |x|
|
20
|
-
x.report "Mysql2" do
|
21
|
-
number_of.times do
|
22
|
-
Mysql2Model.limit(1000).all
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
x.report "do:mysql" do
|
27
|
-
number_of.times do
|
28
|
-
DOMysqlModel.limit(1000).all
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
x.report "Mysql" do
|
33
|
-
number_of.times do
|
34
|
-
MysqlModel.limit(1000).all
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/benchmark/setup_db.rb
DELETED
@@ -1,119 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
-
|
4
|
-
# This script is for generating psudo-random data into a single table consisting of nearly every
|
5
|
-
# data type MySQL 5.1 supports.
|
6
|
-
#
|
7
|
-
# It's meant to be used with the query.rb benchmark script (or others in the future)
|
8
|
-
|
9
|
-
require 'mysql2'
|
10
|
-
require 'rubygems'
|
11
|
-
require 'faker'
|
12
|
-
|
13
|
-
num = ENV['NUM'] && ENV['NUM'].to_i || 10_000
|
14
|
-
|
15
|
-
create_table_sql = %[
|
16
|
-
CREATE TABLE IF NOT EXISTS mysql2_test (
|
17
|
-
null_test VARCHAR(10),
|
18
|
-
bit_test BIT,
|
19
|
-
tiny_int_test TINYINT,
|
20
|
-
small_int_test SMALLINT,
|
21
|
-
medium_int_test MEDIUMINT,
|
22
|
-
int_test INT,
|
23
|
-
big_int_test BIGINT,
|
24
|
-
float_test FLOAT(10,3),
|
25
|
-
float_zero_test FLOAT(10,3),
|
26
|
-
double_test DOUBLE(10,3),
|
27
|
-
decimal_test DECIMAL(10,3),
|
28
|
-
decimal_zero_test DECIMAL(10,3),
|
29
|
-
date_test DATE,
|
30
|
-
date_time_test DATETIME,
|
31
|
-
timestamp_test TIMESTAMP,
|
32
|
-
time_test TIME,
|
33
|
-
year_test YEAR(4),
|
34
|
-
char_test CHAR(10),
|
35
|
-
varchar_test VARCHAR(10),
|
36
|
-
binary_test BINARY(10),
|
37
|
-
varbinary_test VARBINARY(10),
|
38
|
-
tiny_blob_test TINYBLOB,
|
39
|
-
tiny_text_test TINYTEXT,
|
40
|
-
blob_test BLOB,
|
41
|
-
text_test TEXT,
|
42
|
-
medium_blob_test MEDIUMBLOB,
|
43
|
-
medium_text_test MEDIUMTEXT,
|
44
|
-
long_blob_test LONGBLOB,
|
45
|
-
long_text_test LONGTEXT,
|
46
|
-
enum_test ENUM('val1', 'val2'),
|
47
|
-
set_test SET('val1', 'val2')
|
48
|
-
) DEFAULT CHARSET=utf8
|
49
|
-
]
|
50
|
-
|
51
|
-
# connect to localhost by default, pass options as needed
|
52
|
-
@client = Mysql2::Client.new :host => "localhost", :username => "root", :database => "test"
|
53
|
-
|
54
|
-
@client.query create_table_sql
|
55
|
-
|
56
|
-
def insert_record(args)
|
57
|
-
insert_sql = "
|
58
|
-
INSERT INTO mysql2_test (
|
59
|
-
null_test, bit_test, tiny_int_test, small_int_test, medium_int_test, int_test, big_int_test,
|
60
|
-
float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test,
|
61
|
-
year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test,
|
62
|
-
tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test,
|
63
|
-
long_blob_test, long_text_test, enum_test, set_test
|
64
|
-
)
|
65
|
-
|
66
|
-
VALUES (
|
67
|
-
NULL, #{args[:bit_test]}, #{args[:tiny_int_test]}, #{args[:small_int_test]}, #{args[:medium_int_test]}, #{args[:int_test]}, #{args[:big_int_test]},
|
68
|
-
#{args[:float_test]}, #{args[:float_zero_test]}, #{args[:double_test]}, #{args[:decimal_test]}, #{args[:decimal_zero_test]}, '#{args[:date_test]}', '#{args[:date_time_test]}', '#{args[:timestamp_test]}', '#{args[:time_test]}',
|
69
|
-
#{args[:year_test]}, '#{args[:char_test]}', '#{args[:varchar_test]}', '#{args[:binary_test]}', '#{args[:varbinary_test]}', '#{args[:tiny_blob_test]}',
|
70
|
-
'#{args[:tiny_text_test]}', '#{args[:blob_test]}', '#{args[:text_test]}', '#{args[:medium_blob_test]}', '#{args[:medium_text_test]}',
|
71
|
-
'#{args[:long_blob_test]}', '#{args[:long_text_test]}', '#{args[:enum_test]}', '#{args[:set_test]}'
|
72
|
-
)
|
73
|
-
"
|
74
|
-
@client.query insert_sql
|
75
|
-
end
|
76
|
-
|
77
|
-
puts "Creating #{num} records"
|
78
|
-
num.times do |n|
|
79
|
-
five_words = Faker::Lorem.words(rand(5))
|
80
|
-
twenty5_paragraphs = Faker::Lorem.paragraphs(rand(25))
|
81
|
-
insert_record(
|
82
|
-
:bit_test => 1,
|
83
|
-
:tiny_int_test => rand(128),
|
84
|
-
:small_int_test => rand(32767),
|
85
|
-
:medium_int_test => rand(8388607),
|
86
|
-
:int_test => rand(2147483647),
|
87
|
-
:big_int_test => rand(9223372036854775807),
|
88
|
-
:float_test => rand(32767)/1.87,
|
89
|
-
:float_zero_test => 0.0,
|
90
|
-
:double_test => rand(8388607)/1.87,
|
91
|
-
:decimal_test => rand(8388607)/1.87,
|
92
|
-
:decimal_zero_test => 0,
|
93
|
-
:date_test => '2010-4-4',
|
94
|
-
:date_time_test => '2010-4-4 11:44:00',
|
95
|
-
:timestamp_test => '2010-4-4 11:44:00',
|
96
|
-
:time_test => '11:44:00',
|
97
|
-
:year_test => Time.now.year,
|
98
|
-
:char_test => five_words,
|
99
|
-
:varchar_test => five_words,
|
100
|
-
:binary_test => five_words,
|
101
|
-
:varbinary_test => five_words,
|
102
|
-
:tiny_blob_test => five_words,
|
103
|
-
:tiny_text_test => Faker::Lorem.paragraph(rand(5)),
|
104
|
-
:blob_test => twenty5_paragraphs,
|
105
|
-
:text_test => twenty5_paragraphs,
|
106
|
-
:medium_blob_test => twenty5_paragraphs,
|
107
|
-
:medium_text_test => twenty5_paragraphs,
|
108
|
-
:long_blob_test => twenty5_paragraphs,
|
109
|
-
:long_text_test => twenty5_paragraphs,
|
110
|
-
:enum_test => ['val1', 'val2'].rand,
|
111
|
-
:set_test => ['val1', 'val2', 'val1,val2'].rand
|
112
|
-
)
|
113
|
-
if n % 100 == 0
|
114
|
-
$stdout.putc '.'
|
115
|
-
$stdout.flush
|
116
|
-
end
|
117
|
-
end
|
118
|
-
puts
|
119
|
-
puts "Done"
|
data/benchmark/threaded.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'benchmark'
|
6
|
-
require 'active_record'
|
7
|
-
|
8
|
-
mysql2_opts = {
|
9
|
-
:adapter => 'mysql2',
|
10
|
-
:database => 'test',
|
11
|
-
:pool => 25
|
12
|
-
}
|
13
|
-
ActiveRecord::Base.establish_connection(mysql2_opts)
|
14
|
-
x = Benchmark.realtime do
|
15
|
-
threads = []
|
16
|
-
25.times do
|
17
|
-
threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
|
18
|
-
end
|
19
|
-
threads.each {|t| t.join }
|
20
|
-
end
|
21
|
-
puts x
|
22
|
-
|
23
|
-
mysql2_opts = {
|
24
|
-
:adapter => 'mysql',
|
25
|
-
:database => 'test',
|
26
|
-
:pool => 25
|
27
|
-
}
|
28
|
-
ActiveRecord::Base.establish_connection(mysql2_opts)
|
29
|
-
x = Benchmark.realtime do
|
30
|
-
threads = []
|
31
|
-
25.times do
|
32
|
-
threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
|
33
|
-
end
|
34
|
-
threads.each {|t| t.join }
|
35
|
-
end
|
36
|
-
puts x
|
37
|
-
|
38
|
-
# these results are similar on 1.8.7, 1.9.2 and rbx-head
|
39
|
-
#
|
40
|
-
# $ bundle exec ruby benchmarks/threaded.rb
|
41
|
-
# 1.0774750709533691
|
42
|
-
#
|
43
|
-
# and using the mysql gem
|
44
|
-
# 25.099437952041626
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
# AR adapter for using a fibered mysql2 connection with EM
|
4
|
-
# This adapter should be used within Thin or Unicorn with the rack-fiber_pool middleware.
|
5
|
-
# Just update your database.yml's adapter to be 'em_mysql2'
|
6
|
-
|
7
|
-
module ActiveRecord
|
8
|
-
class Base
|
9
|
-
def self.em_mysql2_connection(config)
|
10
|
-
client = ::Mysql2::Fibered::Client.new(config.symbolize_keys)
|
11
|
-
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
|
12
|
-
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
require 'fiber'
|
18
|
-
require 'eventmachine'
|
19
|
-
require 'mysql2'
|
20
|
-
require 'active_record/connection_adapters/mysql2_adapter'
|
21
|
-
require 'active_record/fiber_patches'
|
22
|
-
|
23
|
-
module Mysql2
|
24
|
-
module Fibered
|
25
|
-
class Client < ::Mysql2::Client
|
26
|
-
module Watcher
|
27
|
-
def initialize(client, deferable)
|
28
|
-
@client = client
|
29
|
-
@deferable = deferable
|
30
|
-
end
|
31
|
-
|
32
|
-
def notify_readable
|
33
|
-
begin
|
34
|
-
detach
|
35
|
-
results = @client.async_result
|
36
|
-
@deferable.succeed(results)
|
37
|
-
rescue Exception => e
|
38
|
-
@deferable.fail(e)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def query(sql, opts={})
|
44
|
-
if ::EM.reactor_running?
|
45
|
-
super(sql, opts.merge(:async => true))
|
46
|
-
deferrable = ::EM::DefaultDeferrable.new
|
47
|
-
::EM.watch(self.socket, Watcher, self, deferrable).notify_readable = true
|
48
|
-
fiber = Fiber.current
|
49
|
-
deferrable.callback do |result|
|
50
|
-
fiber.resume(result)
|
51
|
-
end
|
52
|
-
deferrable.errback do |err|
|
53
|
-
fiber.resume(err)
|
54
|
-
end
|
55
|
-
Fiber.yield.tap do |result|
|
56
|
-
raise result if result.is_a?(Exception)
|
57
|
-
end
|
58
|
-
else
|
59
|
-
super(sql, opts)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,104 +0,0 @@
|
|
1
|
-
# Necessary monkeypatching to make AR fiber-friendly.
|
2
|
-
|
3
|
-
module ActiveRecord
|
4
|
-
module ConnectionAdapters
|
5
|
-
|
6
|
-
def self.fiber_pools
|
7
|
-
@fiber_pools ||= []
|
8
|
-
end
|
9
|
-
def self.register_fiber_pool(fp)
|
10
|
-
fiber_pools << fp
|
11
|
-
end
|
12
|
-
|
13
|
-
class FiberedMonitor
|
14
|
-
class Queue
|
15
|
-
def initialize
|
16
|
-
@queue = []
|
17
|
-
end
|
18
|
-
|
19
|
-
def wait(timeout)
|
20
|
-
t = timeout || 5
|
21
|
-
fiber = Fiber.current
|
22
|
-
x = EM::Timer.new(t) do
|
23
|
-
@queue.delete(fiber)
|
24
|
-
fiber.resume(false)
|
25
|
-
end
|
26
|
-
@queue << fiber
|
27
|
-
Fiber.yield.tap do
|
28
|
-
x.cancel
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def signal
|
33
|
-
fiber = @queue.pop
|
34
|
-
fiber.resume(true) if fiber
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def synchronize
|
39
|
-
yield
|
40
|
-
end
|
41
|
-
|
42
|
-
def new_cond
|
43
|
-
Queue.new
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# ActiveRecord's connection pool is based on threads. Since we are working
|
48
|
-
# with EM and a single thread, multiple fiber design, we need to provide
|
49
|
-
# our own connection pool that keys off of Fiber.current so that different
|
50
|
-
# fibers running in the same thread don't try to use the same connection.
|
51
|
-
class ConnectionPool
|
52
|
-
def initialize(spec)
|
53
|
-
@spec = spec
|
54
|
-
|
55
|
-
# The cache of reserved connections mapped to threads
|
56
|
-
@reserved_connections = {}
|
57
|
-
|
58
|
-
# The mutex used to synchronize pool access
|
59
|
-
@connection_mutex = FiberedMonitor.new
|
60
|
-
@queue = @connection_mutex.new_cond
|
61
|
-
|
62
|
-
# default 5 second timeout unless on ruby 1.9
|
63
|
-
@timeout = spec.config[:wait_timeout] || 5
|
64
|
-
|
65
|
-
# default max pool size to 5
|
66
|
-
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
67
|
-
|
68
|
-
@connections = []
|
69
|
-
@checked_out = []
|
70
|
-
end
|
71
|
-
|
72
|
-
def clear_stale_cached_connections!
|
73
|
-
cache = @reserved_connections
|
74
|
-
keys = Set.new(cache.keys)
|
75
|
-
|
76
|
-
ActiveRecord::ConnectionAdapters.fiber_pools.each do |pool|
|
77
|
-
pool.busy_fibers.each_pair do |object_id, fiber|
|
78
|
-
keys.delete(object_id)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
keys.each do |key|
|
83
|
-
next unless cache.has_key?(key)
|
84
|
-
checkin cache[key]
|
85
|
-
cache.delete(key)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
|
-
def current_connection_id #:nodoc:
|
92
|
-
Fiber.current.object_id
|
93
|
-
end
|
94
|
-
|
95
|
-
def checkout_and_verify(c)
|
96
|
-
@checked_out << c
|
97
|
-
c.run_callbacks :checkout
|
98
|
-
c.verify!
|
99
|
-
c
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
end
|