oldmoe-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::PooledDBConnection.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,8 +28,8 @@ class ActiveRecord::ConnectionAdapters::NeverBlockPostgreSQLAdapter < ActiveReco
28
28
  end
29
29
 
30
30
  def connect
31
- @connection = ::NB::DB::PooledFiberedPostgresConnection.new(@connection_parameters.shift) do
32
- conn = PGconn.connect(*@connection_parameters)
31
+ @connection = ::NB::DB::PooledFiberedPostgresConnection.new(@connection_options[0]) do
32
+ conn = PGconn.connect(*@connection_options[1..(@connection_options.length-1)])
33
33
  PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
34
34
  # Ignore async_exec and async_query when using postgres-pr.
35
35
  @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
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
@@ -1,46 +1,53 @@
1
1
  module NeverBlock
2
2
  module DB
3
3
  module FiberedDBConnection
4
- # Attaches the connection socket to an event loop.
5
- # Currently only supports EM, but Rev support will be
6
- # completed soon.
7
- def register_with_event_loop(loop)
8
- @fd = socket
9
- @io = IO.new(socket)
10
- if loop == :em
11
- if EM.reactor_running?
12
- @em_connection = EM::attach(@io,EMConnectionHandler,self)
13
- else
14
- raise "REACTOR NOT RUNNING YA ZALAMA"
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)
15
17
  end
16
- elsif loop.class.name == "REV::Loop"
17
- loop.attach(RevConnectionHandler.new(@fd))
18
18
  else
19
- raise "could not register with the event loop"
19
+ raise ::NB::NBError.new("FiberedDBConnection: EventMachine reactor not running")
20
20
  end
21
- @loop = loop
22
21
  end
23
22
 
24
23
  # Unattaches the connection socket from the event loop
25
24
  def unregister_from_event_loop
26
- if @loop == :em
27
- @em_connection.detach
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
28
30
  else
29
- raise NotImplementedError.new("unregister_from_event_loop not implemented for #{@loop}")
31
+ false
30
32
  end
31
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
32
46
 
33
47
  # The callback, this is called whenever
34
48
  # there is data available at the socket
35
49
  def resume_command
36
- #protection against being called several times
37
- if @fiber
38
- f = @fiber
39
- @fiber = nil
40
- f.resume
41
- else
42
- unregister_from_event_loop
43
- end
50
+ @fiber.resume if @fiber
44
51
  end
45
52
 
46
53
  end
@@ -3,55 +3,57 @@ require 'mysqlplus'
3
3
  module NeverBlock
4
4
 
5
5
  module DB
6
- # A modified mysql 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
11
 
16
12
  include FiberedDBConnection
17
-
13
+
14
+ # Initializes the connection and remembers the connection params
15
+ def initialize(*args)
16
+ @connection_params = args
17
+ super(*@connection_params)
18
+ end
19
+
20
+ # Does a normal real_connect if arguments are passed. If no arguments are
21
+ # passed it uses the ones it remembers
22
+ def real_connect(*args)
23
+ @connection_params = args unless args.empty?
24
+ super(*@connection_params)
25
+ end
26
+
27
+ alias_method :connect, :real_connect
28
+
18
29
  # Assuming the use of NeverBlock fiber extensions and that the exec is run in
19
30
  # the context of a fiber. One that have the value :neverblock set to true.
20
31
  # All neverblock IO classes check this value, setting it to false will force
21
32
  # the execution in a blocking way.
22
33
  def query(sql)
23
- begin
24
- 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
25
37
  send_query sql
26
- @fiber = Fiber.current
27
- Fiber.yield
28
- get_result
29
- else
30
- super(sql)
38
+ Fiber.yield register_with_event_loop
39
+ get_result
40
+ rescue Exception => e
41
+ if error = ['not connected', 'gone away', 'Lost connection'].detect{|msg| e.message.include? msg}
42
+ event_loop_connection_close
43
+ unregister_from_event_loop
44
+ remove_unregister_from_event_loop_callbacks
45
+ #connect
46
+ end
47
+ raise e
31
48
  end
32
- rescue Exception => e
33
- if error = ['not connected', 'gone away', 'Lost connection'].detect{|msg| e.message.include? msg}
34
- stop
35
- connect
36
- end
37
- raise e
38
- end
39
- end
40
-
41
- alias :exec :query
42
-
43
- # stop the connection and deattach from the event loop
44
- def stop
45
- unregister_from_event_loop
49
+ else
50
+ super(sql)
51
+ end
46
52
  end
47
53
 
48
- # reconnect and attach to the event loop
49
- def connect
50
- super
51
- register_with_event_loop(@loop)
52
- end
53
-
54
- end #FiberedMySQLConnection
54
+ alias_method :exec, :query
55
+
56
+ end #FiberedMySQLConnection
55
57
 
56
58
  end #DB
57
59
 
@@ -21,11 +21,13 @@ module NeverBlock
21
21
  # All neverblock IO classes check this value, setting it to false will force
22
22
  # the execution in a blocking way.
23
23
  def exec(sql)
24
- begin
25
- 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
26
28
  send_query sql
27
- @fiber = Fiber.current
28
- Fiber.yield
29
+ @fiber = Fiber.current
30
+ Fiber.yield register_with_event_loop
29
31
  while is_busy
30
32
  consume_input
31
33
  Fiber.yield if is_busy
@@ -35,26 +37,23 @@ module NeverBlock
35
37
  res = self.get_result
36
38
  data << res unless res.nil?
37
39
  end
38
- data.last
39
- else
40
- super(sql)
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
41
50
  end
42
- rescue Exception => e
43
- reset if e.message.include? "not connected"
44
- raise e
45
- end
51
+ else
52
+ super(sql)
53
+ end
46
54
  end
47
55
 
48
- alias :query :exec
49
-
50
- # reset the connection
51
- # and reattach to the
52
- # event loop
53
- def reset
54
- unregister_from_event_loop
55
- super
56
- register_with_event_loop(@loop)
57
- end
56
+ alias_method :query, :exec
58
57
 
59
58
  end #FiberedPostgresConnection
60
59
 
@@ -19,40 +19,12 @@ module NeverBlock
19
19
  end
20
20
 
21
21
  alias :exec :query
22
-
23
- # This method must be called for transactions to work correctly.
24
- # One cannot just send "begin" as you never know which connection
25
- # will be available next. This method ensures you get the same connection
26
- # while in a transaction.
27
- def begin_db_transaction
28
- @pool.hold(true) do |conn|
29
- conn.query("begin")
30
- end
31
- end
32
-
33
- # see =begin_db_transaction
34
- def rollback_db_transaction
35
- @pool.hold do |conn|
36
- conn.query("rollback")
37
- @pool.release(Fiber.current,conn)
38
- end
39
- end
40
-
41
- # see =begin_db_transaction
42
- def commit_db_transaction
43
- @pool.hold do |conn|
44
- conn.query("commit")
45
- @pool.release(Fiber.current,conn)
46
- end
47
- end
48
22
 
49
- #close all connections and remove them from the event loop
50
- def close
51
- @pool.all_connections do |conn|
52
- conn.close
53
- end
23
+ # Replaces the current connection with a brand new one
24
+ def replace_acquired_connection
25
+ @pool.replace_acquired_connection
54
26
  end
55
-
27
+
56
28
  # Pass unknown methods to the connection
57
29
  def method_missing(method, *args)
58
30
  @pool.hold do |conn|
@@ -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
 
@@ -40,18 +31,23 @@ module NeverBlock
40
31
  # every time. Once a fiber is done with its block, it attempts to fetch
41
32
  # another one from the queue
42
33
  def initialize(count = 50)
43
- @fibers,@queue = [],[]
34
+ @fibers,@busy_fibers,@queue = [],{},[]
44
35
  count.times do |i|
45
36
  fiber = Fiber.new do |block|
46
37
  loop do
47
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
48
41
  unless @queue.empty?
49
42
  block = @queue.shift
50
43
  else
51
- block = Fiber.yield @fibers << Fiber.current
44
+ @busy_fibers.delete(Fiber.current.object_id)
45
+ @fibers << Fiber.current
46
+ block = Fiber.yield
52
47
  end
53
48
  end
54
49
  end
50
+ fiber[:callbacks] = []
55
51
  fiber[:neverblock] = true
56
52
  @fibers << fiber
57
53
  end
@@ -61,6 +57,8 @@ module NeverBlock
61
57
  # in a queue
62
58
  def spawn(evented = true, &block)
63
59
  if fiber = @fibers.shift
60
+ fiber[:callbacks] = []
61
+ @busy_fibers[fiber.object_id] = fiber
64
62
  fiber[:neverblock] = evented
65
63
  fiber.resume(block)
66
64
  else
@@ -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
@@ -58,31 +49,19 @@ module NeverBlock
58
49
  end
59
50
  end
60
51
 
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
58
+
61
59
  # If a connection is available, pass it to the block, otherwise pass
62
60
  # the fiber to the queue till a connection is available
63
- # when done with a connection try to porcess other fibers in the queue
64
- # before releasing the connection
65
- # if inside a transaction, don't release the fiber
66
- def hold(transactional = false)
61
+ def hold()
67
62
  fiber = Fiber.current
68
- if conn = @busy_connections[fiber]
69
- return yield(conn)
70
- end
71
- conn = acquire(fiber)
72
- begin
73
- yield conn
74
- ensure
75
- release(fiber, conn) unless transactional
76
- process_queue
77
- end
78
- end
79
-
80
- # Give the fiber back to the pool
81
- # you have to call this explicitly if
82
- # you held a connection for a transaction
83
- def release(fiber, conn)
84
- @busy_connections.delete(fiber)
85
- @connections << conn
63
+ conn = acquire(fiber)
64
+ yield conn
86
65
  end
87
66
 
88
67
  def all_connections
@@ -95,14 +74,36 @@ module NeverBlock
95
74
  # Can we create one?
96
75
  # Wait in the queue then
97
76
  def acquire(fiber)
98
- if !@connections.empty?
99
- @busy_connections[fiber] = @connections.shift
100
- elsif (@connections.length + @busy_connections.length) < @size
101
- conn = @connection_proc.call
102
- @busy_connections[fiber] = conn
103
- 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
104
88
  Fiber.yield @queue << fiber
105
- 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
106
107
  end
107
108
 
108
109
  # Check if there are waiting fibers and
@@ -113,7 +114,7 @@ module NeverBlock
113
114
  # What is really happening here?
114
115
  # we are resuming a fiber from within
115
116
  # another, should we call transfer instead?
116
- fiber.resume @busy_connections[fiber] = @connections.shift
117
+ fiber.resume @connections.shift
117
118
  end
118
119
  end
119
120
 
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-20"
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"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oldmoe-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,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2008-09-20 00:00:00 -07:00
14
+ date: 2008-11-06 00:00:00 -08:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency