neverblock 0.1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +24 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/neverblock_mysql_adapter.rb +68 -0
- data/lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb +85 -0
- data/lib/never_block.rb +102 -0
- data/lib/never_block/db/fibered_db_connection.rb +72 -0
- data/lib/never_block/db/fibered_mysql_connection.rb +62 -0
- data/lib/never_block/db/fibered_postgres_connection.rb +64 -0
- data/lib/never_block/db/pooled_db_connection.rb +43 -0
- data/lib/never_block/extensions/fiber_extensions.rb +31 -0
- data/lib/never_block/frameworks/activerecord.rb +37 -0
- data/lib/never_block/frameworks/rails.rb +65 -0
- data/lib/never_block/pool/fiber_pool.rb +74 -0
- data/lib/never_block/pool/fibered_connection_pool.rb +130 -0
- data/lib/never_block/servers/mongrel.rb +236 -0
- data/lib/never_block/servers/thin.rb +32 -0
- data/lib/neverblock-mysql.rb +5 -0
- data/lib/neverblock-pg.rb +5 -0
- data/lib/neverblock.rb +8 -0
- data/neverblock.gemspec +73 -0
- data/spec/fiber_extensions_spec.rb +24 -0
- data/spec/fiber_pool_spec.rb +60 -0
- data/spec/fibered_connection_pool_spec.rb +106 -0
- data/tasks/spec.rake +12 -0
- data/test/test_mysql.rb +36 -0
- data/test/test_pg.rb +102 -0
- metadata +97 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'pg'
|
2
|
+
|
3
|
+
module NeverBlock
|
4
|
+
|
5
|
+
module DB
|
6
|
+
|
7
|
+
# A modified postgres connection driver
|
8
|
+
# builds on the original pg driver.
|
9
|
+
# This driver is able to register the socket
|
10
|
+
# at a certain backend (EM or Rev)
|
11
|
+
# and then whenever the query is executed
|
12
|
+
# within the scope of a friendly fiber
|
13
|
+
# it will be done in async mode and the fiber
|
14
|
+
# will yield
|
15
|
+
class FiberedPostgresConnection < PGconn
|
16
|
+
|
17
|
+
include FiberedDBConnection
|
18
|
+
|
19
|
+
# Assuming the use of NeverBlock fiber extensions and that the exec is run in
|
20
|
+
# the context of a fiber. One that have the value :neverblock set to true.
|
21
|
+
# All neverblock IO classes check this value, setting it to false will force
|
22
|
+
# the execution in a blocking way.
|
23
|
+
def exec(sql)
|
24
|
+
# TODO Still not "killing the query process"-proof
|
25
|
+
# In some cases, the query is simply sent but the fiber never yields
|
26
|
+
if NB.event_loop_available? && NB.neverblocking?
|
27
|
+
begin
|
28
|
+
send_query sql
|
29
|
+
@fiber = Fiber.current
|
30
|
+
Fiber.yield register_with_event_loop
|
31
|
+
while is_busy
|
32
|
+
consume_input
|
33
|
+
Fiber.yield if is_busy
|
34
|
+
end
|
35
|
+
res, data = 0, []
|
36
|
+
while res != nil
|
37
|
+
res = self.get_result
|
38
|
+
data << res unless res.nil?
|
39
|
+
end
|
40
|
+
data.last
|
41
|
+
rescue Exception => e
|
42
|
+
if error = ['not connected', 'gone away', 'Lost connection','no connection'].detect{|msg| e.message.include? msg}
|
43
|
+
#event_loop_connection_close
|
44
|
+
unregister_from_event_loop
|
45
|
+
reset
|
46
|
+
end
|
47
|
+
raise e
|
48
|
+
ensure
|
49
|
+
unregister_from_event_loop
|
50
|
+
end
|
51
|
+
else
|
52
|
+
super(sql)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :query, :exec
|
57
|
+
|
58
|
+
end #FiberedPostgresConnection
|
59
|
+
|
60
|
+
end #DB
|
61
|
+
|
62
|
+
end #NeverBlock
|
63
|
+
|
64
|
+
NeverBlock::DB::FPGconn = NeverBlock::DB::FiberedPostgresConnection
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module NeverBlock
|
2
|
+
module DB
|
3
|
+
# a proxy for pooled fibered connections
|
4
|
+
class PooledDBConnection
|
5
|
+
# Requires a block with connection parameters
|
6
|
+
# and a pool size (defaults to 4)
|
7
|
+
def initialize(size=4, &block)
|
8
|
+
@pool = NB::Pool::FiberedConnectionPool.new(:size=>size, :eager=>true) do
|
9
|
+
yield
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# A proxy for the connection's query method
|
14
|
+
# quries the pool to get a connection first
|
15
|
+
def query(query)
|
16
|
+
@pool.hold do |conn|
|
17
|
+
conn.query(query)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
alias :exec :query
|
22
|
+
|
23
|
+
# Replaces the current connection with a brand new one
|
24
|
+
def replace_acquired_connection
|
25
|
+
@pool.replace_acquired_connection
|
26
|
+
end
|
27
|
+
|
28
|
+
# Pass unknown methods to the connection
|
29
|
+
def method_missing(method, *args)
|
30
|
+
@pool.hold do |conn|
|
31
|
+
conn.send(method, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Pass method queries to the connection
|
36
|
+
def respond_to?(method)
|
37
|
+
@pool.hold do |conn|
|
38
|
+
conn.respond_to?(method)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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
|
+
|
30
|
+
end
|
31
|
+
|
@@ -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 &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,65 @@
|
|
1
|
+
require 'neverblock' unless defined?(NeverBlock)
|
2
|
+
|
3
|
+
# Rails tries to protect dispatched actions
|
4
|
+
# by wrapping them in a synchronized code
|
5
|
+
# block, since fibers hate synchronized
|
6
|
+
# blocks we will trick the guard and
|
7
|
+
# transform it (without it knowing) to
|
8
|
+
# something more subtle
|
9
|
+
|
10
|
+
require 'thread'
|
11
|
+
# now you synchronize
|
12
|
+
class Mutex
|
13
|
+
def synchronize(&block)
|
14
|
+
# now you don't!
|
15
|
+
block.call
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'action_controller'
|
20
|
+
class ActionController::Base
|
21
|
+
|
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
|
43
|
+
|
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
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module NeverBlock
|
2
|
+
module Pool
|
3
|
+
|
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
|
+
# A pool of initialized fibers
|
9
|
+
# It does not grow in size or create transient fibers
|
10
|
+
# It will queue code blocks when needed (if all its fibers are busy)
|
11
|
+
#
|
12
|
+
# This class is particulary useful when you use the fibers
|
13
|
+
# to connect to evented back ends. It also does not generate
|
14
|
+
# transient objects and thus saves memory.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
# fiber_pool = NeverBlock::Pool::FiberPool.new(150)
|
18
|
+
#
|
19
|
+
# loop do
|
20
|
+
# fiber_pool.spawn do
|
21
|
+
# #fiber body goes here
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
class FiberPool
|
26
|
+
|
27
|
+
# gives access to the currently free fibers
|
28
|
+
attr_reader :fibers
|
29
|
+
|
30
|
+
# Prepare a list of fibers that are able to run different blocks of code
|
31
|
+
# every time. Once a fiber is done with its block, it attempts to fetch
|
32
|
+
# another one from the queue
|
33
|
+
def initialize(count = 50)
|
34
|
+
@fibers,@busy_fibers,@queue = [],{},[]
|
35
|
+
count.times do |i|
|
36
|
+
fiber = Fiber.new do |block|
|
37
|
+
loop do
|
38
|
+
block.call
|
39
|
+
# callbacks are called in a reverse order, much like c++ destructor
|
40
|
+
Fiber.current[:callbacks].pop.call while Fiber.current[:callbacks].length > 0
|
41
|
+
unless @queue.empty?
|
42
|
+
block = @queue.shift
|
43
|
+
else
|
44
|
+
@busy_fibers.delete(Fiber.current.object_id)
|
45
|
+
@fibers << Fiber.current
|
46
|
+
block = Fiber.yield
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
fiber[:callbacks] = []
|
51
|
+
fiber[:em_keys] = []
|
52
|
+
fiber[:neverblock] = true
|
53
|
+
@fibers << fiber
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# If there is an available fiber use it, otherwise, leave it to linger
|
58
|
+
# in a queue
|
59
|
+
def spawn(evented = true, &block)
|
60
|
+
if fiber = @fibers.shift
|
61
|
+
fiber[:callbacks] = []
|
62
|
+
@busy_fibers[fiber.object_id] = fiber
|
63
|
+
fiber[:neverblock] = evented
|
64
|
+
fiber.resume(block)
|
65
|
+
else
|
66
|
+
@queue << block
|
67
|
+
end
|
68
|
+
self # we are keen on hiding our queue
|
69
|
+
end
|
70
|
+
|
71
|
+
end # FiberPool
|
72
|
+
end # Pool
|
73
|
+
end # NeverBlock
|
74
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module NeverBlock
|
2
|
+
module Pool
|
3
|
+
# This class represents a pool of connections,
|
4
|
+
# you hold or release conncetions from the pool
|
5
|
+
# hold requests that cannot be fullfiled will be queued
|
6
|
+
# the fiber will be paused and resumed later when
|
7
|
+
# a connection is avaialble
|
8
|
+
#
|
9
|
+
# Large portions of this class were copied and pasted
|
10
|
+
# form Sequel's threaded connection pool
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# pool = NeverBlock::Pool::FiberedConnectionPool.new(:size=>16)do
|
15
|
+
# # connection creation code goes here
|
16
|
+
# end
|
17
|
+
# 32.times do
|
18
|
+
# Fiber.new do
|
19
|
+
# # acquire a connection from the pool
|
20
|
+
# pool.hold do |conn|
|
21
|
+
# conn.execute('something') # you can use the connection normally now
|
22
|
+
# end
|
23
|
+
# end.resume
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# The pool has support for transactions, just pass true to the
|
27
|
+
# pool#hold method and the connection will not be released after the block
|
28
|
+
# is finished
|
29
|
+
# It is the responsibility of client code to release the connection
|
30
|
+
class FiberedConnectionPool
|
31
|
+
|
32
|
+
attr_reader :size
|
33
|
+
|
34
|
+
# initialize the connection pool using the supplied proc to create
|
35
|
+
# the connections
|
36
|
+
# You can choose to start them eagerly or lazily (lazy by default)
|
37
|
+
# Available options are
|
38
|
+
# :size => the maximum number of connections to be created in the pool
|
39
|
+
# :eager => (true|false) indicates whether connections should be
|
40
|
+
# created initially or when need
|
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
|
+
def replace_acquired_connection
|
53
|
+
fiber = Fiber.current
|
54
|
+
conn = @connection_proc.call
|
55
|
+
@busy_connections[fiber] = conn
|
56
|
+
fiber[connection_pool_key] = conn
|
57
|
+
end
|
58
|
+
|
59
|
+
# If a connection is available, pass it to the block, otherwise pass
|
60
|
+
# the fiber to the queue till a connection is available
|
61
|
+
def hold()
|
62
|
+
fiber = Fiber.current
|
63
|
+
conn = acquire(fiber)
|
64
|
+
yield conn
|
65
|
+
end
|
66
|
+
|
67
|
+
def all_connections
|
68
|
+
(@connections + @busy_connections.values).each {|conn| yield(conn)}
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Can we find a connection?
|
74
|
+
# Can we create one?
|
75
|
+
# Wait in the queue then
|
76
|
+
def acquire(fiber)
|
77
|
+
# A special case for rails when doing ActiveRecord stuff when not yet
|
78
|
+
# running in the context of a request (fiber) like in the case of AR
|
79
|
+
# queries in environment.rb (Root Fiber)
|
80
|
+
return @connections.first unless fiber[:callbacks]
|
81
|
+
|
82
|
+
fiber[:current_pool_key] = connection_pool_key
|
83
|
+
return fiber[connection_pool_key] if fiber[connection_pool_key]
|
84
|
+
conn = if !@connections.empty?
|
85
|
+
@connections.shift
|
86
|
+
elsif (@connections.length + @busy_connections.length) < @size
|
87
|
+
@connection_proc.call
|
88
|
+
else
|
89
|
+
Fiber.yield @queue << fiber
|
90
|
+
end
|
91
|
+
|
92
|
+
# They're called in reverse order i.e. release then process_queue
|
93
|
+
fiber[:callbacks] << self.method(:process_queue)
|
94
|
+
fiber[:callbacks] << self.method(:release)
|
95
|
+
|
96
|
+
@busy_connections[fiber] = conn
|
97
|
+
fiber[connection_pool_key] = conn
|
98
|
+
end
|
99
|
+
|
100
|
+
# Give the fiber's connection back to the pool
|
101
|
+
def release()
|
102
|
+
fiber = Fiber.current
|
103
|
+
if fiber[connection_pool_key]
|
104
|
+
@busy_connections.delete(fiber)
|
105
|
+
@connections << fiber[connection_pool_key]
|
106
|
+
fiber[connection_pool_key] = nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Check if there are waiting fibers and
|
111
|
+
# try to process them
|
112
|
+
def process_queue
|
113
|
+
while !@connections.empty? and !@queue.empty?
|
114
|
+
fiber = @queue.shift
|
115
|
+
# What is really happening here?
|
116
|
+
# we are resuming a fiber from within
|
117
|
+
# another, should we call transfer instead?
|
118
|
+
fiber.resume @connections.shift
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def connection_pool_key
|
123
|
+
@connection_pool_key ||= "connection_pool_#{object_id}".intern
|
124
|
+
end
|
125
|
+
|
126
|
+
end #FiberedConnectionPool
|
127
|
+
|
128
|
+
end #Pool
|
129
|
+
|
130
|
+
end #NeverBlock
|