neverblock 0.1.6.2

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.
@@ -0,0 +1,64 @@
1
+ require 'pg'
2
+
3
+ module NeverBlock
4
+
5
+ module DB
6
+
7
+ # A modified postgres connection driver
8
+ # builds on the original pg driver.
9
+ # This driver is able to register the socket
10
+ # at a certain backend (EM or Rev)
11
+ # and then whenever the query is executed
12
+ # within the scope of a friendly fiber
13
+ # it will be done in async mode and the fiber
14
+ # will yield
15
+ class FiberedPostgresConnection < PGconn
16
+
17
+ include FiberedDBConnection
18
+
19
+ # Assuming the use of NeverBlock fiber extensions and that the exec is run in
20
+ # the context of a fiber. One that have the value :neverblock set to true.
21
+ # All neverblock IO classes check this value, setting it to false will force
22
+ # the execution in a blocking way.
23
+ def exec(sql)
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
28
+ send_query sql
29
+ @fiber = Fiber.current
30
+ Fiber.yield register_with_event_loop
31
+ while is_busy
32
+ consume_input
33
+ Fiber.yield if is_busy
34
+ end
35
+ res, data = 0, []
36
+ while res != nil
37
+ res = self.get_result
38
+ data << res unless res.nil?
39
+ end
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
50
+ end
51
+ else
52
+ super(sql)
53
+ end
54
+ end
55
+
56
+ alias_method :query, :exec
57
+
58
+ end #FiberedPostgresConnection
59
+
60
+ end #DB
61
+
62
+ end #NeverBlock
63
+
64
+ NeverBlock::DB::FPGconn = NeverBlock::DB::FiberedPostgresConnection
@@ -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
@@ -0,0 +1,31 @@
1
+ # Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
2
+ # Copyright:: Copyright (c) 2008 eSpace, Inc.
3
+ # License:: Distributes under the same terms as Ruby
4
+
5
+ # If this file is meant to be used out of neverblock, then uncomment
6
+ # the following line
7
+ #require 'fiber'
8
+
9
+ class Fiber
10
+
11
+ #Attribute Reference--Returns the value of a fiber-local variable, using
12
+ #either a symbol or a string name. If the specified variable does not exist,
13
+ #returns nil.
14
+ def [](key)
15
+ local_fiber_variables[key]
16
+ end
17
+
18
+ #Attribute Assignment--Sets or creates the value of a fiber-local variable,
19
+ #using either a symbol or a string. See also Fiber#[].
20
+ def []=(key,value)
21
+ local_fiber_variables[key] = value
22
+ end
23
+
24
+ private
25
+
26
+ def local_fiber_variables
27
+ @local_fiber_variables ||= {}
28
+ end
29
+
30
+ end
31
+
@@ -0,0 +1,37 @@
1
+ require 'activerecord'
2
+
3
+ # Patch ActiveRecord to store transaction depth information
4
+ # in fibers instead of threads. AR does not support nested
5
+ # transactions which makes the job easy.
6
+ # We also need to override the scoped methods to store
7
+ # the scope in the fiber context
8
+ class ActiveRecord::Base
9
+
10
+ def single_threaded_scoped_methods #:nodoc:
11
+ scoped_methods = (Fiber.current[:scoped_methods] ||= {})
12
+ scoped_methods[self] ||= []
13
+ end
14
+
15
+ def self.transaction(&block)
16
+ increment_open_transactions
17
+ begin
18
+ connection.transaction &block
19
+ ensure
20
+ decrement_open_transactions
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def self.increment_open_transactions #:nodoc:
27
+ open = Fiber.current['open_transactions'] ||= 0
28
+ Fiber.current['start_db_transaction'] = open.zero?
29
+ Fiber.current['open_transactions'] = open + 1
30
+ end
31
+
32
+ def self.decrement_open_transactions #:nodoc:
33
+ Fiber.current['open_transactions'] -= 1
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,65 @@
1
+ require 'neverblock' unless defined?(NeverBlock)
2
+
3
+ # Rails tries to protect dispatched actions
4
+ # by wrapping them in a synchronized code
5
+ # block, since fibers hate synchronized
6
+ # blocks we will trick the guard and
7
+ # transform it (without it knowing) to
8
+ # something more subtle
9
+
10
+ require 'thread'
11
+ # now you synchronize
12
+ class Mutex
13
+ def synchronize(&block)
14
+ # now you don't!
15
+ block.call
16
+ end
17
+ end
18
+
19
+ require 'action_controller'
20
+ class ActionController::Base
21
+
22
+ # Mark some actions to execute in a blocking manner overriding the default
23
+ # settings.
24
+ # Example:
25
+ # class UsersController < ApplicationController
26
+ # .
27
+ # allowblock :index
28
+ # .
29
+ # end
30
+ def self.allowblock(*actions)
31
+ actions.each do |action|
32
+ class_eval <<-"end_eval"
33
+ def allowblock_#{action}
34
+ status = Fiber.current[:neverblock]
35
+ Fiber.current[:neverblock] = false
36
+ yield
37
+ Fiber.current[:neverblock] = status
38
+ end
39
+ around_filter :allowblock_#{action}, :only => [:#{action}]
40
+ end_eval
41
+ end
42
+ end
43
+
44
+ # Mark some actions to execute in a non-blocking manner overriding the default
45
+ # settings.
46
+ # Example:
47
+ # class UsersController < ApplicationController
48
+ # .
49
+ # allowblock :index
50
+ # .
51
+ # end
52
+ def self.neverblock(*actions)
53
+ actions.each do |action|
54
+ class_eval <<-"end_eval"
55
+ def neverblock_#{action}
56
+ status = Fiber.current[:neverblock]
57
+ Fiber.current[:neverblock] = true
58
+ yield
59
+ Fiber.current[:neverblock] = status
60
+ end
61
+ around_filter :allowblock_#{action}, :only => [:#{action}]
62
+ end_eval
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,74 @@
1
+ module NeverBlock
2
+ module Pool
3
+
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
+ # A pool of initialized fibers
9
+ # It does not grow in size or create transient fibers
10
+ # It will queue code blocks when needed (if all its fibers are busy)
11
+ #
12
+ # This class is particulary useful when you use the fibers
13
+ # to connect to evented back ends. It also does not generate
14
+ # transient objects and thus saves memory.
15
+ #
16
+ # Example:
17
+ # fiber_pool = NeverBlock::Pool::FiberPool.new(150)
18
+ #
19
+ # loop do
20
+ # fiber_pool.spawn do
21
+ # #fiber body goes here
22
+ # end
23
+ # end
24
+ #
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[:em_keys] = []
52
+ fiber[:neverblock] = true
53
+ @fibers << fiber
54
+ end
55
+ end
56
+
57
+ # If there is an available fiber use it, otherwise, leave it to linger
58
+ # in a queue
59
+ def spawn(evented = true, &block)
60
+ if fiber = @fibers.shift
61
+ fiber[:callbacks] = []
62
+ @busy_fibers[fiber.object_id] = fiber
63
+ fiber[:neverblock] = evented
64
+ fiber.resume(block)
65
+ else
66
+ @queue << block
67
+ end
68
+ self # we are keen on hiding our queue
69
+ end
70
+
71
+ end # FiberPool
72
+ end # Pool
73
+ end # NeverBlock
74
+
@@ -0,0 +1,130 @@
1
+ module NeverBlock
2
+ module Pool
3
+ # This class represents a pool of connections,
4
+ # you hold or release conncetions from the pool
5
+ # hold requests that cannot be fullfiled will be queued
6
+ # the fiber will be paused and resumed later when
7
+ # a connection is avaialble
8
+ #
9
+ # Large portions of this class were copied and pasted
10
+ # form Sequel's threaded connection pool
11
+ #
12
+ # Example:
13
+ #
14
+ # pool = NeverBlock::Pool::FiberedConnectionPool.new(:size=>16)do
15
+ # # connection creation code goes here
16
+ # end
17
+ # 32.times do
18
+ # Fiber.new do
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
23
+ # end.resume
24
+ # end
25
+ #
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
29
+ # It is the responsibility of client code to release the connection
30
+ class FiberedConnectionPool
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
+ def initialize(options = {}, &block)
42
+ @connections, @busy_connections, @queue = [], {},[]
43
+ @connection_proc = block
44
+ @size = options[:size] || 8
45
+ if options[:eager]
46
+ @size.times do
47
+ @connections << @connection_proc.call
48
+ end
49
+ end
50
+ end
51
+
52
+ def replace_acquired_connection
53
+ fiber = Fiber.current
54
+ conn = @connection_proc.call
55
+ @busy_connections[fiber] = conn
56
+ fiber[connection_pool_key] = conn
57
+ end
58
+
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
65
+ end
66
+
67
+ def all_connections
68
+ (@connections + @busy_connections.values).each {|conn| yield(conn)}
69
+ end
70
+
71
+ private
72
+
73
+ # Can we find a connection?
74
+ # Can we create one?
75
+ # Wait in the queue then
76
+ def acquire(fiber)
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
+ fiber[:current_pool_key] = connection_pool_key
83
+ return fiber[connection_pool_key] if fiber[connection_pool_key]
84
+ conn = if !@connections.empty?
85
+ @connections.shift
86
+ elsif (@connections.length + @busy_connections.length) < @size
87
+ @connection_proc.call
88
+ else
89
+ Fiber.yield @queue << fiber
90
+ end
91
+
92
+ # They're called in reverse order i.e. release then process_queue
93
+ fiber[:callbacks] << self.method(:process_queue)
94
+ fiber[:callbacks] << self.method(:release)
95
+
96
+ @busy_connections[fiber] = conn
97
+ fiber[connection_pool_key] = conn
98
+ end
99
+
100
+ # Give the fiber's connection back to the pool
101
+ def release()
102
+ fiber = Fiber.current
103
+ if fiber[connection_pool_key]
104
+ @busy_connections.delete(fiber)
105
+ @connections << fiber[connection_pool_key]
106
+ fiber[connection_pool_key] = nil
107
+ end
108
+ end
109
+
110
+ # Check if there are waiting fibers and
111
+ # try to process them
112
+ def process_queue
113
+ while !@connections.empty? and !@queue.empty?
114
+ fiber = @queue.shift
115
+ # What is really happening here?
116
+ # we are resuming a fiber from within
117
+ # another, should we call transfer instead?
118
+ fiber.resume @connections.shift
119
+ end
120
+ end
121
+
122
+ def connection_pool_key
123
+ @connection_pool_key ||= "connection_pool_#{object_id}".intern
124
+ end
125
+
126
+ end #FiberedConnectionPool
127
+
128
+ end #Pool
129
+
130
+ end #NeverBlock