oldmoe-neverblock 0.1.0 → 0.1.1
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.
- data/lib/active_record/connection_adapters/neverblock_mysql_adapter.rb +78 -0
- data/lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb +97 -0
- data/lib/never_block/db/fibered_mysql_connection.rb +97 -0
- data/lib/never_block/db/fibered_postgres_connection.rb +109 -0
- data/lib/never_block/db/pooled_fibered_mysql_connection.rb +78 -0
- data/lib/never_block/db/pooled_fibered_postgres_connection.rb +81 -0
- data/lib/never_block/extensions/fiber_extensions.rb +8 -3
- data/lib/never_block/frameworks/activerecord.rb +7 -1
- data/lib/never_block/frameworks/rails.rb +2 -1
- data/lib/never_block/pool/fiber_pool.rb +2 -2
- data/lib/never_block/pool/fibered_connection_pool.rb +5 -1
- data/lib/never_block/servers/thin.rb +12 -0
- data/lib/never_block.rb +65 -2
- data/lib/neverblock-mysql.rb +4 -0
- data/lib/neverblock-pg.rb +4 -0
- data/neverblock.gemspec +13 -3
- metadata +10 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'activesupport'
|
|
2
|
+
require 'never_block/frameworks/activerecord'
|
|
3
|
+
require 'active_record/connection_adapters/mysql_adapter'
|
|
4
|
+
require 'neverblock-mysql'
|
|
5
|
+
|
|
6
|
+
class ActiveRecord::ConnectionAdapters::NeverBlockMysqlAdapter < ActiveRecord::ConnectionAdapters::MysqlAdapter
|
|
7
|
+
|
|
8
|
+
# Returns 'NeverBlockMySQL' as adapter name for identification purposes
|
|
9
|
+
def adapter_name
|
|
10
|
+
'NeverBlockMySQL'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
|
14
|
+
begin_db_transaction
|
|
15
|
+
super sql, name
|
|
16
|
+
id_value || @connection.insert_id
|
|
17
|
+
commit_db_transaction
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update_sql(sql, name = nil) #:nodoc:
|
|
21
|
+
begin_db_transaction
|
|
22
|
+
super
|
|
23
|
+
@connection.affected_rows
|
|
24
|
+
commit_db_transaction
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def begin_db_transaction
|
|
28
|
+
@connection.begin_db_transaction
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def commit_db_transaction
|
|
32
|
+
@connection.commit_db_transaction
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def rollback_db_transaction
|
|
36
|
+
@connection.rollback_db_transaction
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def connect
|
|
40
|
+
@connection = ::NB::DB::PooledFiberedMysqlConnection.new(@connection_options.shift) do
|
|
41
|
+
conn = ::NB::DB::FMysql.init
|
|
42
|
+
encoding = @config[:encoding]
|
|
43
|
+
if encoding
|
|
44
|
+
conn.options(::NB::DB::FMysql::SET_CHARSET_NAME, encoding) rescue nil
|
|
45
|
+
end
|
|
46
|
+
conn.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
|
|
47
|
+
conn.real_connect(*@connection_options)
|
|
48
|
+
conn.query("SET NAMES '#{encoding}'") if encoding
|
|
49
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
|
50
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
|
51
|
+
# conn.query("SET SQL_AUTO_IS_NULL=0")
|
|
52
|
+
conn.register_with_event_loop(:em)
|
|
53
|
+
conn
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class ActiveRecord::Base
|
|
60
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
|
61
|
+
def self.neverblock_mysql_connection(config) # :nodoc:
|
|
62
|
+
config = config.symbolize_keys
|
|
63
|
+
host = config[:host]
|
|
64
|
+
port = config[:port]
|
|
65
|
+
socket = config[:socket]
|
|
66
|
+
username = config[:username] ? config[:username].to_s : 'root'
|
|
67
|
+
password = config[:password].to_s
|
|
68
|
+
size = config[:connections] || 4
|
|
69
|
+
|
|
70
|
+
if config.has_key?(:database)
|
|
71
|
+
database = config[:database]
|
|
72
|
+
else
|
|
73
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
|
74
|
+
end
|
|
75
|
+
MysqlCompat.define_all_hashes_method!
|
|
76
|
+
::ActiveRecord::ConnectionAdapters::NeverBlockMysqlAdapter.new(nil, logger, [size.to_i, host, username, password, database, port, socket, nil], config)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
|
2
|
+
require 'neverblock-pg'
|
|
3
|
+
require 'never_block/frameworks/activerecord'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ActiveRecord::ConnectionAdapters::NeverBlockPostgreSQLAdapter < ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
|
7
|
+
# Returns 'FiberedPostgreSQL' as adapter name for identification purposes.
|
|
8
|
+
def adapter_name
|
|
9
|
+
'NeverBlockPostgreSQL'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def begin_db_transaction
|
|
13
|
+
@connection.begin_db_transaction
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def commit_db_transaction
|
|
17
|
+
@connection.commit_db_transaction
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def rollback_db_transaction
|
|
21
|
+
@connection.rollback_db_transaction
|
|
22
|
+
end
|
|
23
|
+
# Executes an INSERT query and returns the new record's ID, this wont
|
|
24
|
+
# work on earlier versions of PostgreSQL but they don't suppor the async
|
|
25
|
+
# interface anyway
|
|
26
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
|
27
|
+
@connection.exec(sql << " returning id ")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def connect
|
|
31
|
+
size = @connection_parameters.shift
|
|
32
|
+
@connection = ::NB::DB::PooledFiberedPostgresConnection.new(@connection_parameters, size)
|
|
33
|
+
|
|
34
|
+
PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
|
|
35
|
+
|
|
36
|
+
# Ignore async_exec and async_query when using postgres-pr.
|
|
37
|
+
@async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
|
|
38
|
+
|
|
39
|
+
# Use escape string syntax if available. We cannot do this lazily when encountering
|
|
40
|
+
# the first string, because that could then break any transactions in progress.
|
|
41
|
+
# See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
|
|
42
|
+
# If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
|
|
43
|
+
# support escape string syntax. Don't override the inherited quoted_string_prefix.
|
|
44
|
+
@connection.begin_db_transaction
|
|
45
|
+
if supports_standard_conforming_strings?
|
|
46
|
+
self.class.instance_eval do
|
|
47
|
+
define_method(:quoted_string_prefix) { 'E' }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
|
52
|
+
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
|
53
|
+
# should know about this but can't detect it there, so deal with it here.
|
|
54
|
+
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
|
55
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.module_eval(<<-end_eval)
|
|
56
|
+
def extract_precision(sql_type)
|
|
57
|
+
if sql_type =~ /^money$/
|
|
58
|
+
#{money_precision}
|
|
59
|
+
else
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end_eval
|
|
64
|
+
|
|
65
|
+
configure_connection
|
|
66
|
+
@connection.commit_db_transaction
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Close then reopen the connection.
|
|
70
|
+
def reconnect!
|
|
71
|
+
disconnect!
|
|
72
|
+
connect
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class ActiveRecord::Base
|
|
78
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
|
79
|
+
def self.neverblock_postgresql_connection(config) # :nodoc:
|
|
80
|
+
config = config.symbolize_keys
|
|
81
|
+
host = config[:host]
|
|
82
|
+
port = config[:port] || 5432
|
|
83
|
+
username = config[:username].to_s
|
|
84
|
+
password = config[:password].to_s
|
|
85
|
+
size = config[:connections] || 4
|
|
86
|
+
|
|
87
|
+
if config.has_key?(:database)
|
|
88
|
+
database = config[:database]
|
|
89
|
+
else
|
|
90
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
|
94
|
+
# so just pass a nil connection object for the time being.
|
|
95
|
+
::ActiveRecord::ConnectionAdapters::NeverBlockPostgreSQLAdapter.new(nil, logger, [size, host, port, nil, nil, database, username, password], config)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'mysql'
|
|
2
|
+
|
|
3
|
+
module NeverBlock
|
|
4
|
+
|
|
5
|
+
module DB
|
|
6
|
+
# A modified postgres connection driver
|
|
7
|
+
# builds on the original pg driver.
|
|
8
|
+
# This driver is able to register the socket
|
|
9
|
+
# at a certain backend (EM or Rev)
|
|
10
|
+
# and then whenever the query is executed
|
|
11
|
+
# within the scope of a friendly fiber
|
|
12
|
+
# it will be done in async mode and the fiber
|
|
13
|
+
# will yield
|
|
14
|
+
class FiberedMysqlConnection < Mysql
|
|
15
|
+
# needed to access the sockect by the event loop
|
|
16
|
+
attr_reader :fd, :io
|
|
17
|
+
|
|
18
|
+
# Creates a new mysql connection, sets it
|
|
19
|
+
# to nonblocking and wraps the descriptor in an IO
|
|
20
|
+
# object.
|
|
21
|
+
def real_connect(*args)
|
|
22
|
+
super(*args)
|
|
23
|
+
@fd = socket
|
|
24
|
+
@io = IO.new(socket)
|
|
25
|
+
end
|
|
26
|
+
#alias :real_connect :initialize
|
|
27
|
+
#alias :connect :initialize
|
|
28
|
+
|
|
29
|
+
# Assuming the use of NeverBlock fiber extensions and that the exec is run in
|
|
30
|
+
# the context of a fiber. One that have the value :neverblock set to true.
|
|
31
|
+
# All neverblock IO classes check this value, setting it to false will force
|
|
32
|
+
# the execution in a blocking way.
|
|
33
|
+
def query(sql)
|
|
34
|
+
if Fiber.respond_to? :current and Fiber.current[:neverblock]
|
|
35
|
+
send_query sql
|
|
36
|
+
@fiber = Fiber.current
|
|
37
|
+
Fiber.yield
|
|
38
|
+
else
|
|
39
|
+
super(sql)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Attaches the connection socket to an event loop.
|
|
44
|
+
# Currently only supports EM, but Rev support will be
|
|
45
|
+
# completed soon.
|
|
46
|
+
def register_with_event_loop(loop)
|
|
47
|
+
if loop == :em
|
|
48
|
+
unless EM.respond_to?(:attach)
|
|
49
|
+
puts "invalide EM version, please download the modified gem from: (http://github.com/riham/eventmachine)"
|
|
50
|
+
exit
|
|
51
|
+
end
|
|
52
|
+
if EM.reactor_running?
|
|
53
|
+
@em_connection = EM::attach(@io,EMConnectionHandler,self)
|
|
54
|
+
else
|
|
55
|
+
raise "REACTOR NOT RUNNING YA ZALAMA"
|
|
56
|
+
end
|
|
57
|
+
elsif loop.class.name == "REV::Loop"
|
|
58
|
+
loop.attach(RevConnectionHandler.new(socket))
|
|
59
|
+
else
|
|
60
|
+
raise "could not register with the event loop"
|
|
61
|
+
end
|
|
62
|
+
@loop = loop
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Unattaches the connection socket from the event loop
|
|
66
|
+
def unregister_from_event_loop
|
|
67
|
+
if @loop == :em
|
|
68
|
+
@em_connection.unattach(false)
|
|
69
|
+
else
|
|
70
|
+
raise NotImplementedError.new("unregister_from_event_loop not implemented for #{@loop}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The callback, this is called whenever
|
|
75
|
+
# there is data available at the socket
|
|
76
|
+
def process_command
|
|
77
|
+
@fiber.resume get_result
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end #FiberedPostgresConnection
|
|
81
|
+
|
|
82
|
+
# A connection handler for EM
|
|
83
|
+
# More to follow.
|
|
84
|
+
module EMConnectionHandler
|
|
85
|
+
def initialize connection
|
|
86
|
+
@connection = connection
|
|
87
|
+
end
|
|
88
|
+
def notify_readable
|
|
89
|
+
@connection.process_command
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end #DB
|
|
94
|
+
|
|
95
|
+
end #NeverBlock
|
|
96
|
+
|
|
97
|
+
NeverBlock::DB::FMysql = NeverBlock::DB::FiberedMysqlConnection
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
require 'pg'
|
|
2
|
+
|
|
3
|
+
module NeverBlock
|
|
4
|
+
|
|
5
|
+
module DB
|
|
6
|
+
# A modified postgres connection driver
|
|
7
|
+
# builds on the original pg driver.
|
|
8
|
+
# This driver is able to register the socket
|
|
9
|
+
# at a certain backend (EM or Rev)
|
|
10
|
+
# and then whenever the query is executed
|
|
11
|
+
# within the scope of a friendly fiber
|
|
12
|
+
# it will be done in async mode and the fiber
|
|
13
|
+
# will yield
|
|
14
|
+
class FiberedPostgresConnection < PGconn
|
|
15
|
+
# needed to access the sockect by the event loop
|
|
16
|
+
attr_reader :fd, :io
|
|
17
|
+
|
|
18
|
+
# Creates a new postgresql connection, sets it
|
|
19
|
+
# to nonblocking and wraps the descriptor in an IO
|
|
20
|
+
# object.
|
|
21
|
+
def initialize(*args)
|
|
22
|
+
super(*args)
|
|
23
|
+
@fd = socket
|
|
24
|
+
@io = IO.new(socket)
|
|
25
|
+
setnonblocking(true)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Assuming the use of NeverBlock fiber extensions and that the exec is run in
|
|
29
|
+
# the context of a fiber. One that have the value :neverblock set to true.
|
|
30
|
+
# All neverblock IO classes check this value, setting it to false will force
|
|
31
|
+
# the execution in a blocking way.
|
|
32
|
+
def exec(sql)
|
|
33
|
+
if Fiber.respond_to? :current and Fiber.current[:neverblock]
|
|
34
|
+
self.send_query sql
|
|
35
|
+
@fiber = Fiber.current
|
|
36
|
+
Fiber.yield
|
|
37
|
+
else
|
|
38
|
+
super(sql)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Attaches the connection socket to an event loop.
|
|
43
|
+
# Currently only supports EM, but Rev support will be
|
|
44
|
+
# completed soon.
|
|
45
|
+
def register_with_event_loop(loop)
|
|
46
|
+
if loop == :em
|
|
47
|
+
unless EM.respond_to?(:attach)
|
|
48
|
+
puts "invalide EM version, please download the modified gem from: (http://github.com/riham/eventmachine)"
|
|
49
|
+
exit
|
|
50
|
+
end
|
|
51
|
+
if EM.reactor_running?
|
|
52
|
+
@em_connection = EM::attach(@io,EMConnectionHandler,self)
|
|
53
|
+
else
|
|
54
|
+
raise "REACTOR NOT RUNNING YA ZALAMA"
|
|
55
|
+
end
|
|
56
|
+
elsif loop.class.name == "REV::Loop"
|
|
57
|
+
loop.attach(RevConnectionHandler.new(socket))
|
|
58
|
+
else
|
|
59
|
+
raise "could not register with the event loop"
|
|
60
|
+
end
|
|
61
|
+
@loop = loop
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Unattaches the connection socket from the event loop
|
|
65
|
+
# As with register, EM is the only one supported for now
|
|
66
|
+
def unregister_from_event_loop
|
|
67
|
+
if @loop == :em
|
|
68
|
+
@em_connection.unattach(false)
|
|
69
|
+
else
|
|
70
|
+
raise NotImplementedError.new("unregister_from_event_loop not implemented for #{@loop}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The callback, this is called whenever
|
|
75
|
+
# there is data available at the socket
|
|
76
|
+
def process_command
|
|
77
|
+
# make sure all commands are sent
|
|
78
|
+
# before attempting to read
|
|
79
|
+
return unless self.flush
|
|
80
|
+
self.consume_input
|
|
81
|
+
unless is_busy
|
|
82
|
+
res, data = 0, []
|
|
83
|
+
while res != nil
|
|
84
|
+
res = self.get_result
|
|
85
|
+
data << res unless res.nil?
|
|
86
|
+
end
|
|
87
|
+
#let the fiber continue its work
|
|
88
|
+
@fiber.resume(data.last)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end #FiberedPostgresConnection
|
|
93
|
+
|
|
94
|
+
# A connection handler for EM
|
|
95
|
+
# More to follow.
|
|
96
|
+
module EMConnectionHandler
|
|
97
|
+
def initialize connection
|
|
98
|
+
@connection = connection
|
|
99
|
+
end
|
|
100
|
+
def notify_readable
|
|
101
|
+
@connection.process_command
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end #DB
|
|
106
|
+
|
|
107
|
+
end #NeverBlock
|
|
108
|
+
|
|
109
|
+
NeverBlock::DB::FPGconn = NeverBlock::DB::FiberedPostgresConnection
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module NeverBlock
|
|
2
|
+
module DB
|
|
3
|
+
# A pooled postgres connection class.
|
|
4
|
+
# This class represents a proxy interface
|
|
5
|
+
# to a connection pool of fibered postgresql
|
|
6
|
+
# connections.
|
|
7
|
+
class PooledFiberedMysqlConnection
|
|
8
|
+
|
|
9
|
+
# Requires a hash or an array with connection parameters
|
|
10
|
+
# and a pool size (defaults to 4)
|
|
11
|
+
def initialize(size=4, &block)
|
|
12
|
+
@pool = NB::Pool::FiberedConnectionPool.new(:size=>size, :eager=>true) do
|
|
13
|
+
yield
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# A proxy for the connection's exec method
|
|
18
|
+
# quries the pool to get a connection first
|
|
19
|
+
def exec(query)
|
|
20
|
+
@pool.hold do |conn|
|
|
21
|
+
conn.query(query)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
alias :query :exec
|
|
26
|
+
# This method must be called for transactions to work correctly.
|
|
27
|
+
# One cannot just send "begin" as you never know which connection
|
|
28
|
+
# will be available next. This method ensures you get the same connection
|
|
29
|
+
# while in a transaction.
|
|
30
|
+
def begin_db_transaction
|
|
31
|
+
@pool.hold(true) do |conn|
|
|
32
|
+
conn.exec("begin")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# see =begin_db_transaction
|
|
37
|
+
def rollback_db_transaction
|
|
38
|
+
@pool.hold do |conn|
|
|
39
|
+
conn.exec("rollback")
|
|
40
|
+
@pool.release(Fiber.current,conn)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# see =begin_db_transaction
|
|
45
|
+
def commit_db_transaction
|
|
46
|
+
@pool.hold do |conn|
|
|
47
|
+
conn.exec("commit")
|
|
48
|
+
@pool.release(Fiber.current,conn)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#close all connections and remove them from the event loop
|
|
53
|
+
def close
|
|
54
|
+
@pool.all_connections do |conn|
|
|
55
|
+
conn.unregister_from_event_loop
|
|
56
|
+
conn.close
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Pass unknown methods to the connection
|
|
61
|
+
def method_missing(method, *args)
|
|
62
|
+
@pool.hold do |conn|
|
|
63
|
+
conn.send(method, *args)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Pass method queries to the connection
|
|
68
|
+
def respond_to?(method)
|
|
69
|
+
@pool.hold do |conn|
|
|
70
|
+
conn.respond_to?(method)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
NeverBlock::DB::PFMysql = NeverBlock::DB::PooledFiberedMysqlConnection
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module NeverBlock
|
|
2
|
+
module DB
|
|
3
|
+
# A pooled postgres connection class.
|
|
4
|
+
# This class represents a proxy interface
|
|
5
|
+
# to a connection pool of fibered postgresql
|
|
6
|
+
# connections.
|
|
7
|
+
class PooledFiberedPostgresConnection
|
|
8
|
+
|
|
9
|
+
# Requires a hash or an array with connection parameters
|
|
10
|
+
# and a pool size (defaults to 4)
|
|
11
|
+
def initialize(conn_params, size=4)
|
|
12
|
+
@pool = NB::Pool::FiberedConnectionPool.new(:size=>size, :eager=>true) do
|
|
13
|
+
conn = NB::DB::FPGconn.new(*conn_params) if conn_params.is_a? Array
|
|
14
|
+
conn = NB::DB::FPGconn.new(conn_params) if conn_params.is_a? Hash
|
|
15
|
+
conn.register_with_event_loop(:em)
|
|
16
|
+
conn
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# A proxy for the connection's exec method
|
|
21
|
+
# quries the pool to get a connection first
|
|
22
|
+
def exec(query)
|
|
23
|
+
@pool.hold do |conn|
|
|
24
|
+
conn.exec(query)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# This method must be called for transactions to work correctly.
|
|
29
|
+
# One cannot just send "begin" as you never know which connection
|
|
30
|
+
# will be available next. This method ensures you get the same connection
|
|
31
|
+
# while in a transaction.
|
|
32
|
+
def begin_db_transaction
|
|
33
|
+
@pool.hold(true) do |conn|
|
|
34
|
+
conn.exec("begin")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# see =begin_db_transaction
|
|
39
|
+
def rollback_db_transaction
|
|
40
|
+
@pool.hold do |conn|
|
|
41
|
+
conn.exec("rollback")
|
|
42
|
+
@pool.release(Fiber.current,conn)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# see =begin_db_transaction
|
|
47
|
+
def commit_db_transaction
|
|
48
|
+
@pool.hold do |conn|
|
|
49
|
+
conn.exec("commit")
|
|
50
|
+
@pool.release(Fiber.current,conn)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#close all connections and remove them from the event loop
|
|
55
|
+
def close
|
|
56
|
+
@pool.all_connections do |conn|
|
|
57
|
+
conn.unregister_from_event_loop
|
|
58
|
+
conn.close
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Pass unknown methods to the connection
|
|
63
|
+
def method_missing(method, *args)
|
|
64
|
+
@pool.hold do |conn|
|
|
65
|
+
conn.send(method, *args)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Pass method queries to the connection
|
|
70
|
+
def respond_to?(method)
|
|
71
|
+
@pool.hold do |conn|
|
|
72
|
+
conn.respond_to?(method)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
NB::DB::PFPGconn = NeverBlock::DB::FiberedPostgresConnection
|
|
81
|
+
|
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
|
3
3
|
# License:: Distributes under the same terms as Ruby
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# If this file is meant to be used out of neverblock, then uncomment
|
|
6
|
+
# the following line
|
|
7
|
+
#require 'fiber'
|
|
6
8
|
|
|
7
9
|
class Fiber
|
|
8
10
|
|
|
9
|
-
#Attribute Reference--Returns the value of a fiber-local variable, using
|
|
11
|
+
#Attribute Reference--Returns the value of a fiber-local variable, using
|
|
12
|
+
#either a symbol or a string name. If the specified variable does not exist,
|
|
13
|
+
#returns nil.
|
|
10
14
|
def [](key)
|
|
11
15
|
local_fiber_variables[key]
|
|
12
16
|
end
|
|
13
17
|
|
|
14
|
-
#Attribute Assignment--Sets or creates the value of a fiber-local variable,
|
|
18
|
+
#Attribute Assignment--Sets or creates the value of a fiber-local variable,
|
|
19
|
+
#using either a symbol or a string. See also Fiber#[].
|
|
15
20
|
def []=(key,value)
|
|
16
21
|
local_fiber_variables[key] = value
|
|
17
22
|
end
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
require 'never_block/frameworks/rails'
|
|
2
1
|
require 'activerecord'
|
|
3
2
|
|
|
4
3
|
# Patch ActiveRecord to store transaction depth information
|
|
5
4
|
# in fibers instead of threads. AR does not support nested
|
|
6
5
|
# transactions which makes the job easy.
|
|
6
|
+
# We also need to override the scoped methods to store
|
|
7
|
+
# the scope in the fiber context
|
|
7
8
|
class ActiveRecord::Base
|
|
8
9
|
|
|
10
|
+
def single_threaded_scoped_methods #:nodoc:
|
|
11
|
+
scoped_methods = (Fiber.current[:scoped_methods] ||= {})
|
|
12
|
+
scoped_methods[self] ||= []
|
|
13
|
+
end
|
|
14
|
+
|
|
9
15
|
def self.transaction(&block)
|
|
10
16
|
increment_open_transactions
|
|
11
17
|
begin
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
|
6
6
|
# License:: Distributes under the same terms as Ruby
|
|
7
7
|
|
|
8
|
-
require 'fiber'
|
|
8
|
+
#require 'fiber'
|
|
9
9
|
|
|
10
10
|
module NeverBlock
|
|
11
11
|
module Pool
|
|
@@ -64,7 +64,7 @@ module NeverBlock
|
|
|
64
64
|
# use it, otherwise, leave it to linger in a queue
|
|
65
65
|
def spawn(evented = true, &block)
|
|
66
66
|
if fiber = @fibers.shift
|
|
67
|
-
fiber[:
|
|
67
|
+
fiber[:neverblock] = evented
|
|
68
68
|
fiber.resume(block)
|
|
69
69
|
else
|
|
70
70
|
@queue << block
|
|
@@ -31,4 +31,16 @@ module Thin
|
|
|
31
31
|
|
|
32
32
|
end # Connection
|
|
33
33
|
|
|
34
|
+
module Backends
|
|
35
|
+
class Base
|
|
36
|
+
def config
|
|
37
|
+
# EM.epoll
|
|
38
|
+
# Set the maximum number of socket descriptors that the server may open.
|
|
39
|
+
# The process needs to have required privilege to set it higher the 1024 on
|
|
40
|
+
# some systems.
|
|
41
|
+
@maximum_connections = EventMachine.set_descriptor_table_size(@maximum_connections) unless Thin.win?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end # Backends
|
|
45
|
+
|
|
34
46
|
end # Thin
|
data/lib/never_block.rb
CHANGED
|
@@ -4,7 +4,70 @@
|
|
|
4
4
|
|
|
5
5
|
$:.unshift File.expand_path(File.dirname(__FILE__))
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
unless defined? Fiber
|
|
8
|
+
require 'thread'
|
|
9
|
+
require 'singleton'
|
|
10
|
+
class FiberError < StandardError; end
|
|
11
|
+
class Fiber
|
|
12
|
+
def initialize
|
|
13
|
+
raise ArgumentError, 'new Fiber requires a block' unless block_given?
|
|
14
|
+
|
|
15
|
+
@yield = Queue.new
|
|
16
|
+
@resume = Queue.new
|
|
17
|
+
|
|
18
|
+
@thread = Thread.new{ @yield.push [ *yield(*@resume.pop) ] }
|
|
19
|
+
@thread.abort_on_exception = true
|
|
20
|
+
@thread[:fiber] = self
|
|
21
|
+
end
|
|
22
|
+
attr_reader :thread
|
|
23
|
+
|
|
24
|
+
def resume *args
|
|
25
|
+
raise FiberError, 'dead fiber called' unless @thread.alive?
|
|
26
|
+
@resume.push(args)
|
|
27
|
+
result = @yield.pop
|
|
28
|
+
result.size > 1 ? result : result.first
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def yield *args
|
|
32
|
+
@yield.push(args)
|
|
33
|
+
result = @resume.pop
|
|
34
|
+
result.size > 1 ? result : result.first
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.yield *args
|
|
38
|
+
raise FiberError, "can't yield from root fiber" unless fiber = Thread.current[:fiber]
|
|
39
|
+
fiber.yield(*args)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.current
|
|
43
|
+
Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def inspect
|
|
47
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}>"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class RootFiber < Fiber
|
|
52
|
+
include Singleton
|
|
53
|
+
def initialize
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def resume *args
|
|
57
|
+
raise FiberError, "can't resume root fiber"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def yield *args
|
|
61
|
+
raise FiberError, "can't yield from root fiber"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#attach the root fiber to the main thread
|
|
66
|
+
Thread.main[:fiber] = RootFiber.instance
|
|
67
|
+
else
|
|
68
|
+
require 'fiber'
|
|
69
|
+
end
|
|
70
|
+
|
|
8
71
|
require 'never_block/extensions/fiber_extensions'
|
|
9
72
|
require 'never_block/pool/fiber_pool'
|
|
10
73
|
require 'never_block/pool/fibered_connection_pool'
|
|
@@ -12,4 +75,4 @@ require 'never_block/pool/fibered_connection_pool'
|
|
|
12
75
|
module NeverBlock
|
|
13
76
|
end
|
|
14
77
|
|
|
15
|
-
NB = NeverBlock
|
|
78
|
+
NB = NeverBlock
|
data/neverblock.gemspec
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
Gem::Specification.new do |s|
|
|
2
2
|
s.name = "neverblock"
|
|
3
|
-
s.version = "0.1.
|
|
3
|
+
s.version = "0.1.1"
|
|
4
4
|
s.date = "2008-08-13"
|
|
5
5
|
s.summary = "Utilities for non-blocking stack components"
|
|
6
6
|
s.email = "oldmoe@gmail.com"
|
|
7
7
|
s.homepage = "http://github.com/oldmoe/neverblock"
|
|
8
8
|
s.description = "NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner."
|
|
9
9
|
s.has_rdoc = true
|
|
10
|
-
s.authors = ["Muhammad A. Ali", "Ahmed Sobhi"]
|
|
10
|
+
s.authors = ["Muhammad A. Ali", "Ahmed Sobhi", "Osama Brekaa"]
|
|
11
11
|
s.files = [
|
|
12
12
|
"neverblock.gemspec",
|
|
13
13
|
"README",
|
|
14
14
|
"lib/neverblock.rb",
|
|
15
15
|
"lib/never_block.rb",
|
|
16
|
+
"lib/neverblock-pg.rb",
|
|
17
|
+
"lib/neverblock-mysql.rb",
|
|
16
18
|
"lib/never_block/extensions/fiber_extensions.rb",
|
|
17
19
|
"lib/never_block/pool/fiber_pool.rb",
|
|
18
20
|
"lib/never_block/pool/fibered_connection_pool.rb",
|
|
19
21
|
"lib/never_block/frameworks/rails.rb",
|
|
20
22
|
"lib/never_block/frameworks/activerecord.rb",
|
|
21
|
-
"lib/never_block/servers/thin.rb"
|
|
23
|
+
"lib/never_block/servers/thin.rb",
|
|
24
|
+
"lib/never_block/db/fibered_postgres_connection.rb",
|
|
25
|
+
"lib/never_block/db/pooled_fibered_postgres_connection.rb",
|
|
26
|
+
"lib/never_block/db/fibered_mysql_connection.rb",
|
|
27
|
+
"lib/never_block/db/pooled_fibered_mysql_connection.rb",
|
|
28
|
+
"lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb",
|
|
29
|
+
"lib/active_record/connection_adapters/neverblock_mysql_adapter.rb"
|
|
30
|
+
]
|
|
22
31
|
s.rdoc_options = ["--main", "README"]
|
|
23
32
|
s.extra_rdoc_files = ["README"]
|
|
24
33
|
end
|
|
25
34
|
|
|
35
|
+
|
metadata
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: oldmoe-neverblock
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Muhammad A. Ali
|
|
8
8
|
- Ahmed Sobhi
|
|
9
|
+
- Osama Brekaa
|
|
9
10
|
autorequire:
|
|
10
11
|
bindir: bin
|
|
11
12
|
cert_chain: []
|
|
@@ -27,12 +28,20 @@ files:
|
|
|
27
28
|
- README
|
|
28
29
|
- lib/neverblock.rb
|
|
29
30
|
- lib/never_block.rb
|
|
31
|
+
- lib/neverblock-pg.rb
|
|
32
|
+
- lib/neverblock-mysql.rb
|
|
30
33
|
- lib/never_block/extensions/fiber_extensions.rb
|
|
31
34
|
- lib/never_block/pool/fiber_pool.rb
|
|
32
35
|
- lib/never_block/pool/fibered_connection_pool.rb
|
|
33
36
|
- lib/never_block/frameworks/rails.rb
|
|
34
37
|
- lib/never_block/frameworks/activerecord.rb
|
|
35
38
|
- lib/never_block/servers/thin.rb
|
|
39
|
+
- lib/never_block/db/fibered_postgres_connection.rb
|
|
40
|
+
- lib/never_block/db/pooled_fibered_postgres_connection.rb
|
|
41
|
+
- lib/never_block/db/fibered_mysql_connection.rb
|
|
42
|
+
- lib/never_block/db/pooled_fibered_mysql_connection.rb
|
|
43
|
+
- lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb
|
|
44
|
+
- lib/active_record/connection_adapters/neverblock_mysql_adapter.rb
|
|
36
45
|
has_rdoc: true
|
|
37
46
|
homepage: http://github.com/oldmoe/neverblock
|
|
38
47
|
post_install_message:
|