espace-neverblock 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +21 -0
- data/lib/active_record/connection_adapters/neverblock_mysql_adapter.rb +80 -0
- data/lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb +102 -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 +30 -0
- data/lib/never_block/frameworks/activerecord.rb +37 -0
- data/lib/never_block/frameworks/rails.rb +37 -0
- data/lib/never_block/pool/fiber_pool.rb +76 -0
- data/lib/never_block/pool/fibered_connection_pool.rb +118 -0
- data/lib/never_block/servers/thin.rb +37 -0
- data/lib/never_block.rb +84 -0
- data/lib/neverblock-mysql.rb +4 -0
- data/lib/neverblock-pg.rb +4 -0
- data/lib/neverblock.rb +8 -0
- data/neverblock.gemspec +35 -0
- metadata +73 -0
data/README
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== NeverBlock
|
2
|
+
Never, ever!
|
3
|
+
|
4
|
+
NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
|
5
|
+
|
6
|
+
NeverBlock currently provides the following Libraries:
|
7
|
+
|
8
|
+
=== FiberExtensions
|
9
|
+
A set of extenstions to the standard Fiber implementation
|
10
|
+
|
11
|
+
=== NeverBlock::Pool::FiberPool
|
12
|
+
A pool of fibers that can be used to provide an upper limit to the numbers of active fibers in an application
|
13
|
+
|
14
|
+
=== NeverBlock::Pool::FiberedConnectionPool
|
15
|
+
A generic fibered connection pool for all sorts of connections with support for transactions. This was mostly copied from Sequel::ConnectionPool
|
16
|
+
|
17
|
+
NeverBlock should be the basis for providing completely async Ruby application development that does not require the usual twisted style of evented programming. For example, you will be able to develop in Rails in the usual style and deploy to a NeverBlock server which will do all the IO in an evented manner without you even noticing this.
|
18
|
+
|
19
|
+
=== License
|
20
|
+
Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
|
21
|
+
|
@@ -0,0 +1,80 @@
|
|
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
|
+
NB.neverblock(false) do
|
49
|
+
conn.query("SET NAMES '#{encoding}'") if encoding
|
50
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
51
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
52
|
+
conn.query("SET SQL_AUTO_IS_NULL=0")
|
53
|
+
end
|
54
|
+
conn.register_with_event_loop(:em)
|
55
|
+
conn
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class ActiveRecord::Base
|
62
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
63
|
+
def self.neverblock_mysql_connection(config) # :nodoc:
|
64
|
+
config = config.symbolize_keys
|
65
|
+
host = config[:host]
|
66
|
+
port = config[:port]
|
67
|
+
socket = config[:socket]
|
68
|
+
username = config[:username] ? config[:username].to_s : 'root'
|
69
|
+
password = config[:password].to_s
|
70
|
+
size = config[:connections] || 4
|
71
|
+
|
72
|
+
if config.has_key?(:database)
|
73
|
+
database = config[:database]
|
74
|
+
else
|
75
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
76
|
+
end
|
77
|
+
MysqlCompat.define_all_hashes_method!
|
78
|
+
::ActiveRecord::ConnectionAdapters::NeverBlockMysqlAdapter.new(nil, logger, [size.to_i, host, username, password, database, port, socket, nil], config)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,102 @@
|
|
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
|
+
NB.neverblock(false) do
|
45
|
+
|
46
|
+
@connection.begin_db_transaction
|
47
|
+
if supports_standard_conforming_strings?
|
48
|
+
self.class.instance_eval do
|
49
|
+
define_method(:quoted_string_prefix) { 'E' }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
54
|
+
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
55
|
+
# should know about this but can't detect it there, so deal with it here.
|
56
|
+
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
57
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.module_eval(<<-end_eval)
|
58
|
+
def extract_precision(sql_type)
|
59
|
+
if sql_type =~ /^money$/
|
60
|
+
#{money_precision}
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end_eval
|
66
|
+
|
67
|
+
configure_connection
|
68
|
+
@connection.commit_db_transaction
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# Close then reopen the connection.
|
75
|
+
def reconnect!
|
76
|
+
disconnect!
|
77
|
+
connect
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
class ActiveRecord::Base
|
83
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
84
|
+
def self.neverblock_postgresql_connection(config) # :nodoc:
|
85
|
+
config = config.symbolize_keys
|
86
|
+
host = config[:host]
|
87
|
+
port = config[:port] || 5432
|
88
|
+
username = config[:username].to_s
|
89
|
+
password = config[:password].to_s
|
90
|
+
size = config[:connections] || 4
|
91
|
+
|
92
|
+
if config.has_key?(:database)
|
93
|
+
database = config[:database]
|
94
|
+
else
|
95
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
96
|
+
end
|
97
|
+
|
98
|
+
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
99
|
+
# so just pass a nil connection object for the time being.
|
100
|
+
::ActiveRecord::ConnectionAdapters::NeverBlockPostgreSQLAdapter.new(nil, logger, [size, host, port, nil, nil, database, username, password], config)
|
101
|
+
end
|
102
|
+
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
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
3
|
+
# License:: Distributes under the same terms as Ruby
|
4
|
+
|
5
|
+
# If this file is meant to be used out of neverblock, then uncomment
|
6
|
+
# the following line
|
7
|
+
#require 'fiber'
|
8
|
+
|
9
|
+
class Fiber
|
10
|
+
|
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.
|
14
|
+
def [](key)
|
15
|
+
local_fiber_variables[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
#Attribute Assignment--Sets or creates the value of a fiber-local variable,
|
19
|
+
#using either a symbol or a string. See also Fiber#[].
|
20
|
+
def []=(key,value)
|
21
|
+
local_fiber_variables[key] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def local_fiber_variables
|
27
|
+
@local_fiber_variables ||= {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'activerecord'
|
2
|
+
|
3
|
+
# Patch ActiveRecord to store transaction depth information
|
4
|
+
# in fibers instead of threads. AR does not support nested
|
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
|
8
|
+
class ActiveRecord::Base
|
9
|
+
|
10
|
+
def single_threaded_scoped_methods #:nodoc:
|
11
|
+
scoped_methods = (Fiber.current[:scoped_methods] ||= {})
|
12
|
+
scoped_methods[self] ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.transaction(&block)
|
16
|
+
increment_open_transactions
|
17
|
+
begin
|
18
|
+
connection.transaction(Fiber.current['start_db_transaction'], &block)
|
19
|
+
ensure
|
20
|
+
decrement_open_transactions
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def self.increment_open_transactions #:nodoc:
|
27
|
+
open = Fiber.current['open_transactions'] ||= 0
|
28
|
+
Fiber.current['start_db_transaction'] = open.zero?
|
29
|
+
Fiber.current['open_transactions'] = open + 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.decrement_open_transactions #:nodoc:
|
33
|
+
Fiber.current['open_transactions'] -= 1
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'neverblock' unless defined?(NeverBlock)
|
2
|
+
#require 'actionpack'
|
3
|
+
#require 'action_controller'
|
4
|
+
|
5
|
+
# Rails tries to protect dispatched actions
|
6
|
+
# by wrapping them in a synchronized code
|
7
|
+
# block, since fibers hate synchronized
|
8
|
+
# blocks we will trick the guard and
|
9
|
+
# transform it (without it knowing) to
|
10
|
+
# something more subtle
|
11
|
+
|
12
|
+
|
13
|
+
=begin
|
14
|
+
class ActionController::Dispatcher
|
15
|
+
|
16
|
+
# let's show this guard who is
|
17
|
+
# the man of the house
|
18
|
+
@@guard = Object.new
|
19
|
+
|
20
|
+
# now you synchronize
|
21
|
+
def @@guard.synchronize(&block)
|
22
|
+
# now you don't!
|
23
|
+
block.call
|
24
|
+
end
|
25
|
+
end
|
26
|
+
=end
|
27
|
+
|
28
|
+
|
29
|
+
require 'thread'
|
30
|
+
|
31
|
+
# now you synchronize
|
32
|
+
class Mutex
|
33
|
+
def synchronize(&block)
|
34
|
+
# now you don't!
|
35
|
+
block.call
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# we need Fiber.current
|
2
|
+
# so we must require
|
3
|
+
# fiber
|
4
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
5
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
6
|
+
# License:: Distributes under the same terms as Ruby
|
7
|
+
|
8
|
+
#require 'fiber'
|
9
|
+
|
10
|
+
module NeverBlock
|
11
|
+
module Pool
|
12
|
+
|
13
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
14
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
15
|
+
# License:: Distributes under the same terms as Ruby
|
16
|
+
#
|
17
|
+
# A pool of initialized fibers
|
18
|
+
# It does not grow in size or create transient fibers
|
19
|
+
# It will queue code blocks when needed (if all its fibers are busy)
|
20
|
+
#
|
21
|
+
# This class is particulary useful when you use the fibers
|
22
|
+
# to connect to evented back ends. It also does not generate
|
23
|
+
# transient objects and thus saves memory.
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
# fiber_pool = NeverBlock::Pool::FiberPool.new(150)
|
27
|
+
#
|
28
|
+
# loop do
|
29
|
+
# fiber_pool.spawn do
|
30
|
+
# #fiber body goes here
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class FiberPool
|
35
|
+
|
36
|
+
# gives access to the currently free fibers
|
37
|
+
attr_reader :fibers
|
38
|
+
|
39
|
+
# Prepare a list of fibers
|
40
|
+
# that are able to run different
|
41
|
+
# blocks of code every time
|
42
|
+
# once a fiber is done with its block
|
43
|
+
# it attempts to fetch another one
|
44
|
+
# from the queue.
|
45
|
+
def initialize(count = 50)
|
46
|
+
@fibers,@queue = [],[]
|
47
|
+
count.times do |i|
|
48
|
+
fiber = Fiber.new do |block|
|
49
|
+
loop do
|
50
|
+
block.call
|
51
|
+
unless @queue.empty?
|
52
|
+
block = @queue.shift
|
53
|
+
else
|
54
|
+
block = Fiber.yield @fibers << Fiber.current
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
fiber[:neverblock] = true
|
59
|
+
@fibers << fiber
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# If there is an available fiber
|
64
|
+
# use it, otherwise, leave it to linger in a queue
|
65
|
+
def spawn(evented = true, &block)
|
66
|
+
if fiber = @fibers.shift
|
67
|
+
fiber[:neverblock] = evented
|
68
|
+
fiber.resume(block)
|
69
|
+
else
|
70
|
+
@queue << block
|
71
|
+
end
|
72
|
+
self # we are keen on hiding our queue
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
3
|
+
# License:: Distributes under the same terms as Ruby
|
4
|
+
|
5
|
+
module NeverBlock
|
6
|
+
module Pool
|
7
|
+
|
8
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
9
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
10
|
+
# License:: Distributes under the same terms as Ruby
|
11
|
+
#
|
12
|
+
# This class represents a pool of connections,
|
13
|
+
# you hold or release conncetions from the pool
|
14
|
+
# hold requests that cannot be fullfiled will be queued
|
15
|
+
# the fiber will be paused and resumed later when
|
16
|
+
# a connection is avaialble
|
17
|
+
#
|
18
|
+
# Large portions of this class were copied and pasted
|
19
|
+
# form Sequel's threaded connection pool
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# pool = NeverBlock::Pool::FiberedConnectionPool.new(:size=>16)do
|
24
|
+
# # connection creation code goes here
|
25
|
+
# end
|
26
|
+
# 32.times do
|
27
|
+
# Fiber.new do
|
28
|
+
# conn = pool.hold # hold will pause the fiber until a connection is available
|
29
|
+
# conn.execute('something') # you can use the connection normally now
|
30
|
+
# end.resume
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# The pool has support for transactions, just pass true to the pool#hold method
|
34
|
+
# and the connection will not be released after the block is finished
|
35
|
+
# It is the responsibility of client code to release the connection
|
36
|
+
class FiberedConnectionPool
|
37
|
+
|
38
|
+
# initialize the connection pool
|
39
|
+
# using the supplied proc to create the connections
|
40
|
+
# you can choose to start them eagerly or lazily (lazy by default)
|
41
|
+
def initialize(options = {}, &block)
|
42
|
+
@connections, @busy_connections, @queue = [], {},[]
|
43
|
+
@connection_proc = block
|
44
|
+
@size = options[:size] || 8
|
45
|
+
if options[:eager]
|
46
|
+
@size.times do
|
47
|
+
@connections << @connection_proc.call
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# If a connection is available,
|
53
|
+
# pass it to the block, otherwise
|
54
|
+
# pass the fiber to the queue
|
55
|
+
# till a connection is available
|
56
|
+
# when done with a connection
|
57
|
+
# try to porcess other fibers in the queue
|
58
|
+
# before releasing the connection
|
59
|
+
# if inside a transaction, don't release the fiber
|
60
|
+
def hold(transactional = false)
|
61
|
+
fiber = Fiber.current
|
62
|
+
if conn = @busy_connections[fiber]
|
63
|
+
return yield(conn)
|
64
|
+
end
|
65
|
+
conn = acquire(fiber)
|
66
|
+
begin
|
67
|
+
yield conn
|
68
|
+
ensure
|
69
|
+
release(fiber, conn) unless transactional
|
70
|
+
process_queue
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Give the fiber back to the pool
|
75
|
+
# you have to call this explicitly if
|
76
|
+
# you held a connection for a transaction
|
77
|
+
def release(fiber, conn)
|
78
|
+
@busy_connections.delete(fiber)
|
79
|
+
@connections << conn
|
80
|
+
end
|
81
|
+
|
82
|
+
def all_connections
|
83
|
+
(@connections + @busy_connections.values).each {|conn| yield(conn)}
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Can we find a connection?
|
89
|
+
# Can we create one?
|
90
|
+
# Wait in the queue then
|
91
|
+
def acquire(fiber)
|
92
|
+
if !@connections.empty?
|
93
|
+
@busy_connections[fiber] = @connections.shift
|
94
|
+
elsif (@connections.length + @busy_connections.length) < @size
|
95
|
+
conn = @connection_proc.call
|
96
|
+
@busy_connections[fiber] = conn
|
97
|
+
else
|
98
|
+
Fiber.yield @queue << fiber
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Check if there are waiting fibers and
|
103
|
+
# try to process them
|
104
|
+
def process_queue
|
105
|
+
while !@connections.empty? and !@queue.empty?
|
106
|
+
fiber = @queue.shift
|
107
|
+
# What is really happening here?
|
108
|
+
# we are resuming a fiber from within
|
109
|
+
# another, should we call transfer insted?
|
110
|
+
fiber.resume @busy_connections[fiber] = @connections.shift
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end #FiberedConnectionPool
|
115
|
+
|
116
|
+
end #Pool
|
117
|
+
|
118
|
+
end #NeverBlock
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'neverblock' unless defined?(NeverBlock)
|
3
|
+
require 'thin'
|
4
|
+
|
5
|
+
module Thin
|
6
|
+
|
7
|
+
# Patch the thin server to use
|
8
|
+
# NeverBlock::Pool::FiberPool
|
9
|
+
# to be able to wrap requests
|
10
|
+
# in fibers
|
11
|
+
class Server
|
12
|
+
|
13
|
+
DEFAULT_FIBER_POOL_SIZE = 20
|
14
|
+
|
15
|
+
def fiber_pool
|
16
|
+
@fiber_pool ||= NB::Pool::FiberPool.new(DEFAULT_FIBER_POOL_SIZE)
|
17
|
+
end
|
18
|
+
|
19
|
+
end # Server
|
20
|
+
|
21
|
+
# A request is processed by wrapping it
|
22
|
+
# in a fiber from the fiber pool. If all
|
23
|
+
# the fibers are busy the request will
|
24
|
+
# wait in a queue to be picked up later.
|
25
|
+
# Meanwhile, the server will still be
|
26
|
+
# processing requests
|
27
|
+
class Connection < EventMachine::Connection
|
28
|
+
|
29
|
+
def process
|
30
|
+
@request.threaded = false
|
31
|
+
@backend.server.fiber_pool.spawn{post_process(pre_process)}
|
32
|
+
end
|
33
|
+
|
34
|
+
end # Connection
|
35
|
+
|
36
|
+
|
37
|
+
end # Thin
|
data/lib/never_block.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
3
|
+
# License:: Distributes under the same terms as Ruby
|
4
|
+
|
5
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
6
|
+
|
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
|
+
|
71
|
+
require 'never_block/extensions/fiber_extensions'
|
72
|
+
require 'never_block/pool/fiber_pool'
|
73
|
+
require 'never_block/pool/fibered_connection_pool'
|
74
|
+
|
75
|
+
module NeverBlock
|
76
|
+
def self.neverblock(nb = true, &block)
|
77
|
+
status = Fiber.current[:neverblock]
|
78
|
+
Fiber.current[:neverblock] = nb
|
79
|
+
block.call
|
80
|
+
Fiber.current[:neverblock] = status
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
NB = NeverBlock
|
data/lib/neverblock.rb
ADDED
data/neverblock.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "neverblock"
|
3
|
+
s.version = "0.1.2"
|
4
|
+
s.date = "2008-09-04"
|
5
|
+
s.summary = "Utilities for non-blocking stack components"
|
6
|
+
s.email = "oldmoe@gmail.com"
|
7
|
+
s.homepage = "http://github.com/oldmoe/neverblock"
|
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
|
+
s.has_rdoc = true
|
10
|
+
s.authors = ["Muhammad A. Ali", "Ahmed Sobhi", "Osama Brekaa"]
|
11
|
+
s.files = [
|
12
|
+
"neverblock.gemspec",
|
13
|
+
"README",
|
14
|
+
"lib/neverblock.rb",
|
15
|
+
"lib/never_block.rb",
|
16
|
+
"lib/neverblock-pg.rb",
|
17
|
+
"lib/neverblock-mysql.rb",
|
18
|
+
"lib/never_block/extensions/fiber_extensions.rb",
|
19
|
+
"lib/never_block/pool/fiber_pool.rb",
|
20
|
+
"lib/never_block/pool/fibered_connection_pool.rb",
|
21
|
+
"lib/never_block/frameworks/rails.rb",
|
22
|
+
"lib/never_block/frameworks/activerecord.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
|
+
]
|
31
|
+
s.rdoc_options = ["--main", "README"]
|
32
|
+
s.extra_rdoc_files = ["README"]
|
33
|
+
end
|
34
|
+
|
35
|
+
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: espace-neverblock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Muhammad A. Ali
|
8
|
+
- Ahmed Sobhi
|
9
|
+
- Osama Brekaa
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2008-09-04 00:00:00 -07:00
|
15
|
+
default_executable:
|
16
|
+
dependencies: []
|
17
|
+
|
18
|
+
description: NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
|
19
|
+
email: oldmoe@gmail.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files:
|
25
|
+
- README
|
26
|
+
files:
|
27
|
+
- neverblock.gemspec
|
28
|
+
- README
|
29
|
+
- lib/neverblock.rb
|
30
|
+
- lib/never_block.rb
|
31
|
+
- lib/neverblock-pg.rb
|
32
|
+
- lib/neverblock-mysql.rb
|
33
|
+
- lib/never_block/extensions/fiber_extensions.rb
|
34
|
+
- lib/never_block/pool/fiber_pool.rb
|
35
|
+
- lib/never_block/pool/fibered_connection_pool.rb
|
36
|
+
- lib/never_block/frameworks/rails.rb
|
37
|
+
- lib/never_block/frameworks/activerecord.rb
|
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
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/oldmoe/neverblock
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --main
|
50
|
+
- README
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.2.0
|
69
|
+
signing_key:
|
70
|
+
specification_version: 2
|
71
|
+
summary: Utilities for non-blocking stack components
|
72
|
+
test_files: []
|
73
|
+
|