espace-neverblock 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
-