neverblock 0.1.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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