oldmoe-neverblock 0.1.6 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/never_block.rb +5 -78
- data/lib/neverblock.rb +3 -1
- data/lib/{never_block/extensions/fiber_extensions.rb → neverblock/core/fiber.rb} +20 -6
- data/lib/neverblock/core/pool.rb +72 -0
- data/lib/neverblock/core/reactor.rb +50 -0
- data/lib/neverblock/core/system/system.rb +38 -0
- data/lib/neverblock/core/system/timeout.rb +67 -0
- data/lib/{never_block/db/pooled_db_connection.rb → neverblock/io/db/connection.rb} +16 -2
- data/lib/neverblock/io/db/drivers/mysql.rb +73 -0
- data/lib/neverblock/io/db/drivers/postgres.rb +63 -0
- data/lib/neverblock/io/db/fibered_connection_pool.rb +130 -0
- data/lib/{never_block → neverblock/io}/db/fibered_mysql_connection.rb +5 -18
- data/lib/{never_block/pool/fibered_connection_pool.rb → neverblock/io/db/pool.rb} +25 -40
- data/lib/neverblock/io/file.rb +24 -0
- data/lib/neverblock/io/io.rb +219 -0
- data/lib/neverblock/io/socket.rb +75 -0
- data/lib/neverblock_io.rb +6 -0
- data/lib/system.rb +4 -0
- data/neverblock.gemspec +23 -21
- metadata +23 -20
- data/lib/active_record/connection_adapters/neverblock_mysql_adapter.rb +0 -68
- data/lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb +0 -85
- data/lib/never_block/db/fibered_db_connection.rb +0 -72
- data/lib/never_block/db/fibered_postgres_connection.rb +0 -64
- data/lib/never_block/frameworks/activerecord.rb +0 -37
- data/lib/never_block/frameworks/rails.rb +0 -65
- data/lib/never_block/pool/fiber_pool.rb +0 -74
- data/lib/never_block/servers/mongrel.rb +0 -236
- data/lib/never_block/servers/thin.rb +0 -32
- data/lib/neverblock-mysql.rb +0 -5
- data/lib/neverblock-pg.rb +0 -5
data/lib/never_block.rb
CHANGED
@@ -1,96 +1,23 @@
|
|
1
1
|
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
-
# Copyright:: Copyright (c)
|
2
|
+
# Copyright:: Copyright (c) 2009 eSpace, Inc.
|
3
3
|
# License:: Distributes under the same terms as Ruby
|
4
4
|
|
5
5
|
$:.unshift File.expand_path(File.dirname(__FILE__))
|
6
6
|
|
7
|
-
# Thanks to Aman Gupta for writing the Ruby 1.8 Fiber simulator
|
8
|
-
unless defined? Fiber
|
9
|
-
require 'thread'
|
10
|
-
require 'singleton'
|
11
|
-
class FiberError < StandardError; end
|
12
|
-
class Fiber
|
13
|
-
def initialize
|
14
|
-
raise ArgumentError, 'new Fiber requires a block' unless block_given?
|
15
|
-
|
16
|
-
@yield = Queue.new
|
17
|
-
@resume = Queue.new
|
18
|
-
|
19
|
-
@thread = Thread.new{ @yield.push [ *yield(*@resume.pop) ] }
|
20
|
-
@thread.abort_on_exception = true
|
21
|
-
@thread[:fiber] = self
|
22
|
-
end
|
23
|
-
attr_reader :thread
|
24
|
-
|
25
|
-
def resume *args
|
26
|
-
raise FiberError, 'dead fiber called' unless @thread.alive?
|
27
|
-
@resume.push(args)
|
28
|
-
result = @yield.pop
|
29
|
-
result.size > 1 ? result : result.first
|
30
|
-
end
|
31
|
-
|
32
|
-
def yield *args
|
33
|
-
@yield.push(args)
|
34
|
-
result = @resume.pop
|
35
|
-
result.size > 1 ? result : result.first
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.yield *args
|
39
|
-
raise FiberError, "can't yield from root fiber" unless fiber = Thread.current[:fiber]
|
40
|
-
fiber.yield(*args)
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.current
|
44
|
-
Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
|
45
|
-
end
|
46
|
-
|
47
|
-
def inspect
|
48
|
-
"#<#{self.class}:0x#{self.object_id.to_s(16)}>"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class RootFiber < Fiber
|
53
|
-
include Singleton
|
54
|
-
def initialize
|
55
|
-
end
|
56
|
-
|
57
|
-
def resume *args
|
58
|
-
raise FiberError, "can't resume root fiber"
|
59
|
-
end
|
60
|
-
|
61
|
-
def yield *args
|
62
|
-
raise FiberError, "can't yield from root fiber"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
#attach the root fiber to the main thread
|
67
|
-
Thread.main[:fiber] = RootFiber.instance
|
68
|
-
else
|
69
|
-
require 'fiber'
|
70
|
-
end
|
71
|
-
|
72
|
-
require 'never_block/extensions/fiber_extensions'
|
73
|
-
require 'never_block/pool/fiber_pool'
|
74
|
-
require 'never_block/pool/fibered_connection_pool'
|
75
|
-
|
76
7
|
module NeverBlock
|
77
8
|
|
78
9
|
# Checks if we should be working in a non-blocking mode
|
79
10
|
def self.neverblocking?
|
80
|
-
Fiber.respond_to?(:current) && Fiber.current[:neverblock]
|
81
|
-
end
|
82
|
-
|
83
|
-
def self.event_loop_available?
|
84
|
-
defined?(EM) && EM.reactor_running?
|
11
|
+
NB::Fiber.respond_to?(:current) && NB::Fiber.current.respond_to?('[]') && NB::Fiber.current[:neverblock] && NB.reactor.running?
|
85
12
|
end
|
86
13
|
|
87
14
|
# The given block will run its queries either in blocking or non-blocking
|
88
15
|
# mode based on the first parameter
|
89
16
|
def self.neverblock(nb = true, &block)
|
90
|
-
status = Fiber.current[:neverblock]
|
91
|
-
Fiber.current[:neverblock] = nb
|
17
|
+
status = NB::Fiber.current[:neverblock]
|
18
|
+
NB::Fiber.current[:neverblock] = nb
|
92
19
|
block.call
|
93
|
-
Fiber.current[:neverblock] = status
|
20
|
+
NB::Fiber.current[:neverblock] = status
|
94
21
|
end
|
95
22
|
|
96
23
|
# Exception to be thrown for all neverblock internal errors
|
data/lib/neverblock.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
-
# Copyright:: Copyright (c)
|
2
|
+
# Copyright:: Copyright (c) 2009 eSpace, Inc.
|
3
3
|
# License:: Distributes under the same terms as Ruby
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
require 'fiber'
|
6
|
+
require File.expand_path(File.dirname(__FILE__)+'/../../never_block')
|
7
|
+
|
8
|
+
class NeverBlock::Fiber < Fiber
|
9
|
+
|
10
|
+
def initialize(neverblock = true, &block)
|
11
|
+
self[:neverblock] = neverblock
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
8
15
|
|
9
|
-
class Fiber
|
10
|
-
|
11
16
|
#Attribute Reference--Returns the value of a fiber-local variable, using
|
12
17
|
#either a symbol or a string name. If the specified variable does not exist,
|
13
18
|
#returns nil.
|
@@ -21,6 +26,15 @@ class Fiber
|
|
21
26
|
local_fiber_variables[key] = value
|
22
27
|
end
|
23
28
|
|
29
|
+
#Sending an exception instance to resume will yield the fiber
|
30
|
+
#and then raise the exception. This is necessary to raise exceptions
|
31
|
+
#in their correct context.
|
32
|
+
def self.yield(*args)
|
33
|
+
result = super
|
34
|
+
raise result if result.is_a? Exception
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
24
38
|
private
|
25
39
|
|
26
40
|
def local_fiber_variables
|
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(File.dirname(__FILE__)+'/fiber')
|
3
|
+
|
4
|
+
module NeverBlock
|
5
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
6
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
7
|
+
# License:: Distributes under the same terms as Ruby
|
8
|
+
#
|
9
|
+
# A pool of initialized fibers
|
10
|
+
# It does not grow in size or create transient fibers
|
11
|
+
# It will queue code blocks when needed (if all its fibers are busy)
|
12
|
+
#
|
13
|
+
# This class is particulary useful when you use the fibers
|
14
|
+
# to connect to evented back ends. It also does not generate
|
15
|
+
# transient objects and thus saves memory.
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
# fiber_pool = NeverBlock::FiberPool.new(150)
|
19
|
+
#
|
20
|
+
# loop do
|
21
|
+
# fiber_pool.spawn do
|
22
|
+
# #fiber body goes here
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
class FiberPool
|
27
|
+
|
28
|
+
# gives access to the currently free fibers
|
29
|
+
attr_reader :fibers
|
30
|
+
|
31
|
+
# Prepare a list of fibers that are able to run different blocks of code
|
32
|
+
# every time. Once a fiber is done with its block, it attempts to fetch
|
33
|
+
# another one from the queue
|
34
|
+
def initialize(count = 50)
|
35
|
+
@fibers,@busy_fibers,@queue = [],{},[]
|
36
|
+
count.times do |i|
|
37
|
+
fiber = NB::Fiber.new do |block|
|
38
|
+
loop do
|
39
|
+
block.call
|
40
|
+
# callbacks are called in a reverse order, much like c++ destructor
|
41
|
+
NB::Fiber.current[:callbacks].pop.call while NB::Fiber.current[:callbacks].length > 0
|
42
|
+
unless @queue.empty?
|
43
|
+
block = @queue.shift
|
44
|
+
else
|
45
|
+
@busy_fibers.delete(NB::Fiber.current.object_id)
|
46
|
+
@fibers << NB::Fiber.current
|
47
|
+
block = NB::Fiber.yield
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
fiber[:callbacks] = []
|
52
|
+
@fibers << fiber
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# If there is an available fiber use it, otherwise, leave it to linger
|
57
|
+
# in a queue
|
58
|
+
def spawn(evented = true, &block)
|
59
|
+
if fiber = @fibers.shift
|
60
|
+
fiber[:callbacks] = []
|
61
|
+
@busy_fibers[fiber.object_id] = fiber
|
62
|
+
fiber[:neverblock] = evented
|
63
|
+
fiber.resume(block)
|
64
|
+
else
|
65
|
+
@queue << block
|
66
|
+
end
|
67
|
+
self # we are keen on hiding our queue
|
68
|
+
end
|
69
|
+
|
70
|
+
end # FiberPool
|
71
|
+
end # NeverBlock
|
72
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'reactor'
|
2
|
+
require 'thread'
|
3
|
+
require File.expand_path(File.dirname(__FILE__)+'/fiber')
|
4
|
+
|
5
|
+
module NeverBlock
|
6
|
+
|
7
|
+
@@reactors = {}
|
8
|
+
|
9
|
+
@@queue = Queue.new
|
10
|
+
|
11
|
+
@@thread_pool = []
|
12
|
+
|
13
|
+
20.times do
|
14
|
+
@@thread_pool << Thread.new do
|
15
|
+
loop do
|
16
|
+
io, method, params, fiber, reactor = *(@@queue.shift)
|
17
|
+
begin
|
18
|
+
reactor.next_tick{fiber.resume(io.__send__(method, *params))} if fiber.alive?
|
19
|
+
rescue Exception => e
|
20
|
+
reactor.next_tick{fiber.resume(e)} if fiber.alive?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.reactor
|
27
|
+
@@reactors[Thread.current.object_id] ||= ::Reactor::Base.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.wait(mode, io)
|
31
|
+
fiber = NB::Fiber.current
|
32
|
+
NB.reactor.attach(mode, io){fiber.resume}
|
33
|
+
NB::Fiber.yield
|
34
|
+
NB.reactor.detach(mode, io)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.sleep(time)
|
38
|
+
NB::Fiber.yield if time.nil?
|
39
|
+
return if time <= 0
|
40
|
+
fiber = NB::Fiber.current
|
41
|
+
NB.reactor.add_timer(time){fiber.resume}
|
42
|
+
NB::Fiber.yield
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.defer(io, action, args)
|
46
|
+
@@queue << [io, action, args, NB::Fiber.current, NB.reactor]
|
47
|
+
NB::Fiber.yield
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(File.dirname(__FILE__)+'/../../../neverblock')
|
3
|
+
|
4
|
+
module Kernel
|
5
|
+
|
6
|
+
alias_method :rb_sleep, :sleep
|
7
|
+
|
8
|
+
def sleep(time=nil)
|
9
|
+
return rb_sleep(time) unless NB.neverblocking?
|
10
|
+
NB.sleep(time)
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method :rb_system, :system
|
14
|
+
|
15
|
+
def system(cmd, *args)
|
16
|
+
return rb_system(cmd, *args) unless NB.neverblocking?
|
17
|
+
begin
|
18
|
+
backticks(cmd, *args)
|
19
|
+
result = $?.exitstatus
|
20
|
+
return true if result.zero?
|
21
|
+
return nil if result == 127
|
22
|
+
return false
|
23
|
+
rescue Errno::ENOENT => e
|
24
|
+
return nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def backticks(cmd, *args)
|
29
|
+
myargs = "#{cmd} "
|
30
|
+
myargs << args.join(' ') if args
|
31
|
+
res = ''
|
32
|
+
IO.popen(myargs) do |f|
|
33
|
+
res << f.read
|
34
|
+
end
|
35
|
+
res
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require File.expand_path(File.dirname(__FILE__)+'/../../../neverblock')
|
3
|
+
|
4
|
+
module Timeout
|
5
|
+
|
6
|
+
alias_method :rb_timeout, :timeout
|
7
|
+
|
8
|
+
def timeout(time, klass=Timeout::Error, &block)
|
9
|
+
return rb_timeout(time, klass,&block) unless NB.neverblocking?
|
10
|
+
if time.nil? || time <= 0
|
11
|
+
block.call
|
12
|
+
else
|
13
|
+
|
14
|
+
|
15
|
+
fiber = NB::Fiber.current
|
16
|
+
|
17
|
+
timer = NB.reactor.add_timer(time) do
|
18
|
+
fiber[:timeouts].last.each do |event|
|
19
|
+
if event.is_a? Reactor::Timer
|
20
|
+
event.cancel
|
21
|
+
else
|
22
|
+
NB.reactor.detach(event[0], event[1])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
fiber.resume(klass.new)
|
26
|
+
end
|
27
|
+
(fiber[:timeouts] ||= []) << []
|
28
|
+
begin
|
29
|
+
block.call
|
30
|
+
rescue Exception => e
|
31
|
+
raise e
|
32
|
+
ensure
|
33
|
+
timer.cancel
|
34
|
+
fiber[:timeouts].pop
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module_function :timeout
|
40
|
+
module_function :rb_timeout
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
NB.reactor.on_add_timer do |timer|
|
45
|
+
Kernel.puts "on add"
|
46
|
+
timeouts = NB::Fiber.current[:timeouts]
|
47
|
+
unless timeouts.nil? || timeouts.empty?
|
48
|
+
timeouts.last << timer
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
NB.reactor.on_attach do |mode, io|
|
53
|
+
Kernel.puts "on attach"
|
54
|
+
timeouts = NB::Fiber.current[:timeouts]
|
55
|
+
unless timeouts.nil? || timeouts.empty?
|
56
|
+
timeouts.last << [mode, io]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
NB.reactor.on_detach do |mode, io|
|
61
|
+
Kernel.puts "on detach"
|
62
|
+
timeouts = NB::Fiber.current[:timeouts]
|
63
|
+
unless timeouts.nil? || timeouts.empty?
|
64
|
+
timeouts.delete_if{|to|to.is_a? Array && to[0] == mode && to[1] == io}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -1,11 +1,14 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)+'/pool')
|
2
|
+
|
1
3
|
module NeverBlock
|
4
|
+
|
2
5
|
module DB
|
3
6
|
# a proxy for pooled fibered connections
|
4
|
-
class
|
7
|
+
class Connection
|
5
8
|
# Requires a block with connection parameters
|
6
9
|
# and a pool size (defaults to 4)
|
7
10
|
def initialize(size=4, &block)
|
8
|
-
@pool = NB::Pool
|
11
|
+
@pool = NB::DB::Pool.new(:size=>size, :eager=>true) do
|
9
12
|
yield
|
10
13
|
end
|
11
14
|
end
|
@@ -37,7 +40,18 @@ module NeverBlock
|
|
37
40
|
@pool.hold do |conn|
|
38
41
|
conn.respond_to?(method)
|
39
42
|
end
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# are we in a transaction?
|
48
|
+
# if no then just hold a connection and run the block
|
49
|
+
# else get a connection, pass it to the block
|
50
|
+
# and move away
|
51
|
+
def hold_connection
|
52
|
+
|
40
53
|
end
|
54
|
+
|
41
55
|
end
|
42
56
|
end
|
43
57
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
require 'mysqlplus'
|
3
|
+
require File.expand_path(File.dirname(__FILE__)+'/../../../../neverblock')
|
4
|
+
require File.expand_path(File.dirname(__FILE__)+'/../connection')
|
5
|
+
require File.expand_path(File.dirname(__FILE__)+'/../fibered_mysql_connection')
|
6
|
+
require File.expand_path(File.dirname(__FILE__)+'/../pool')
|
7
|
+
|
8
|
+
|
9
|
+
module NeverBlock
|
10
|
+
|
11
|
+
module DB
|
12
|
+
# A modified mysql connection driver. It builds on the original pg driver.
|
13
|
+
# This driver is able to register the socket at a certain backend (EM)
|
14
|
+
# and then whenever the query is executed within the scope of a friendly
|
15
|
+
# fiber. It will be done in async mode and the fiber will yield
|
16
|
+
class Mysql < ::Mysql
|
17
|
+
|
18
|
+
# Initializes the connection and remembers the connection params
|
19
|
+
def initialize(*args)
|
20
|
+
@connection_params = args
|
21
|
+
super(*@connection_params)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Does a normal real_connect if arguments are passed. If no arguments are
|
25
|
+
# passed it uses the ones it remembers
|
26
|
+
def real_connect(*args)
|
27
|
+
@connection_params = args unless args.empty?
|
28
|
+
super(*@connection_params)
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :connect, :real_connect
|
32
|
+
|
33
|
+
# Assuming the use of NeverBlock fiber extensions and that the exec is run in
|
34
|
+
# the context of a fiber. One that have the value :neverblock set to true.
|
35
|
+
# All neverblock IO classes check this value, setting it to false will force
|
36
|
+
# the execution in a blocking way.
|
37
|
+
def query(sql)
|
38
|
+
if NB.neverblocking? && NB.reactor.running?
|
39
|
+
send_query sql
|
40
|
+
NB.wait(:read, IO.new(socket))
|
41
|
+
get_result
|
42
|
+
else
|
43
|
+
super(sql)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :exec, :query
|
48
|
+
|
49
|
+
end #MySQL
|
50
|
+
|
51
|
+
class PooledMySQL < ::NeverBlock::DB::Connection
|
52
|
+
|
53
|
+
def initialize(*args)
|
54
|
+
options = {}
|
55
|
+
if args #&& (options = args.last).is_a? Hash
|
56
|
+
size = options[:size] || 4
|
57
|
+
eager = options[:eager] || true
|
58
|
+
args.pop
|
59
|
+
end
|
60
|
+
@pool = NB::DB::Pool.new(:size=>size, :eager=>eager) do
|
61
|
+
MySQL.new(*args)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end #PooledMySQL
|
66
|
+
|
67
|
+
end #DB
|
68
|
+
|
69
|
+
|
70
|
+
end #NeverBlock
|
71
|
+
|
72
|
+
|
73
|
+
|