oldmoe-neverblock 0.1.3 → 0.1.4
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 +1 -1
- data/lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb +29 -38
- data/lib/never_block/db/fibered_db_connection.rb +57 -0
- data/lib/never_block/db/fibered_mysql_connection.rb +31 -68
- data/lib/never_block/db/fibered_postgres_connection.rb +34 -78
- data/lib/never_block/db/{pooled_fibered_mysql_connection.rb → pooled_db_connection.rb} +7 -13
- data/lib/never_block/frameworks/rails.rb +47 -19
- data/lib/never_block/pool/fiber_pool.rb +39 -40
- data/lib/never_block/pool/fibered_connection_pool.rb +23 -17
- data/lib/never_block/servers/thin.rb +6 -11
- data/lib/neverblock-mysql.rb +2 -1
- data/neverblock.gemspec +5 -4
- metadata +14 -6
- data/lib/never_block/db/pooled_fibered_postgres_connection.rb +0 -81
|
@@ -37,7 +37,7 @@ class ActiveRecord::ConnectionAdapters::NeverBlockMysqlAdapter < ActiveRecord::C
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def connect
|
|
40
|
-
@connection = ::NB::DB::
|
|
40
|
+
@connection = ::NB::DB::PooledDBConnection.new(@connection_options.shift) do
|
|
41
41
|
conn = ::NB::DB::FMysql.init
|
|
42
42
|
encoding = @config[:encoding]
|
|
43
43
|
if encoding
|
|
@@ -28,47 +28,38 @@ class ActiveRecord::ConnectionAdapters::NeverBlockPostgreSQLAdapter < ActiveReco
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def connect
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
31
|
+
@connection = ::NB::DB::PooledFiberedPostgresConnection.new(@connection_parameters.shift) do
|
|
32
|
+
conn = PGconn.connect(*@connection_parameters)
|
|
33
|
+
PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
|
|
34
|
+
# Ignore async_exec and async_query when using postgres-pr.
|
|
35
|
+
@async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
|
|
36
|
+
# Use escape string syntax if available. We cannot do this lazily when encountering
|
|
37
|
+
# the first string, because that could then break any transactions in progress.
|
|
38
|
+
# See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
|
|
39
|
+
# If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
|
|
40
|
+
# support escape string syntax. Don't override the inherited quoted_string_prefix.
|
|
41
|
+
NB.neverblock(false) do
|
|
42
|
+
if supports_standard_conforming_strings?
|
|
43
|
+
self.class.instance_eval do
|
|
44
|
+
define_method(:quoted_string_prefix) { 'E' }
|
|
63
45
|
end
|
|
64
46
|
end
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
47
|
+
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
|
48
|
+
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
|
49
|
+
# should know about this but can't detect it there, so deal with it here.
|
|
50
|
+
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
|
51
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.module_eval(<<-end_eval)
|
|
52
|
+
def extract_precision(sql_type)
|
|
53
|
+
if sql_type =~ /^money$/
|
|
54
|
+
#{money_precision}
|
|
55
|
+
else
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end_eval
|
|
60
|
+
#configure_connection
|
|
61
|
+
end
|
|
70
62
|
end
|
|
71
|
-
|
|
72
63
|
end
|
|
73
64
|
|
|
74
65
|
# Close then reopen the connection.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module NeverBlock
|
|
2
|
+
module DB
|
|
3
|
+
module FiberedDBConnection
|
|
4
|
+
# Attaches the connection socket to an event loop.
|
|
5
|
+
# Currently only supports EM, but Rev support will be
|
|
6
|
+
# completed soon.
|
|
7
|
+
def register_with_event_loop(loop)
|
|
8
|
+
@fd = socket
|
|
9
|
+
@io = IO.new(socket)
|
|
10
|
+
if loop == :em
|
|
11
|
+
if EM.reactor_running?
|
|
12
|
+
@em_connection = EM::attach(@io,EMConnectionHandler,self)
|
|
13
|
+
else
|
|
14
|
+
raise "REACTOR NOT RUNNING YA ZALAMA"
|
|
15
|
+
end
|
|
16
|
+
elsif loop.class.name == "REV::Loop"
|
|
17
|
+
loop.attach(RevConnectionHandler.new(@fd))
|
|
18
|
+
else
|
|
19
|
+
raise "could not register with the event loop"
|
|
20
|
+
end
|
|
21
|
+
@loop = loop
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Unattaches the connection socket from the event loop
|
|
25
|
+
def unregister_from_event_loop
|
|
26
|
+
if @loop == :em
|
|
27
|
+
@em_connection.detach
|
|
28
|
+
else
|
|
29
|
+
raise NotImplementedError.new("unregister_from_event_loop not implemented for #{@loop}")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The callback, this is called whenever
|
|
34
|
+
# there is data available at the socket
|
|
35
|
+
def resume_command
|
|
36
|
+
#protection against being called several times
|
|
37
|
+
if @fiber
|
|
38
|
+
f = @fiber
|
|
39
|
+
@fiber = nil
|
|
40
|
+
f.resume
|
|
41
|
+
else
|
|
42
|
+
unregister_from_event_loop
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module EMConnectionHandler
|
|
49
|
+
def initialize connection
|
|
50
|
+
@db_connection = connection
|
|
51
|
+
end
|
|
52
|
+
def notify_readable
|
|
53
|
+
@db_connection.resume_command
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'mysqlplus'
|
|
2
2
|
|
|
3
3
|
module NeverBlock
|
|
4
4
|
|
|
5
5
|
module DB
|
|
6
|
-
# A modified
|
|
6
|
+
# A modified mysql connection driver
|
|
7
7
|
# builds on the original pg driver.
|
|
8
8
|
# This driver is able to register the socket
|
|
9
9
|
# at a certain backend (EM or Rev)
|
|
@@ -12,85 +12,48 @@ module NeverBlock
|
|
|
12
12
|
# it will be done in async mode and the fiber
|
|
13
13
|
# will yield
|
|
14
14
|
class FiberedMysqlConnection < Mysql
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
15
|
+
|
|
16
|
+
include FiberedDBConnection
|
|
28
17
|
|
|
29
18
|
# Assuming the use of NeverBlock fiber extensions and that the exec is run in
|
|
30
19
|
# the context of a fiber. One that have the value :neverblock set to true.
|
|
31
20
|
# All neverblock IO classes check this value, setting it to false will force
|
|
32
21
|
# the execution in a blocking way.
|
|
33
22
|
def query(sql)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
23
|
+
begin
|
|
24
|
+
if Fiber.respond_to? :current and Fiber.current[:neverblock]
|
|
25
|
+
send_query sql
|
|
26
|
+
@fiber = Fiber.current
|
|
27
|
+
Fiber.yield
|
|
28
|
+
get_result
|
|
29
|
+
else
|
|
30
|
+
super(sql)
|
|
51
31
|
end
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
32
|
+
rescue Exception => e
|
|
33
|
+
if error = ['not connected', 'gone away', 'Lost connection'].detect{|msg| e.message.include? msg}
|
|
34
|
+
stop
|
|
35
|
+
connect
|
|
36
|
+
end
|
|
37
|
+
raise e
|
|
38
|
+
end
|
|
72
39
|
end
|
|
73
40
|
|
|
74
|
-
|
|
75
|
-
# there is data available at the socket
|
|
76
|
-
def process_command
|
|
77
|
-
@fiber.resume get_result
|
|
78
|
-
end
|
|
41
|
+
alias :exec :query
|
|
79
42
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# More to follow.
|
|
84
|
-
module EMConnectionHandler
|
|
85
|
-
def initialize connection
|
|
86
|
-
@connection = connection
|
|
43
|
+
# stop the connection and deattach from the event loop
|
|
44
|
+
def stop
|
|
45
|
+
unregister_from_event_loop
|
|
87
46
|
end
|
|
88
|
-
|
|
89
|
-
|
|
47
|
+
|
|
48
|
+
# reconnect and attach to the event loop
|
|
49
|
+
def connect
|
|
50
|
+
super
|
|
51
|
+
register_with_event_loop(@loop)
|
|
90
52
|
end
|
|
91
|
-
|
|
53
|
+
|
|
54
|
+
end #FiberedMySQLConnection
|
|
92
55
|
|
|
93
|
-
|
|
56
|
+
end #DB
|
|
94
57
|
|
|
95
58
|
end #NeverBlock
|
|
96
59
|
|
|
@@ -3,6 +3,7 @@ require 'pg'
|
|
|
3
3
|
module NeverBlock
|
|
4
4
|
|
|
5
5
|
module DB
|
|
6
|
+
|
|
6
7
|
# A modified postgres connection driver
|
|
7
8
|
# builds on the original pg driver.
|
|
8
9
|
# This driver is able to register the socket
|
|
@@ -12,95 +13,50 @@ module NeverBlock
|
|
|
12
13
|
# it will be done in async mode and the fiber
|
|
13
14
|
# will yield
|
|
14
15
|
class FiberedPostgresConnection < PGconn
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
16
|
+
|
|
17
|
+
include FiberedDBConnection
|
|
18
|
+
|
|
28
19
|
# Assuming the use of NeverBlock fiber extensions and that the exec is run in
|
|
29
20
|
# the context of a fiber. One that have the value :neverblock set to true.
|
|
30
21
|
# All neverblock IO classes check this value, setting it to false will force
|
|
31
22
|
# the execution in a blocking way.
|
|
32
23
|
def exec(sql)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
24
|
+
begin
|
|
25
|
+
if Fiber.respond_to? :current and Fiber.current[:neverblock]
|
|
26
|
+
send_query sql
|
|
27
|
+
@fiber = Fiber.current
|
|
28
|
+
Fiber.yield
|
|
29
|
+
while is_busy
|
|
30
|
+
consume_input
|
|
31
|
+
Fiber.yield if is_busy
|
|
32
|
+
end
|
|
33
|
+
res, data = 0, []
|
|
34
|
+
while res != nil
|
|
35
|
+
res = self.get_result
|
|
36
|
+
data << res unless res.nil?
|
|
37
|
+
end
|
|
38
|
+
data.last
|
|
39
|
+
else
|
|
40
|
+
super(sql)
|
|
50
41
|
end
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
42
|
+
rescue Exception => e
|
|
43
|
+
reset if e.message.include? "not connected"
|
|
44
|
+
raise e
|
|
45
|
+
end
|
|
62
46
|
end
|
|
63
47
|
|
|
64
|
-
|
|
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
|
|
48
|
+
alias :query :exec
|
|
73
49
|
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
50
|
+
# reset the connection
|
|
51
|
+
# and reattach to the
|
|
52
|
+
# event loop
|
|
53
|
+
def reset
|
|
54
|
+
unregister_from_event_loop
|
|
55
|
+
super
|
|
56
|
+
register_with_event_loop(@loop)
|
|
90
57
|
end
|
|
91
|
-
|
|
58
|
+
|
|
92
59
|
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
60
|
|
|
105
61
|
end #DB
|
|
106
62
|
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
module NeverBlock
|
|
2
2
|
module DB
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# connections.
|
|
7
|
-
class PooledFiberedMysqlConnection
|
|
8
|
-
|
|
9
|
-
# Requires a hash or an array with connection parameters
|
|
3
|
+
# a proxy for pooled fibered connections
|
|
4
|
+
class PooledDBConnection
|
|
5
|
+
# Requires a block with connection parameters
|
|
10
6
|
# and a pool size (defaults to 4)
|
|
11
7
|
def initialize(size=4, &block)
|
|
12
8
|
@pool = NB::Pool::FiberedConnectionPool.new(:size=>size, :eager=>true) do
|
|
13
9
|
yield
|
|
14
10
|
end
|
|
15
11
|
end
|
|
16
|
-
|
|
12
|
+
|
|
17
13
|
# A proxy for the connection's query method
|
|
18
14
|
# quries the pool to get a connection first
|
|
19
15
|
def query(query)
|
|
@@ -21,6 +17,8 @@ module NeverBlock
|
|
|
21
17
|
conn.query(query)
|
|
22
18
|
end
|
|
23
19
|
end
|
|
20
|
+
|
|
21
|
+
alias :exec :query
|
|
24
22
|
|
|
25
23
|
# This method must be called for transactions to work correctly.
|
|
26
24
|
# One cannot just send "begin" as you never know which connection
|
|
@@ -51,7 +49,6 @@ module NeverBlock
|
|
|
51
49
|
#close all connections and remove them from the event loop
|
|
52
50
|
def close
|
|
53
51
|
@pool.all_connections do |conn|
|
|
54
|
-
conn.unregister_from_event_loop
|
|
55
52
|
conn.close
|
|
56
53
|
end
|
|
57
54
|
end
|
|
@@ -68,10 +65,7 @@ module NeverBlock
|
|
|
68
65
|
@pool.hold do |conn|
|
|
69
66
|
conn.respond_to?(method)
|
|
70
67
|
end
|
|
71
|
-
end
|
|
72
|
-
|
|
68
|
+
end
|
|
73
69
|
end
|
|
74
70
|
end
|
|
75
71
|
end
|
|
76
|
-
|
|
77
|
-
NeverBlock::DB::PFMysql = NeverBlock::DB::PooledFiberedMysqlConnection
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
require 'neverblock' unless defined?(NeverBlock)
|
|
2
|
-
#require 'actionpack'
|
|
3
|
-
#require 'action_controller'
|
|
4
2
|
|
|
5
3
|
# Rails tries to protect dispatched actions
|
|
6
4
|
# by wrapping them in a synchronized code
|
|
@@ -9,29 +7,59 @@ require 'neverblock' unless defined?(NeverBlock)
|
|
|
9
7
|
# transform it (without it knowing) to
|
|
10
8
|
# something more subtle
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class
|
|
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)
|
|
10
|
+
require 'thread'
|
|
11
|
+
# now you synchronize
|
|
12
|
+
class Mutex
|
|
13
|
+
def synchronize(&block)
|
|
22
14
|
# now you don't!
|
|
23
15
|
block.call
|
|
24
16
|
end
|
|
25
17
|
end
|
|
26
|
-
=end
|
|
27
18
|
|
|
19
|
+
require 'action_controller'
|
|
20
|
+
class ActionController::Base
|
|
28
21
|
|
|
29
|
-
|
|
22
|
+
# Mark some actions to execute in a blocking manner overriding the default
|
|
23
|
+
# settings.
|
|
24
|
+
# Example:
|
|
25
|
+
# class UsersController < ApplicationController
|
|
26
|
+
# .
|
|
27
|
+
# allowblock :index
|
|
28
|
+
# .
|
|
29
|
+
# end
|
|
30
|
+
def self.allowblock(*actions)
|
|
31
|
+
actions.each do |action|
|
|
32
|
+
class_eval <<-"end_eval"
|
|
33
|
+
def allowblock_#{action}
|
|
34
|
+
status = Fiber.current[:neverblock]
|
|
35
|
+
Fiber.current[:neverblock] = false
|
|
36
|
+
yield
|
|
37
|
+
Fiber.current[:neverblock] = status
|
|
38
|
+
end
|
|
39
|
+
around_filter :allowblock_#{action}, :only => [:#{action}]
|
|
40
|
+
end_eval
|
|
41
|
+
end
|
|
42
|
+
end
|
|
30
43
|
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
# Mark some actions to execute in a non-blocking manner overriding the default
|
|
45
|
+
# settings.
|
|
46
|
+
# Example:
|
|
47
|
+
# class UsersController < ApplicationController
|
|
48
|
+
# .
|
|
49
|
+
# allowblock :index
|
|
50
|
+
# .
|
|
51
|
+
# end
|
|
52
|
+
def self.neverblock(*actions)
|
|
53
|
+
actions.each do |action|
|
|
54
|
+
class_eval <<-"end_eval"
|
|
55
|
+
def neverblock_#{action}
|
|
56
|
+
status = Fiber.current[:neverblock]
|
|
57
|
+
Fiber.current[:neverblock] = true
|
|
58
|
+
yield
|
|
59
|
+
Fiber.current[:neverblock] = status
|
|
60
|
+
end
|
|
61
|
+
around_filter :allowblock_#{action}, :only => [:#{action}]
|
|
62
|
+
end_eval
|
|
63
|
+
end
|
|
36
64
|
end
|
|
37
65
|
end
|
|
@@ -31,46 +31,45 @@ module NeverBlock
|
|
|
31
31
|
# end
|
|
32
32
|
# end
|
|
33
33
|
#
|
|
34
|
-
|
|
34
|
+
class FiberPool
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
# gives access to the currently free fibers
|
|
37
|
+
attr_reader :fibers
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
39
|
+
# Prepare a list of fibers that are able to run different blocks of code
|
|
40
|
+
# every time. Once a fiber is done with its block, it attempts to fetch
|
|
41
|
+
# another one from the queue
|
|
42
|
+
def initialize(count = 50)
|
|
43
|
+
@fibers,@queue = [],[]
|
|
44
|
+
count.times do |i|
|
|
45
|
+
fiber = Fiber.new do |block|
|
|
46
|
+
loop do
|
|
47
|
+
block.call
|
|
48
|
+
unless @queue.empty?
|
|
49
|
+
block = @queue.shift
|
|
50
|
+
else
|
|
51
|
+
block = Fiber.yield @fibers << Fiber.current
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
fiber[:neverblock] = true
|
|
56
|
+
@fibers << fiber
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# If there is an available fiber use it, otherwise, leave it to linger
|
|
61
|
+
# in a queue
|
|
62
|
+
def spawn(evented = true, &block)
|
|
63
|
+
if fiber = @fibers.shift
|
|
64
|
+
fiber[:neverblock] = evented
|
|
65
|
+
fiber.resume(block)
|
|
66
|
+
else
|
|
67
|
+
@queue << block
|
|
68
|
+
end
|
|
69
|
+
self # we are keen on hiding our queue
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end # FiberPool
|
|
73
|
+
end # Pool
|
|
74
|
+
end # NeverBlock
|
|
62
75
|
|
|
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
|
|
@@ -25,19 +25,28 @@ module NeverBlock
|
|
|
25
25
|
# end
|
|
26
26
|
# 32.times do
|
|
27
27
|
# Fiber.new do
|
|
28
|
-
#
|
|
29
|
-
#
|
|
28
|
+
# # acquire a connection from the pool
|
|
29
|
+
# pool.hold do |conn|
|
|
30
|
+
# conn.execute('something') # you can use the connection normally now
|
|
31
|
+
# end
|
|
30
32
|
# end.resume
|
|
31
33
|
# end
|
|
32
34
|
#
|
|
33
|
-
# The pool has support for transactions, just pass true to the
|
|
34
|
-
# and the connection will not be released after the block
|
|
35
|
+
# The pool has support for transactions, just pass true to the
|
|
36
|
+
# pool#hold method and the connection will not be released after the block
|
|
37
|
+
# is finished
|
|
35
38
|
# It is the responsibility of client code to release the connection
|
|
36
39
|
class FiberedConnectionPool
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
#
|
|
40
|
+
|
|
41
|
+
attr_reader :size
|
|
42
|
+
|
|
43
|
+
# initialize the connection pool using the supplied proc to create
|
|
44
|
+
# the connections
|
|
45
|
+
# You can choose to start them eagerly or lazily (lazy by default)
|
|
46
|
+
# Available options are
|
|
47
|
+
# :size => the maximum number of connections to be created in the pool
|
|
48
|
+
# :eager => (true|false) indicates whether connections should be
|
|
49
|
+
# created initially or when need
|
|
41
50
|
def initialize(options = {}, &block)
|
|
42
51
|
@connections, @busy_connections, @queue = [], {},[]
|
|
43
52
|
@connection_proc = block
|
|
@@ -45,16 +54,13 @@ module NeverBlock
|
|
|
45
54
|
if options[:eager]
|
|
46
55
|
@size.times do
|
|
47
56
|
@connections << @connection_proc.call
|
|
48
|
-
end
|
|
57
|
+
end
|
|
49
58
|
end
|
|
50
59
|
end
|
|
51
60
|
|
|
52
|
-
# If a connection is available,
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# till a connection is available
|
|
56
|
-
# when done with a connection
|
|
57
|
-
# try to porcess other fibers in the queue
|
|
61
|
+
# If a connection is available, pass it to the block, otherwise pass
|
|
62
|
+
# the fiber to the queue till a connection is available
|
|
63
|
+
# when done with a connection try to porcess other fibers in the queue
|
|
58
64
|
# before releasing the connection
|
|
59
65
|
# if inside a transaction, don't release the fiber
|
|
60
66
|
def hold(transactional = false)
|
|
@@ -67,7 +73,7 @@ module NeverBlock
|
|
|
67
73
|
yield conn
|
|
68
74
|
ensure
|
|
69
75
|
release(fiber, conn) unless transactional
|
|
70
|
-
process_queue
|
|
76
|
+
process_queue
|
|
71
77
|
end
|
|
72
78
|
end
|
|
73
79
|
|
|
@@ -106,7 +112,7 @@ module NeverBlock
|
|
|
106
112
|
fiber = @queue.shift
|
|
107
113
|
# What is really happening here?
|
|
108
114
|
# we are resuming a fiber from within
|
|
109
|
-
# another, should we call transfer
|
|
115
|
+
# another, should we call transfer instead?
|
|
110
116
|
fiber.resume @busy_connections[fiber] = @connections.shift
|
|
111
117
|
end
|
|
112
118
|
end
|
|
@@ -4,10 +4,8 @@ require 'thin'
|
|
|
4
4
|
|
|
5
5
|
module Thin
|
|
6
6
|
|
|
7
|
-
# Patch the thin server to use
|
|
8
|
-
#
|
|
9
|
-
# to be able to wrap requests
|
|
10
|
-
# in fibers
|
|
7
|
+
# Patch the thin server to use NeverBlock::Pool::FiberPool to be able to
|
|
8
|
+
# wrap requests in fibers
|
|
11
9
|
class Server
|
|
12
10
|
|
|
13
11
|
DEFAULT_FIBER_POOL_SIZE = 20
|
|
@@ -18,17 +16,14 @@ module Thin
|
|
|
18
16
|
|
|
19
17
|
end # Server
|
|
20
18
|
|
|
21
|
-
# A request is processed by wrapping it
|
|
22
|
-
#
|
|
23
|
-
# the
|
|
24
|
-
# wait in a queue to be picked up later.
|
|
25
|
-
# Meanwhile, the server will still be
|
|
26
|
-
# processing requests
|
|
19
|
+
# A request is processed by wrapping it in a fiber from the fiber pool.
|
|
20
|
+
# If all the fibers are busy the request will wait in a queue to be picked up
|
|
21
|
+
# later. Meanwhile, the server will still be processing requests
|
|
27
22
|
class Connection < EventMachine::Connection
|
|
28
23
|
|
|
29
24
|
def process
|
|
30
25
|
@request.threaded = false
|
|
31
|
-
@backend.server.fiber_pool.spawn{post_process(pre_process)}
|
|
26
|
+
@backend.server.fiber_pool.spawn {post_process(pre_process)}
|
|
32
27
|
end
|
|
33
28
|
|
|
34
29
|
end # Connection
|
data/lib/neverblock-mysql.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
$:.unshift File.expand_path(File.dirname(__FILE__))
|
|
2
2
|
require 'neverblock'
|
|
3
|
+
require 'never_block/db/fibered_db_connection'
|
|
3
4
|
require 'never_block/db/fibered_mysql_connection'
|
|
4
|
-
require 'never_block/db/
|
|
5
|
+
require 'never_block/db/pooled_db_connection'
|
data/neverblock.gemspec
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Gem::Specification.new do |s|
|
|
2
2
|
s.name = "neverblock"
|
|
3
|
-
s.version = "0.1.
|
|
4
|
-
s.date = "2008-09-
|
|
3
|
+
s.version = "0.1.4"
|
|
4
|
+
s.date = "2008-09-20"
|
|
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"
|
|
@@ -23,14 +23,15 @@ Gem::Specification.new do |s|
|
|
|
23
23
|
"lib/never_block/servers/thin.rb",
|
|
24
24
|
"lib/never_block/servers/mongrel.rb",
|
|
25
25
|
"lib/never_block/db/fibered_postgres_connection.rb",
|
|
26
|
-
"lib/never_block/db/
|
|
26
|
+
"lib/never_block/db/pooled_db_connection.rb",
|
|
27
27
|
"lib/never_block/db/fibered_mysql_connection.rb",
|
|
28
|
-
"lib/never_block/db/
|
|
28
|
+
"lib/never_block/db/fibered_db_connection.rb",
|
|
29
29
|
"lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb",
|
|
30
30
|
"lib/active_record/connection_adapters/neverblock_mysql_adapter.rb"
|
|
31
31
|
]
|
|
32
32
|
s.rdoc_options = ["--main", "README"]
|
|
33
33
|
s.extra_rdoc_files = ["README"]
|
|
34
|
+
s.add_dependency('eventmachine', '>= 0.12.2')
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Muhammad A. Ali
|
|
@@ -11,10 +11,18 @@ autorequire:
|
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
13
|
|
|
14
|
-
date: 2008-09-
|
|
14
|
+
date: 2008-09-20 00:00:00 -07:00
|
|
15
15
|
default_executable:
|
|
16
|
-
dependencies:
|
|
17
|
-
|
|
16
|
+
dependencies:
|
|
17
|
+
- !ruby/object:Gem::Dependency
|
|
18
|
+
name: eventmachine
|
|
19
|
+
version_requirement:
|
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
21
|
+
requirements:
|
|
22
|
+
- - ">="
|
|
23
|
+
- !ruby/object:Gem::Version
|
|
24
|
+
version: 0.12.2
|
|
25
|
+
version:
|
|
18
26
|
description: NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
|
|
19
27
|
email: oldmoe@gmail.com
|
|
20
28
|
executables: []
|
|
@@ -38,9 +46,9 @@ files:
|
|
|
38
46
|
- lib/never_block/servers/thin.rb
|
|
39
47
|
- lib/never_block/servers/mongrel.rb
|
|
40
48
|
- lib/never_block/db/fibered_postgres_connection.rb
|
|
41
|
-
- lib/never_block/db/
|
|
49
|
+
- lib/never_block/db/pooled_db_connection.rb
|
|
42
50
|
- lib/never_block/db/fibered_mysql_connection.rb
|
|
43
|
-
- lib/never_block/db/
|
|
51
|
+
- lib/never_block/db/fibered_db_connection.rb
|
|
44
52
|
- lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb
|
|
45
53
|
- lib/active_record/connection_adapters/neverblock_mysql_adapter.rb
|
|
46
54
|
has_rdoc: true
|
|
@@ -1,81 +0,0 @@
|
|
|
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
|
-
|