neverblock 0.1.6.2

Sign up to get free protection for your applications and to get access to all the features.
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
+
@@ -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
@@ -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