oldmoe-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::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