neverblock 0.1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/README
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
=== Updates from conickal
|
2
|
+
Fixes issues brought up with Rails > 2.3.4 and EventMachine > 0.12.10
|
3
|
+
|
4
|
+
== NeverBlock
|
5
|
+
Never, ever!
|
6
|
+
|
7
|
+
NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
|
8
|
+
|
9
|
+
NeverBlock currently provides the following Libraries:
|
10
|
+
|
11
|
+
=== FiberExtensions
|
12
|
+
A set of extenstions to the standard Fiber implementation
|
13
|
+
|
14
|
+
=== NeverBlock::Pool::FiberPool
|
15
|
+
A pool of fibers that can be used to provide an upper limit to the numbers of active fibers in an application
|
16
|
+
|
17
|
+
=== NeverBlock::Pool::FiberedConnectionPool
|
18
|
+
A generic fibered connection pool for all sorts of connections with support for transactions. This was mostly copied from Sequel::ConnectionPool
|
19
|
+
|
20
|
+
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.
|
21
|
+
|
22
|
+
=== License
|
23
|
+
Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
|
24
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "neverblock"
|
8
|
+
gem.summary = %Q{Utilities for non-blocking stack components}
|
9
|
+
gem.description = %Q{NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.}
|
10
|
+
gem.email = "conickal@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/conickal/neverblock"
|
12
|
+
gem.authors = ["Muhammad A. Ali", "Ahmed Sobhi", "Osama Brekaa", "Nicholas Silva"]
|
13
|
+
gem.add_dependency('eventmachine', '>= 0.12.2')
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
Dir['tasks/*.rake'].each { |rake| load rake }
|
22
|
+
|
23
|
+
task :default => :spec
|
24
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.6.2
|
@@ -0,0 +1,68 @@
|
|
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
|
+
super sql, name
|
15
|
+
id_value || @connection.insert_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_sql(sql, name = nil) #:nodoc:
|
19
|
+
super
|
20
|
+
@connection.affected_rows
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect
|
24
|
+
#initialize the connection pool
|
25
|
+
unless @connection
|
26
|
+
@connection = ::NB::DB::PooledDBConnection.new(@connection_options[0]) do
|
27
|
+
conn = ::NB::DB::FMysql.init
|
28
|
+
encoding = @config[:encoding]
|
29
|
+
if encoding
|
30
|
+
conn.options(::NB::DB::FMysql::SET_CHARSET_NAME, encoding) rescue nil
|
31
|
+
end
|
32
|
+
conn.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
|
33
|
+
conn.real_connect(*@connection_options[1..(@connection_options.length-1)])
|
34
|
+
NB.neverblock(false) do
|
35
|
+
conn.query("SET NAMES '#{encoding}'") if encoding
|
36
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
37
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
38
|
+
conn.query("SET SQL_AUTO_IS_NULL=0")
|
39
|
+
end
|
40
|
+
conn
|
41
|
+
end
|
42
|
+
else # we have a connection pool, we need to recover a connection
|
43
|
+
@connection.replace_acquired_connection
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class ActiveRecord::Base
|
50
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
51
|
+
def self.neverblock_mysql_connection(config) # :nodoc:
|
52
|
+
config = config.symbolize_keys
|
53
|
+
host = config[:host]
|
54
|
+
port = config[:port]
|
55
|
+
socket = config[:socket]
|
56
|
+
username = config[:username] ? config[:username].to_s : 'root'
|
57
|
+
password = config[:password].to_s
|
58
|
+
size = config[:connections] || 4
|
59
|
+
|
60
|
+
if config.has_key?(:database)
|
61
|
+
database = config[:database]
|
62
|
+
else
|
63
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
64
|
+
end
|
65
|
+
MysqlCompat.define_all_hashes_method!
|
66
|
+
::ActiveRecord::ConnectionAdapters::NeverBlockMysqlAdapter.new(nil, logger, [size.to_i, host, username, password, database, port, socket, nil], config)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,85 @@
|
|
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
|
+
# Executes an INSERT query and returns the new record's ID, this wont
|
13
|
+
# work on earlier versions of PostgreSQL but they don't suppor the async
|
14
|
+
# interface anyway
|
15
|
+
# def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
16
|
+
# @connection.exec(sql << " returning id ")
|
17
|
+
# end
|
18
|
+
|
19
|
+
def connect
|
20
|
+
@connection = ::NB::DB::PooledDBConnection.new(@connection_parameters[0]) do
|
21
|
+
conn = ::NB::DB::FiberedPostgresConnection.connect(*@connection_parameters[1..(@connection_parameters.length-1)])
|
22
|
+
=begin
|
23
|
+
::NB::DB::FiberedPostgresConnection.translate_results = false if ::NB::DB::FiberedPostgresConnection.respond_to?(:translate_results=)
|
24
|
+
# Ignore async_exec and async_query when using postgres-pr.
|
25
|
+
@async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
|
26
|
+
# Use escape string syntax if available. We cannot do this lazily when encountering
|
27
|
+
# the first string, because that could then break any transactions in progress.
|
28
|
+
# See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
|
29
|
+
# If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
|
30
|
+
# support escape string syntax. Don't override the inherited quoted_string_prefix.
|
31
|
+
NB.neverblock(false) do
|
32
|
+
if supports_standard_conforming_strings?
|
33
|
+
self.class.instance_eval do
|
34
|
+
define_method(:quoted_string_prefix) { 'E' }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
38
|
+
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
39
|
+
# should know about this but can't detect it there, so deal with it here.
|
40
|
+
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
41
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.module_eval(<<-end_eval)
|
42
|
+
def extract_precision(sql_type)
|
43
|
+
if sql_type =~ /^money$/
|
44
|
+
#{money_precision}
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end_eval
|
50
|
+
#configure_connection
|
51
|
+
end
|
52
|
+
conn
|
53
|
+
=end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Close then reopen the connection.
|
58
|
+
def reconnect!
|
59
|
+
disconnect!
|
60
|
+
connect
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class ActiveRecord::Base
|
66
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
67
|
+
def self.neverblock_postgresql_connection(config) # :nodoc:
|
68
|
+
config = config.symbolize_keys
|
69
|
+
host = config[:host]
|
70
|
+
port = config[:port] || 5432
|
71
|
+
username = config[:username].to_s
|
72
|
+
password = config[:password].to_s
|
73
|
+
size = config[:connections] || 4
|
74
|
+
|
75
|
+
if config.has_key?(:database)
|
76
|
+
database = config[:database]
|
77
|
+
else
|
78
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
79
|
+
end
|
80
|
+
|
81
|
+
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
82
|
+
# so just pass a nil connection object for the time being.
|
83
|
+
::ActiveRecord::ConnectionAdapters::NeverBlockPostgreSQLAdapter.new(nil, logger, [size, host, port, nil, nil, database, username, password], config)
|
84
|
+
end
|
85
|
+
end
|
data/lib/never_block.rb
ADDED
@@ -0,0 +1,102 @@
|
|
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
|
+
# 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
|
+
module NeverBlock
|
77
|
+
|
78
|
+
# Checks if we should be working in a non-blocking mode
|
79
|
+
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?
|
85
|
+
end
|
86
|
+
|
87
|
+
# The given block will run its queries either in blocking or non-blocking
|
88
|
+
# mode based on the first parameter
|
89
|
+
def self.neverblock(nb = true, &block)
|
90
|
+
status = Fiber.current[:neverblock]
|
91
|
+
Fiber.current[:neverblock] = nb
|
92
|
+
block.call
|
93
|
+
Fiber.current[:neverblock] = status
|
94
|
+
end
|
95
|
+
|
96
|
+
# Exception to be thrown for all neverblock internal errors
|
97
|
+
class NBError < StandardError
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
puts "Using Neverblock"
|
102
|
+
NB = NeverBlock
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module NeverBlock
|
2
|
+
module DB
|
3
|
+
module FiberedDBConnection
|
4
|
+
|
5
|
+
# Attaches the connection socket to an event loop and adds a callback
|
6
|
+
# to the fiber's callbacks that unregisters the connection from event loop
|
7
|
+
# Raises NB::NBError
|
8
|
+
def register_with_event_loop
|
9
|
+
#puts ">>>>>register_with_event_loop"
|
10
|
+
if EM.reactor_running?
|
11
|
+
@fiber = Fiber.current
|
12
|
+
#puts ">>>>>register_with_event_loop fiber #{@fiber.inspect}"
|
13
|
+
# When there's no previous em_connection
|
14
|
+
key = em_connection_with_pool_key
|
15
|
+
unless @fiber[key]
|
16
|
+
@fiber[key] = EM::watch(socket,EMConnectionHandler,self) { |c| c.notify_readable = true }
|
17
|
+
@fiber[:callbacks] << self.method(:unregister_from_event_loop)
|
18
|
+
@fiber[:em_keys] << key
|
19
|
+
end
|
20
|
+
else
|
21
|
+
raise ::NB::NBError.new("FiberedDBConnection: EventMachine reactor not running")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Unattaches the connection socket from the event loop
|
26
|
+
def unregister_from_event_loop
|
27
|
+
#puts ">>>>>unregister_from_event_loop #{self.inspect} #{@fiber.inspect}"
|
28
|
+
key = @fiber[:em_keys].pop
|
29
|
+
if em_c = @fiber[key]
|
30
|
+
em_c.detach
|
31
|
+
@fiber[key] = nil
|
32
|
+
true
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Removes the unregister_from_event_loop callback from the fiber's
|
39
|
+
# callbacks. It should be used when errors occur in an already registered
|
40
|
+
# connection
|
41
|
+
def remove_unregister_from_event_loop_callbacks
|
42
|
+
@fiber[:callbacks].delete self.method(:unregister_from_event_loop)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Closes the connection using event loop
|
46
|
+
def event_loop_connection_close
|
47
|
+
key = em_connection_with_pool_key
|
48
|
+
@fiber[key].close_connection if @fiber[key]
|
49
|
+
end
|
50
|
+
|
51
|
+
# The callback, this is called whenever
|
52
|
+
# there is data available at the socket
|
53
|
+
def resume_command
|
54
|
+
@fiber.resume if @fiber
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def em_connection_with_pool_key
|
59
|
+
"em_#{@fiber[:current_pool_key]}".intern
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module EMConnectionHandler
|
64
|
+
def initialize connection
|
65
|
+
@db_connection = connection
|
66
|
+
end
|
67
|
+
def notify_readable
|
68
|
+
@db_connection.resume_command
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'mysqlplus'
|
2
|
+
|
3
|
+
module NeverBlock
|
4
|
+
|
5
|
+
module DB
|
6
|
+
# A modified mysql connection driver. It builds on the original pg driver.
|
7
|
+
# This driver is able to register the socket at a certain backend (EM)
|
8
|
+
# and then whenever the query is executed within the scope of a friendly
|
9
|
+
# fiber. It will be done in async mode and the fiber will yield
|
10
|
+
class FiberedMysqlConnection < Mysql
|
11
|
+
|
12
|
+
include FiberedDBConnection
|
13
|
+
|
14
|
+
# Initializes the connection and remembers the connection params
|
15
|
+
def initialize(*args)
|
16
|
+
@connection_params = args
|
17
|
+
super(*@connection_params)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Does a normal real_connect if arguments are passed. If no arguments are
|
21
|
+
# passed it uses the ones it remembers
|
22
|
+
def real_connect(*args)
|
23
|
+
@connection_params = args unless args.empty?
|
24
|
+
super(*@connection_params)
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :connect, :real_connect
|
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 NB.event_loop_available? && NB.neverblocking?
|
35
|
+
raise ::NB::NBError.new("FiberedMysqlConnection: The running fiber is attached to a connection other than the current one") if (c = Fiber.current[Fiber.current[:current_pool_key]]) && c != self
|
36
|
+
begin
|
37
|
+
send_query sql
|
38
|
+
Fiber.yield register_with_event_loop
|
39
|
+
get_result
|
40
|
+
rescue Exception => e
|
41
|
+
if error = ['not connected', 'gone away', 'Lost connection'].detect{|msg| e.message.include? msg}
|
42
|
+
event_loop_connection_close
|
43
|
+
unregister_from_event_loop
|
44
|
+
remove_unregister_from_event_loop_callbacks
|
45
|
+
#connect
|
46
|
+
end
|
47
|
+
raise e
|
48
|
+
end
|
49
|
+
else
|
50
|
+
super(sql)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
alias_method :exec, :query
|
55
|
+
|
56
|
+
end #FiberedMySQLConnection
|
57
|
+
|
58
|
+
end #DB
|
59
|
+
|
60
|
+
end #NeverBlock
|
61
|
+
|
62
|
+
NeverBlock::DB::FMysql = NeverBlock::DB::FiberedMysqlConnection
|