espace-neverblock 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,50 +11,38 @@ class ActiveRecord::ConnectionAdapters::NeverBlockMysqlAdapter < ActiveRecord::C
11
11
  end
12
12
 
13
13
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
14
- begin_db_transaction
15
14
  super sql, name
16
15
  id_value || @connection.insert_id
17
- commit_db_transaction
18
16
  end
19
17
 
20
18
  def update_sql(sql, name = nil) #:nodoc:
21
- begin_db_transaction
22
19
  super
23
20
  @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
21
  end
38
22
 
39
23
  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")
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
53
41
  end
54
- conn.register_with_event_loop(:em)
55
- conn
42
+ else # we have a connection pool, we need to recover a connection
43
+ @connection.replace_acquired_connection
56
44
  end
57
- end
45
+ end
58
46
 
59
47
  end
60
48
 
@@ -28,47 +28,38 @@ class ActiveRecord::ConnectionAdapters::NeverBlockPostgreSQLAdapter < ActiveReco
28
28
  end
29
29
 
30
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
31
+ @connection = ::NB::DB::PooledFiberedPostgresConnection.new(@connection_options[0]) do
32
+ conn = PGconn.connect(*@connection_options[1..(@connection_options.length-1)])
33
+ PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
34
+ # Ignore async_exec and async_query when using postgres-pr.
35
+ @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
36
+ # Use escape string syntax if available. We cannot do this lazily when encountering
37
+ # the first string, because that could then break any transactions in progress.
38
+ # See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
39
+ # If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
40
+ # support escape string syntax. Don't override the inherited quoted_string_prefix.
41
+ NB.neverblock(false) do
42
+ if supports_standard_conforming_strings?
43
+ self.class.instance_eval do
44
+ define_method(:quoted_string_prefix) { 'E' }
63
45
  end
64
46
  end
65
- end_eval
66
-
67
- configure_connection
68
- @connection.commit_db_transaction
69
-
47
+ # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
48
+ # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
49
+ # should know about this but can't detect it there, so deal with it here.
50
+ money_precision = (postgresql_version >= 80300) ? 19 : 10
51
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.module_eval(<<-end_eval)
52
+ def extract_precision(sql_type)
53
+ if sql_type =~ /^money$/
54
+ #{money_precision}
55
+ else
56
+ super
57
+ end
58
+ end
59
+ end_eval
60
+ #configure_connection
61
+ end
70
62
  end
71
-
72
63
  end
73
64
 
74
65
  # Close then reopen the connection.
data/lib/never_block.rb CHANGED
@@ -4,6 +4,7 @@
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
7
8
  unless defined? Fiber
8
9
  require 'thread'
9
10
  require 'singleton'
@@ -73,12 +74,29 @@ require 'never_block/pool/fiber_pool'
73
74
  require 'never_block/pool/fibered_connection_pool'
74
75
 
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
76
89
  def self.neverblock(nb = true, &block)
77
90
  status = Fiber.current[:neverblock]
78
91
  Fiber.current[:neverblock] = nb
79
92
  block.call
80
93
  Fiber.current[:neverblock] = status
81
94
  end
95
+
96
+ # Exception to be thrown for all neverblock internal errors
97
+ class NBError < StandardError
98
+ end
99
+
82
100
  end
83
101
 
84
102
  NB = NeverBlock
@@ -0,0 +1,64 @@
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
+ unless @fiber[:em_connection]
15
+ @fiber[:em_connection] = EM::attach(socket,EMConnectionHandler,self)
16
+ @fiber[:callbacks] << self.method(:unregister_from_event_loop)
17
+ end
18
+ else
19
+ raise ::NB::NBError.new("FiberedDBConnection: EventMachine reactor not running")
20
+ end
21
+ end
22
+
23
+ # Unattaches the connection socket from the event loop
24
+ def unregister_from_event_loop
25
+ #puts ">>>>>unregister_from_event_loop #{self.inspect} #{@fiber.inspect}"
26
+ if em_c = @fiber[:em_connection]
27
+ em_c.detach
28
+ @fiber[:em_connection] = nil
29
+ true
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ # Removes the unregister_from_event_loop callback from the fiber's
36
+ # callbacks. It should be used when errors occur in an already registered
37
+ # connection
38
+ def remove_unregister_from_event_loop_callbacks
39
+ @fiber[:callbacks].delete self.method(:unregister_from_event_loop)
40
+ end
41
+
42
+ # Closes the connection using event loop
43
+ def event_loop_connection_close
44
+ @fiber[:em_connection].close_connection if @fiber[:em_connection]
45
+ end
46
+
47
+ # The callback, this is called whenever
48
+ # there is data available at the socket
49
+ def resume_command
50
+ @fiber.resume if @fiber
51
+ end
52
+
53
+ end
54
+
55
+ module EMConnectionHandler
56
+ def initialize connection
57
+ @db_connection = connection
58
+ end
59
+ def notify_readable
60
+ @db_connection.resume_command
61
+ end
62
+ end
63
+ end
64
+ end
@@ -3,115 +3,59 @@ require 'mysqlplus'
3
3
  module NeverBlock
4
4
 
5
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
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
14
10
  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 self.real_connect(*args)
22
- me = super(*args)
23
- me.init_descriptor
24
- me
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)
25
18
  end
26
- #alias :real_connect :initialize
27
- #alias :connect :initialize
28
-
29
- def init_descriptor
30
- @fd = socket
31
- @io = IO.new(socket)
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)
32
25
  end
33
-
26
+
27
+ alias_method :connect, :real_connect
28
+
34
29
  # Assuming the use of NeverBlock fiber extensions and that the exec is run in
35
30
  # the context of a fiber. One that have the value :neverblock set to true.
36
31
  # All neverblock IO classes check this value, setting it to false will force
37
32
  # the execution in a blocking way.
38
33
  def query(sql)
39
- begin
40
- if Fiber.respond_to? :current and Fiber.current[:neverblock]
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[:connection]) && c != self
36
+ begin
41
37
  send_query sql
42
- @fiber = Fiber.current
43
- Fiber.yield
44
- get_result
45
- else
46
- super(sql)
47
- end
48
- rescue Exception => e
49
- reconnect if e.msg.include? "not connected"
50
- raise e
51
- end
52
- end
53
-
54
- # reset the connection
55
- # and reattach to the
56
- # event loop
57
- def reconnect
58
- unregister_from_event_loop
59
- super
60
- init_descriptor
61
- register_with_event_loop(@loop)
62
- end
63
-
64
- # Attaches the connection socket to an event loop.
65
- # Currently only supports EM, but Rev support will be
66
- # completed soon.
67
- def register_with_event_loop(loop)
68
- if loop == :em
69
- unless EM.respond_to?(:attach)
70
- puts "invalide EM version, please download the modified gem from: (http://github.com/riham/eventmachine)"
71
- exit
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
72
48
  end
73
- if EM.reactor_running?
74
- @em_connection = EM::attach(@io,EMConnectionHandler,self)
75
- else
76
- raise "REACTOR NOT RUNNING YA ZALAMA"
77
- end
78
- elsif loop.class.name == "REV::Loop"
79
- loop.attach(RevConnectionHandler.new(socket))
80
49
  else
81
- raise "could not register with the event loop"
82
- end
83
- @loop = loop
84
- end
85
-
86
- # Unattaches the connection socket from the event loop
87
- def unregister_from_event_loop
88
- if @loop == :em
89
- @em_connection.unattach(false)
90
- else
91
- raise NotImplementedError.new("unregister_from_event_loop not implemented for #{@loop}")
50
+ super(sql)
92
51
  end
93
52
  end
94
-
95
- # The callback, this is called whenever
96
- # there is data available at the socket
97
- def resume_command
98
- @fiber.resume
99
- end
100
53
 
101
- end #FiberedPostgresConnection
102
-
103
- # A connection handler for EM
104
- # More to follow.
105
- module EMConnectionHandler
106
- def initialize connection
107
- @connection = connection
108
- end
109
- def notify_readable
110
- @connection.resume_command
111
- end
112
- end
54
+ alias_method :exec, :query
55
+
56
+ end #FiberedMySQLConnection
113
57
 
114
- end #DB
58
+ end #DB
115
59
 
116
60
  end #NeverBlock
117
61
 
@@ -3,6 +3,7 @@ require 'pg'
3
3
  module NeverBlock
4
4
 
5
5
  module DB
6
+
6
7
  # A modified postgres connection driver
7
8
  # builds on the original pg driver.
8
9
  # This driver is able to register the socket
@@ -12,32 +13,21 @@ module NeverBlock
12
13
  # it will be done in async mode and the fiber
13
14
  # will yield
14
15
  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
- init_descriptor
24
- #setnonblocking(true)
25
- end
26
-
27
- def init_descriptor
28
- @fd = socket
29
- @io = IO.new(socket)
30
- end
16
+
17
+ include FiberedDBConnection
18
+
31
19
  # Assuming the use of NeverBlock fiber extensions and that the exec is run in
32
20
  # the context of a fiber. One that have the value :neverblock set to true.
33
21
  # All neverblock IO classes check this value, setting it to false will force
34
22
  # the execution in a blocking way.
35
23
  def exec(sql)
36
- begin
37
- if Fiber.respond_to? :current and Fiber.current[:neverblock]
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
38
28
  send_query sql
39
- @fiber = Fiber.current
40
- Fiber.yield
29
+ @fiber = Fiber.current
30
+ Fiber.yield register_with_event_loop
41
31
  while is_busy
42
32
  consume_input
43
33
  Fiber.yield if is_busy
@@ -47,77 +37,25 @@ module NeverBlock
47
37
  res = self.get_result
48
38
  data << res unless res.nil?
49
39
  end
50
- data.last
51
- else
52
- super(sql)
53
- end
54
- rescue Exception => e
55
- reset if e.msg.include? "not connected"
56
- raise e
57
- end
58
- end
59
-
60
- # reset the connection
61
- # and reattach to the
62
- # event loop
63
- def reset
64
- unregister_from_event_loop
65
- super
66
- init_descriptor
67
- register_with_event_loop(@loop)
68
- end
69
-
70
- # Attaches the connection socket to an event loop.
71
- # Currently only supports EM, but Rev support will be
72
- # completed soon.
73
- def register_with_event_loop(loop)
74
- if loop == :em
75
- unless EM.respond_to?(:attach)
76
- puts "invalide EM version, please download the modified gem from: (http://github.com/riham/eventmachine)"
77
- exit
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
78
50
  end
79
- if EM.reactor_running?
80
- @em_connection = EM::attach(@io,EMConnectionHandler,self)
81
- else
82
- raise "REACTOR NOT RUNNING YA ZALAMA"
83
- end
84
- elsif loop.class.name == "REV::Loop"
85
- loop.attach(RevConnectionHandler.new(socket))
86
51
  else
87
- raise "could not register with the event loop"
52
+ super(sql)
88
53
  end
89
- @loop = loop
90
54
  end
91
55
 
92
- # Unattaches the connection socket from the event loop
93
- # As with register, EM is the only one supported for now
94
- def unregister_from_event_loop
95
- if @loop == :em
96
- @em_connection.unattach(false)
97
- else
98
- raise NotImplementedError.new("unregister_from_event_loop not implemented for #{@loop}")
99
- end
100
- end
101
-
102
- # The callback, this is called whenever
103
- # there is data available at the socket
104
- def resume_command
105
- #let the fiber continue its work
106
- @fiber.resume
107
- end
108
-
56
+ alias_method :query, :exec
57
+
109
58
  end #FiberedPostgresConnection
110
-
111
- # A connection handler for EM
112
- # More to follow.
113
- module EMConnectionHandler
114
- def initialize connection
115
- @connection = connection
116
- end
117
- def notify_readable
118
- @connection.resume_command
119
- end
120
- end
121
59
 
122
60
  end #DB
123
61
 
@@ -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
@@ -25,6 +25,7 @@ class Fiber
25
25
 
26
26
  def local_fiber_variables
27
27
  @local_fiber_variables ||= {}
28
- end
28
+ end
29
+
29
30
  end
30
31
 
@@ -1,12 +1,3 @@
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
1
  module NeverBlock
11
2
  module Pool
12
3
 
@@ -31,46 +22,52 @@ module NeverBlock
31
22
  # end
32
23
  # end
33
24
  #
34
- class FiberPool
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[:neverblock] = true
52
+ @fibers << fiber
53
+ end
54
+ end
35
55
 
36
- # gives access to the currently free fibers
37
- attr_reader :fibers
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
38
69
 
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
70
+ end # FiberPool
71
+ end # Pool
72
+ end # NeverBlock
62
73
 
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
@@ -1,14 +1,5 @@
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
1
  module NeverBlock
6
2
  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
3
  # This class represents a pool of connections,
13
4
  # you hold or release conncetions from the pool
14
5
  # hold requests that cannot be fullfiled will be queued
@@ -25,19 +16,28 @@ module NeverBlock
25
16
  # end
26
17
  # 32.times do
27
18
  # 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
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
30
23
  # end.resume
31
24
  # end
32
25
  #
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
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
35
29
  # It is the responsibility of client code to release the connection
36
30
  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)
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
41
  def initialize(options = {}, &block)
42
42
  @connections, @busy_connections, @queue = [], {},[]
43
43
  @connection_proc = block
@@ -45,38 +45,23 @@ module NeverBlock
45
45
  if options[:eager]
46
46
  @size.times do
47
47
  @connections << @connection_proc.call
48
- end
48
+ end
49
49
  end
50
50
  end
51
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
52
+ def replace_acquired_connection
53
+ fiber = Fiber.current
54
+ conn = @connection_proc.call
55
+ @busy_connections[fiber] = conn
56
+ fiber[:connection] = conn
57
+ end
73
58
 
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
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
80
65
  end
81
66
 
82
67
  def all_connections
@@ -89,14 +74,36 @@ module NeverBlock
89
74
  # Can we create one?
90
75
  # Wait in the queue then
91
76
  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
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
+ return fiber[:connection] if fiber[:connection]
83
+ conn = if !@connections.empty?
84
+ @connections.shift
85
+ elsif (@connections.length + @busy_connections.length) < @size
86
+ @connection_proc.call
87
+ else
98
88
  Fiber.yield @queue << fiber
99
- end
89
+ end
90
+
91
+ # They're called in reverse order i.e. release then process_queue
92
+ fiber[:callbacks] << self.method(:process_queue)
93
+ fiber[:callbacks] << self.method(:release)
94
+
95
+ @busy_connections[fiber] = conn
96
+ fiber[:connection] = conn
97
+ end
98
+
99
+ # Give the fiber's connection back to the pool
100
+ def release()
101
+ fiber = Fiber.current
102
+ if fiber[:connection]
103
+ @busy_connections.delete(fiber)
104
+ @connections << fiber[:connection]
105
+ fiber[:connection] = nil
106
+ end
100
107
  end
101
108
 
102
109
  # Check if there are waiting fibers and
@@ -106,8 +113,8 @@ module NeverBlock
106
113
  fiber = @queue.shift
107
114
  # What is really happening here?
108
115
  # we are resuming a fiber from within
109
- # another, should we call transfer insted?
110
- fiber.resume @busy_connections[fiber] = @connections.shift
116
+ # another, should we call transfer instead?
117
+ fiber.resume @connections.shift
111
118
  end
112
119
  end
113
120
 
@@ -4,10 +4,8 @@ require 'thin'
4
4
 
5
5
  module Thin
6
6
 
7
- # Patch the thin server to use
8
- # NeverBlock::Pool::FiberPool
9
- # to be able to wrap requests
10
- # in fibers
7
+ # Patch the thin server to use NeverBlock::Pool::FiberPool to be able to
8
+ # wrap requests in fibers
11
9
  class Server
12
10
 
13
11
  DEFAULT_FIBER_POOL_SIZE = 20
@@ -18,17 +16,14 @@ module Thin
18
16
 
19
17
  end # Server
20
18
 
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
19
+ # A request is processed by wrapping it in a fiber from the fiber pool.
20
+ # If all the fibers are busy the request will wait in a queue to be picked up
21
+ # later. Meanwhile, the server will still be processing requests
27
22
  class Connection < EventMachine::Connection
28
23
 
29
24
  def process
30
25
  @request.threaded = false
31
- @backend.server.fiber_pool.spawn{post_process(pre_process)}
26
+ @backend.server.fiber_pool.spawn {post_process(pre_process)}
32
27
  end
33
28
 
34
29
  end # Connection
@@ -1,4 +1,5 @@
1
1
  $:.unshift File.expand_path(File.dirname(__FILE__))
2
2
  require 'neverblock'
3
+ require 'never_block/db/fibered_db_connection'
3
4
  require 'never_block/db/fibered_mysql_connection'
4
- require 'never_block/db/pooled_fibered_mysql_connection'
5
+ require 'never_block/db/pooled_db_connection'
data/lib/neverblock-pg.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  $:.unshift File.expand_path(File.dirname(__FILE__))
2
-
2
+ require 'neverblock'
3
+ require 'never_block/db/fibered_db_connection'
4
+ require 'never_block/db/pooled_db_connection'
3
5
  require 'never_block/db/fibered_postgres_connection'
4
- require 'never_block/db/pooled_fibered_postgres_connection'
data/neverblock.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "neverblock"
3
- s.version = "0.1.4"
4
- s.date = "2008-09-04"
3
+ s.version = "0.1.5"
4
+ s.date = "2008-11-06"
5
5
  s.summary = "Utilities for non-blocking stack components"
6
6
  s.email = "oldmoe@gmail.com"
7
7
  s.homepage = "http://github.com/oldmoe/neverblock"
@@ -23,14 +23,15 @@ Gem::Specification.new do |s|
23
23
  "lib/never_block/servers/thin.rb",
24
24
  "lib/never_block/servers/mongrel.rb",
25
25
  "lib/never_block/db/fibered_postgres_connection.rb",
26
- "lib/never_block/db/pooled_fibered_postgres_connection.rb",
26
+ "lib/never_block/db/pooled_db_connection.rb",
27
27
  "lib/never_block/db/fibered_mysql_connection.rb",
28
- "lib/never_block/db/pooled_fibered_mysql_connection.rb",
28
+ "lib/never_block/db/fibered_db_connection.rb",
29
29
  "lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb",
30
30
  "lib/active_record/connection_adapters/neverblock_mysql_adapter.rb"
31
31
  ]
32
32
  s.rdoc_options = ["--main", "README"]
33
33
  s.extra_rdoc_files = ["README"]
34
+ s.add_dependency('eventmachine', '>= 0.12.2')
34
35
  end
35
36
 
36
37
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: espace-neverblock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muhammad A. Ali
@@ -11,10 +11,18 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2008-09-04 00:00:00 -07:00
14
+ date: 2008-11-06 00:00:00 -08:00
15
15
  default_executable:
16
- dependencies: []
17
-
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: eventmachine
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.12.2
25
+ version:
18
26
  description: NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
19
27
  email: oldmoe@gmail.com
20
28
  executables: []
@@ -38,9 +46,9 @@ files:
38
46
  - lib/never_block/servers/thin.rb
39
47
  - lib/never_block/servers/mongrel.rb
40
48
  - lib/never_block/db/fibered_postgres_connection.rb
41
- - lib/never_block/db/pooled_fibered_postgres_connection.rb
49
+ - lib/never_block/db/pooled_db_connection.rb
42
50
  - lib/never_block/db/fibered_mysql_connection.rb
43
- - lib/never_block/db/pooled_fibered_mysql_connection.rb
51
+ - lib/never_block/db/fibered_db_connection.rb
44
52
  - lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb
45
53
  - lib/active_record/connection_adapters/neverblock_mysql_adapter.rb
46
54
  has_rdoc: true
@@ -1,77 +0,0 @@
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 query method
18
- # quries the pool to get a connection first
19
- def query(query)
20
- @pool.hold do |conn|
21
- conn.query(query)
22
- end
23
- end
24
-
25
- # This method must be called for transactions to work correctly.
26
- # One cannot just send "begin" as you never know which connection
27
- # will be available next. This method ensures you get the same connection
28
- # while in a transaction.
29
- def begin_db_transaction
30
- @pool.hold(true) do |conn|
31
- conn.query("begin")
32
- end
33
- end
34
-
35
- # see =begin_db_transaction
36
- def rollback_db_transaction
37
- @pool.hold do |conn|
38
- conn.query("rollback")
39
- @pool.release(Fiber.current,conn)
40
- end
41
- end
42
-
43
- # see =begin_db_transaction
44
- def commit_db_transaction
45
- @pool.hold do |conn|
46
- conn.query("commit")
47
- @pool.release(Fiber.current,conn)
48
- end
49
- end
50
-
51
- #close all connections and remove them from the event loop
52
- def close
53
- @pool.all_connections do |conn|
54
- conn.unregister_from_event_loop
55
- conn.close
56
- end
57
- end
58
-
59
- # Pass unknown methods to the connection
60
- def method_missing(method, *args)
61
- @pool.hold do |conn|
62
- conn.send(method, *args)
63
- end
64
- end
65
-
66
- # Pass method queries to the connection
67
- def respond_to?(method)
68
- @pool.hold do |conn|
69
- conn.respond_to?(method)
70
- end
71
- end
72
-
73
- end
74
- end
75
- end
76
-
77
- NeverBlock::DB::PFMysql = NeverBlock::DB::PooledFiberedMysqlConnection
@@ -1,81 +0,0 @@
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
-