espace-neverblock 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,21 @@
1
+ == NeverBlock
2
+ Never, ever!
3
+
4
+ NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
5
+
6
+ NeverBlock currently provides the following Libraries:
7
+
8
+ === FiberExtensions
9
+ A set of extenstions to the standard Fiber implementation
10
+
11
+ === NeverBlock::Pool::FiberPool
12
+ A pool of fibers that can be used to provide an upper limit to the numbers of active fibers in an application
13
+
14
+ === NeverBlock::Pool::FiberedConnectionPool
15
+ A generic fibered connection pool for all sorts of connections with support for transactions. This was mostly copied from Sequel::ConnectionPool
16
+
17
+ 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.
18
+
19
+ === License
20
+ Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
21
+
@@ -0,0 +1,80 @@
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
+ begin_db_transaction
15
+ super sql, name
16
+ id_value || @connection.insert_id
17
+ commit_db_transaction
18
+ end
19
+
20
+ def update_sql(sql, name = nil) #:nodoc:
21
+ begin_db_transaction
22
+ super
23
+ @connection.affected_rows
24
+ commit_db_transaction
25
+ end
26
+
27
+ def begin_db_transaction
28
+ @connection.begin_db_transaction
29
+ end
30
+
31
+ def commit_db_transaction
32
+ @connection.commit_db_transaction
33
+ end
34
+
35
+ def rollback_db_transaction
36
+ @connection.rollback_db_transaction
37
+ end
38
+
39
+ def connect
40
+ @connection = ::NB::DB::PooledFiberedMysqlConnection.new(@connection_options.shift) do
41
+ conn = ::NB::DB::FMysql.init
42
+ encoding = @config[:encoding]
43
+ if encoding
44
+ conn.options(::NB::DB::FMysql::SET_CHARSET_NAME, encoding) rescue nil
45
+ end
46
+ conn.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
47
+ conn.real_connect(*@connection_options)
48
+ NB.neverblock(false) do
49
+ conn.query("SET NAMES '#{encoding}'") if encoding
50
+ # By default, MySQL 'where id is null' selects the last inserted id.
51
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
52
+ conn.query("SET SQL_AUTO_IS_NULL=0")
53
+ end
54
+ conn.register_with_event_loop(:em)
55
+ conn
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ class ActiveRecord::Base
62
+ # Establishes a connection to the database that's used by all Active Record objects
63
+ def self.neverblock_mysql_connection(config) # :nodoc:
64
+ config = config.symbolize_keys
65
+ host = config[:host]
66
+ port = config[:port]
67
+ socket = config[:socket]
68
+ username = config[:username] ? config[:username].to_s : 'root'
69
+ password = config[:password].to_s
70
+ size = config[:connections] || 4
71
+
72
+ if config.has_key?(:database)
73
+ database = config[:database]
74
+ else
75
+ raise ArgumentError, "No database specified. Missing argument: database."
76
+ end
77
+ MysqlCompat.define_all_hashes_method!
78
+ ::ActiveRecord::ConnectionAdapters::NeverBlockMysqlAdapter.new(nil, logger, [size.to_i, host, username, password, database, port, socket, nil], config)
79
+ end
80
+ end
@@ -0,0 +1,102 @@
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
+ def begin_db_transaction
13
+ @connection.begin_db_transaction
14
+ end
15
+
16
+ def commit_db_transaction
17
+ @connection.commit_db_transaction
18
+ end
19
+
20
+ def rollback_db_transaction
21
+ @connection.rollback_db_transaction
22
+ end
23
+ # Executes an INSERT query and returns the new record's ID, this wont
24
+ # work on earlier versions of PostgreSQL but they don't suppor the async
25
+ # interface anyway
26
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
27
+ @connection.exec(sql << " returning id ")
28
+ end
29
+
30
+ def connect
31
+ size = @connection_parameters.shift
32
+ @connection = ::NB::DB::PooledFiberedPostgresConnection.new(@connection_parameters, size)
33
+
34
+ PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
35
+
36
+ # Ignore async_exec and async_query when using postgres-pr.
37
+ @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
38
+
39
+ # Use escape string syntax if available. We cannot do this lazily when encountering
40
+ # the first string, because that could then break any transactions in progress.
41
+ # See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
42
+ # If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
43
+ # support escape string syntax. Don't override the inherited quoted_string_prefix.
44
+ NB.neverblock(false) do
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
63
+ end
64
+ end
65
+ end_eval
66
+
67
+ configure_connection
68
+ @connection.commit_db_transaction
69
+
70
+ end
71
+
72
+ end
73
+
74
+ # Close then reopen the connection.
75
+ def reconnect!
76
+ disconnect!
77
+ connect
78
+ end
79
+
80
+ end
81
+
82
+ class ActiveRecord::Base
83
+ # Establishes a connection to the database that's used by all Active Record objects
84
+ def self.neverblock_postgresql_connection(config) # :nodoc:
85
+ config = config.symbolize_keys
86
+ host = config[:host]
87
+ port = config[:port] || 5432
88
+ username = config[:username].to_s
89
+ password = config[:password].to_s
90
+ size = config[:connections] || 4
91
+
92
+ if config.has_key?(:database)
93
+ database = config[:database]
94
+ else
95
+ raise ArgumentError, "No database specified. Missing argument: database."
96
+ end
97
+
98
+ # The postgres drivers don't allow the creation of an unconnected PGconn object,
99
+ # so just pass a nil connection object for the time being.
100
+ ::ActiveRecord::ConnectionAdapters::NeverBlockPostgreSQLAdapter.new(nil, logger, [size, host, port, nil, nil, database, username, password], config)
101
+ end
102
+ end
@@ -0,0 +1,97 @@
1
+ require 'mysql'
2
+
3
+ module NeverBlock
4
+
5
+ module DB
6
+ # A modified postgres connection driver
7
+ # builds on the original pg driver.
8
+ # This driver is able to register the socket
9
+ # at a certain backend (EM or Rev)
10
+ # and then whenever the query is executed
11
+ # within the scope of a friendly fiber
12
+ # it will be done in async mode and the fiber
13
+ # will yield
14
+ class FiberedMysqlConnection < Mysql
15
+ # needed to access the sockect by the event loop
16
+ attr_reader :fd, :io
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
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 Fiber.respond_to? :current and Fiber.current[:neverblock]
35
+ send_query sql
36
+ @fiber = Fiber.current
37
+ Fiber.yield
38
+ else
39
+ super(sql)
40
+ end
41
+ end
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
51
+ end
52
+ if EM.reactor_running?
53
+ @em_connection = EM::attach(@io,EMConnectionHandler,self)
54
+ else
55
+ raise "REACTOR NOT RUNNING YA ZALAMA"
56
+ end
57
+ elsif loop.class.name == "REV::Loop"
58
+ loop.attach(RevConnectionHandler.new(socket))
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
72
+ end
73
+
74
+ # The callback, this is called whenever
75
+ # there is data available at the socket
76
+ def process_command
77
+ @fiber.resume get_result
78
+ end
79
+
80
+ end #FiberedPostgresConnection
81
+
82
+ # A connection handler for EM
83
+ # More to follow.
84
+ module EMConnectionHandler
85
+ def initialize connection
86
+ @connection = connection
87
+ end
88
+ def notify_readable
89
+ @connection.process_command
90
+ end
91
+ end
92
+
93
+ end #DB
94
+
95
+ end #NeverBlock
96
+
97
+ NeverBlock::DB::FMysql = NeverBlock::DB::FiberedMysqlConnection
@@ -0,0 +1,109 @@
1
+ require 'pg'
2
+
3
+ module NeverBlock
4
+
5
+ module DB
6
+ # A modified postgres connection driver
7
+ # builds on the original pg driver.
8
+ # This driver is able to register the socket
9
+ # at a certain backend (EM or Rev)
10
+ # and then whenever the query is executed
11
+ # within the scope of a friendly fiber
12
+ # it will be done in async mode and the fiber
13
+ # will yield
14
+ class FiberedPostgresConnection < PGconn
15
+ # needed to access the sockect by the event loop
16
+ attr_reader :fd, :io
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
+
28
+ # Assuming the use of NeverBlock fiber extensions and that the exec is run in
29
+ # the context of a fiber. One that have the value :neverblock set to true.
30
+ # All neverblock IO classes check this value, setting it to false will force
31
+ # the execution in a blocking way.
32
+ def exec(sql)
33
+ if Fiber.respond_to? :current and Fiber.current[:neverblock]
34
+ self.send_query sql
35
+ @fiber = Fiber.current
36
+ Fiber.yield
37
+ else
38
+ super(sql)
39
+ end
40
+ end
41
+
42
+ # Attaches the connection socket to an event loop.
43
+ # Currently only supports EM, but Rev support will be
44
+ # completed soon.
45
+ def register_with_event_loop(loop)
46
+ if loop == :em
47
+ unless EM.respond_to?(:attach)
48
+ puts "invalide EM version, please download the modified gem from: (http://github.com/riham/eventmachine)"
49
+ exit
50
+ end
51
+ if EM.reactor_running?
52
+ @em_connection = EM::attach(@io,EMConnectionHandler,self)
53
+ else
54
+ raise "REACTOR NOT RUNNING YA ZALAMA"
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
62
+ end
63
+
64
+ # Unattaches the connection socket from the event loop
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
73
+
74
+ # The callback, this is called whenever
75
+ # there is data available at the socket
76
+ def process_command
77
+ # make sure all commands are sent
78
+ # before attempting to read
79
+ #return unless self.flush
80
+ self.consume_input
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
90
+ end
91
+
92
+ 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
+
105
+ end #DB
106
+
107
+ end #NeverBlock
108
+
109
+ NeverBlock::DB::FPGconn = NeverBlock::DB::FiberedPostgresConnection
@@ -0,0 +1,78 @@
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 PooledFiberedMysqlConnection
8
+
9
+ # Requires a hash or an array with connection parameters
10
+ # and a pool size (defaults to 4)
11
+ def initialize(size=4, &block)
12
+ @pool = NB::Pool::FiberedConnectionPool.new(:size=>size, :eager=>true) do
13
+ yield
14
+ end
15
+ end
16
+
17
+ # A proxy for the connection's exec method
18
+ # quries the pool to get a connection first
19
+ def exec(query)
20
+ @pool.hold do |conn|
21
+ conn.query(query)
22
+ end
23
+ end
24
+
25
+ alias :query :exec
26
+ # This method must be called for transactions to work correctly.
27
+ # One cannot just send "begin" as you never know which connection
28
+ # will be available next. This method ensures you get the same connection
29
+ # while in a transaction.
30
+ def begin_db_transaction
31
+ @pool.hold(true) do |conn|
32
+ conn.exec("begin")
33
+ end
34
+ end
35
+
36
+ # see =begin_db_transaction
37
+ def rollback_db_transaction
38
+ @pool.hold do |conn|
39
+ conn.exec("rollback")
40
+ @pool.release(Fiber.current,conn)
41
+ end
42
+ end
43
+
44
+ # see =begin_db_transaction
45
+ def commit_db_transaction
46
+ @pool.hold do |conn|
47
+ conn.exec("commit")
48
+ @pool.release(Fiber.current,conn)
49
+ end
50
+ end
51
+
52
+ #close all connections and remove them from the event loop
53
+ def close
54
+ @pool.all_connections do |conn|
55
+ conn.unregister_from_event_loop
56
+ conn.close
57
+ end
58
+ end
59
+
60
+ # Pass unknown methods to the connection
61
+ def method_missing(method, *args)
62
+ @pool.hold do |conn|
63
+ conn.send(method, *args)
64
+ end
65
+ end
66
+
67
+ # Pass method queries to the connection
68
+ def respond_to?(method)
69
+ @pool.hold do |conn|
70
+ conn.respond_to?(method)
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+
78
+ NeverBlock::DB::PFMysql = NeverBlock::DB::PooledFiberedMysqlConnection
@@ -0,0 +1,81 @@
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
+
@@ -0,0 +1,30 @@
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
+ end
30
+
@@ -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(Fiber.current['start_db_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,37 @@
1
+ require 'neverblock' unless defined?(NeverBlock)
2
+ #require 'actionpack'
3
+ #require 'action_controller'
4
+
5
+ # Rails tries to protect dispatched actions
6
+ # by wrapping them in a synchronized code
7
+ # block, since fibers hate synchronized
8
+ # blocks we will trick the guard and
9
+ # transform it (without it knowing) to
10
+ # something more subtle
11
+
12
+
13
+ =begin
14
+ class ActionController::Dispatcher
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)
22
+ # now you don't!
23
+ block.call
24
+ end
25
+ end
26
+ =end
27
+
28
+
29
+ require 'thread'
30
+
31
+ # now you synchronize
32
+ class Mutex
33
+ def synchronize(&block)
34
+ # now you don't!
35
+ block.call
36
+ end
37
+ end
@@ -0,0 +1,76 @@
1
+ # we need Fiber.current
2
+ # so we must require
3
+ # fiber
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
+ #require 'fiber'
9
+
10
+ module NeverBlock
11
+ module Pool
12
+
13
+ # Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
14
+ # Copyright:: Copyright (c) 2008 eSpace, Inc.
15
+ # License:: Distributes under the same terms as Ruby
16
+ #
17
+ # A pool of initialized fibers
18
+ # It does not grow in size or create transient fibers
19
+ # It will queue code blocks when needed (if all its fibers are busy)
20
+ #
21
+ # This class is particulary useful when you use the fibers
22
+ # to connect to evented back ends. It also does not generate
23
+ # transient objects and thus saves memory.
24
+ #
25
+ # Example:
26
+ # fiber_pool = NeverBlock::Pool::FiberPool.new(150)
27
+ #
28
+ # loop do
29
+ # fiber_pool.spawn do
30
+ # #fiber body goes here
31
+ # end
32
+ # end
33
+ #
34
+ class FiberPool
35
+
36
+ # gives access to the currently free fibers
37
+ attr_reader :fibers
38
+
39
+ # Prepare a list of fibers
40
+ # that are able to run different
41
+ # blocks of code every time
42
+ # once a fiber is done with its block
43
+ # it attempts to fetch another one
44
+ # from the queue.
45
+ def initialize(count = 50)
46
+ @fibers,@queue = [],[]
47
+ count.times do |i|
48
+ fiber = Fiber.new do |block|
49
+ loop do
50
+ block.call
51
+ unless @queue.empty?
52
+ block = @queue.shift
53
+ else
54
+ block = Fiber.yield @fibers << Fiber.current
55
+ end
56
+ end
57
+ end
58
+ fiber[:neverblock] = true
59
+ @fibers << fiber
60
+ end
61
+ end
62
+
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
@@ -0,0 +1,118 @@
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
+ module NeverBlock
6
+ module Pool
7
+
8
+ # Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
9
+ # Copyright:: Copyright (c) 2008 eSpace, Inc.
10
+ # License:: Distributes under the same terms as Ruby
11
+ #
12
+ # This class represents a pool of connections,
13
+ # you hold or release conncetions from the pool
14
+ # hold requests that cannot be fullfiled will be queued
15
+ # the fiber will be paused and resumed later when
16
+ # a connection is avaialble
17
+ #
18
+ # Large portions of this class were copied and pasted
19
+ # form Sequel's threaded connection pool
20
+ #
21
+ # Example:
22
+ #
23
+ # pool = NeverBlock::Pool::FiberedConnectionPool.new(:size=>16)do
24
+ # # connection creation code goes here
25
+ # end
26
+ # 32.times do
27
+ # Fiber.new do
28
+ # conn = pool.hold # hold will pause the fiber until a connection is available
29
+ # conn.execute('something') # you can use the connection normally now
30
+ # end.resume
31
+ # end
32
+ #
33
+ # The pool has support for transactions, just pass true to the pool#hold method
34
+ # and the connection will not be released after the block is finished
35
+ # It is the responsibility of client code to release the connection
36
+ class FiberedConnectionPool
37
+
38
+ # initialize the connection pool
39
+ # using the supplied proc to create the connections
40
+ # you can choose to start them eagerly or lazily (lazy by default)
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
+ # If a connection is available,
53
+ # pass it to the block, otherwise
54
+ # pass the fiber to the queue
55
+ # till a connection is available
56
+ # when done with a connection
57
+ # try to porcess other fibers in the queue
58
+ # before releasing the connection
59
+ # if inside a transaction, don't release the fiber
60
+ def hold(transactional = false)
61
+ fiber = Fiber.current
62
+ if conn = @busy_connections[fiber]
63
+ return yield(conn)
64
+ end
65
+ conn = acquire(fiber)
66
+ begin
67
+ yield conn
68
+ ensure
69
+ release(fiber, conn) unless transactional
70
+ process_queue
71
+ end
72
+ end
73
+
74
+ # Give the fiber back to the pool
75
+ # you have to call this explicitly if
76
+ # you held a connection for a transaction
77
+ def release(fiber, conn)
78
+ @busy_connections.delete(fiber)
79
+ @connections << conn
80
+ end
81
+
82
+ def all_connections
83
+ (@connections + @busy_connections.values).each {|conn| yield(conn)}
84
+ end
85
+
86
+ private
87
+
88
+ # Can we find a connection?
89
+ # Can we create one?
90
+ # Wait in the queue then
91
+ def acquire(fiber)
92
+ if !@connections.empty?
93
+ @busy_connections[fiber] = @connections.shift
94
+ elsif (@connections.length + @busy_connections.length) < @size
95
+ conn = @connection_proc.call
96
+ @busy_connections[fiber] = conn
97
+ else
98
+ Fiber.yield @queue << fiber
99
+ end
100
+ end
101
+
102
+ # Check if there are waiting fibers and
103
+ # try to process them
104
+ def process_queue
105
+ while !@connections.empty? and !@queue.empty?
106
+ fiber = @queue.shift
107
+ # What is really happening here?
108
+ # we are resuming a fiber from within
109
+ # another, should we call transfer insted?
110
+ fiber.resume @busy_connections[fiber] = @connections.shift
111
+ end
112
+ end
113
+
114
+ end #FiberedConnectionPool
115
+
116
+ end #Pool
117
+
118
+ end #NeverBlock
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+ require 'neverblock' unless defined?(NeverBlock)
3
+ require 'thin'
4
+
5
+ module Thin
6
+
7
+ # Patch the thin server to use
8
+ # NeverBlock::Pool::FiberPool
9
+ # to be able to wrap requests
10
+ # in fibers
11
+ class Server
12
+
13
+ DEFAULT_FIBER_POOL_SIZE = 20
14
+
15
+ def fiber_pool
16
+ @fiber_pool ||= NB::Pool::FiberPool.new(DEFAULT_FIBER_POOL_SIZE)
17
+ end
18
+
19
+ end # Server
20
+
21
+ # A request is processed by wrapping it
22
+ # in a fiber from the fiber pool. If all
23
+ # the fibers are busy the request will
24
+ # wait in a queue to be picked up later.
25
+ # Meanwhile, the server will still be
26
+ # processing requests
27
+ class Connection < EventMachine::Connection
28
+
29
+ def process
30
+ @request.threaded = false
31
+ @backend.server.fiber_pool.spawn{post_process(pre_process)}
32
+ end
33
+
34
+ end # Connection
35
+
36
+
37
+ end # Thin
@@ -0,0 +1,84 @@
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
+ unless defined? Fiber
8
+ require 'thread'
9
+ require 'singleton'
10
+ class FiberError < StandardError; end
11
+ class Fiber
12
+ def initialize
13
+ raise ArgumentError, 'new Fiber requires a block' unless block_given?
14
+
15
+ @yield = Queue.new
16
+ @resume = Queue.new
17
+
18
+ @thread = Thread.new{ @yield.push [ *yield(*@resume.pop) ] }
19
+ @thread.abort_on_exception = true
20
+ @thread[:fiber] = self
21
+ end
22
+ attr_reader :thread
23
+
24
+ def resume *args
25
+ raise FiberError, 'dead fiber called' unless @thread.alive?
26
+ @resume.push(args)
27
+ result = @yield.pop
28
+ result.size > 1 ? result : result.first
29
+ end
30
+
31
+ def yield *args
32
+ @yield.push(args)
33
+ result = @resume.pop
34
+ result.size > 1 ? result : result.first
35
+ end
36
+
37
+ def self.yield *args
38
+ raise FiberError, "can't yield from root fiber" unless fiber = Thread.current[:fiber]
39
+ fiber.yield(*args)
40
+ end
41
+
42
+ def self.current
43
+ Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
44
+ end
45
+
46
+ def inspect
47
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}>"
48
+ end
49
+ end
50
+
51
+ class RootFiber < Fiber
52
+ include Singleton
53
+ def initialize
54
+ end
55
+
56
+ def resume *args
57
+ raise FiberError, "can't resume root fiber"
58
+ end
59
+
60
+ def yield *args
61
+ raise FiberError, "can't yield from root fiber"
62
+ end
63
+ end
64
+
65
+ #attach the root fiber to the main thread
66
+ Thread.main[:fiber] = RootFiber.instance
67
+ else
68
+ require 'fiber'
69
+ end
70
+
71
+ require 'never_block/extensions/fiber_extensions'
72
+ require 'never_block/pool/fiber_pool'
73
+ require 'never_block/pool/fibered_connection_pool'
74
+
75
+ module NeverBlock
76
+ def self.neverblock(nb = true, &block)
77
+ status = Fiber.current[:neverblock]
78
+ Fiber.current[:neverblock] = nb
79
+ block.call
80
+ Fiber.current[:neverblock] = status
81
+ end
82
+ end
83
+
84
+ NB = NeverBlock
@@ -0,0 +1,4 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+ require 'neverblock'
3
+ require 'never_block/db/fibered_mysql_connection'
4
+ require 'never_block/db/pooled_fibered_mysql_connection'
@@ -0,0 +1,4 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'never_block/db/fibered_postgres_connection'
4
+ require 'never_block/db/pooled_fibered_postgres_connection'
data/lib/neverblock.rb ADDED
@@ -0,0 +1,8 @@
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
+ require 'never_block'
8
+
@@ -0,0 +1,35 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "neverblock"
3
+ s.version = "0.1.2"
4
+ s.date = "2008-09-04"
5
+ s.summary = "Utilities for non-blocking stack components"
6
+ s.email = "oldmoe@gmail.com"
7
+ s.homepage = "http://github.com/oldmoe/neverblock"
8
+ s.description = "NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner."
9
+ s.has_rdoc = true
10
+ s.authors = ["Muhammad A. Ali", "Ahmed Sobhi", "Osama Brekaa"]
11
+ s.files = [
12
+ "neverblock.gemspec",
13
+ "README",
14
+ "lib/neverblock.rb",
15
+ "lib/never_block.rb",
16
+ "lib/neverblock-pg.rb",
17
+ "lib/neverblock-mysql.rb",
18
+ "lib/never_block/extensions/fiber_extensions.rb",
19
+ "lib/never_block/pool/fiber_pool.rb",
20
+ "lib/never_block/pool/fibered_connection_pool.rb",
21
+ "lib/never_block/frameworks/rails.rb",
22
+ "lib/never_block/frameworks/activerecord.rb",
23
+ "lib/never_block/servers/thin.rb",
24
+ "lib/never_block/db/fibered_postgres_connection.rb",
25
+ "lib/never_block/db/pooled_fibered_postgres_connection.rb",
26
+ "lib/never_block/db/fibered_mysql_connection.rb",
27
+ "lib/never_block/db/pooled_fibered_mysql_connection.rb",
28
+ "lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb",
29
+ "lib/active_record/connection_adapters/neverblock_mysql_adapter.rb"
30
+ ]
31
+ s.rdoc_options = ["--main", "README"]
32
+ s.extra_rdoc_files = ["README"]
33
+ end
34
+
35
+
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: espace-neverblock
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Muhammad A. Ali
8
+ - Ahmed Sobhi
9
+ - Osama Brekaa
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2008-09-04 00:00:00 -07:00
15
+ default_executable:
16
+ dependencies: []
17
+
18
+ description: NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
19
+ email: oldmoe@gmail.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files:
25
+ - README
26
+ files:
27
+ - neverblock.gemspec
28
+ - README
29
+ - lib/neverblock.rb
30
+ - lib/never_block.rb
31
+ - lib/neverblock-pg.rb
32
+ - lib/neverblock-mysql.rb
33
+ - lib/never_block/extensions/fiber_extensions.rb
34
+ - lib/never_block/pool/fiber_pool.rb
35
+ - lib/never_block/pool/fibered_connection_pool.rb
36
+ - lib/never_block/frameworks/rails.rb
37
+ - lib/never_block/frameworks/activerecord.rb
38
+ - lib/never_block/servers/thin.rb
39
+ - lib/never_block/db/fibered_postgres_connection.rb
40
+ - lib/never_block/db/pooled_fibered_postgres_connection.rb
41
+ - lib/never_block/db/fibered_mysql_connection.rb
42
+ - lib/never_block/db/pooled_fibered_mysql_connection.rb
43
+ - lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb
44
+ - lib/active_record/connection_adapters/neverblock_mysql_adapter.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/oldmoe/neverblock
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --main
50
+ - README
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.2.0
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: Utilities for non-blocking stack components
72
+ test_files: []
73
+