oldmoe-neverblock 0.1.6 → 1.0

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.
Files changed (31) hide show
  1. data/lib/never_block.rb +5 -78
  2. data/lib/neverblock.rb +3 -1
  3. data/lib/{never_block/extensions/fiber_extensions.rb → neverblock/core/fiber.rb} +20 -6
  4. data/lib/neverblock/core/pool.rb +72 -0
  5. data/lib/neverblock/core/reactor.rb +50 -0
  6. data/lib/neverblock/core/system/system.rb +38 -0
  7. data/lib/neverblock/core/system/timeout.rb +67 -0
  8. data/lib/{never_block/db/pooled_db_connection.rb → neverblock/io/db/connection.rb} +16 -2
  9. data/lib/neverblock/io/db/drivers/mysql.rb +73 -0
  10. data/lib/neverblock/io/db/drivers/postgres.rb +63 -0
  11. data/lib/neverblock/io/db/fibered_connection_pool.rb +130 -0
  12. data/lib/{never_block → neverblock/io}/db/fibered_mysql_connection.rb +5 -18
  13. data/lib/{never_block/pool/fibered_connection_pool.rb → neverblock/io/db/pool.rb} +25 -40
  14. data/lib/neverblock/io/file.rb +24 -0
  15. data/lib/neverblock/io/io.rb +219 -0
  16. data/lib/neverblock/io/socket.rb +75 -0
  17. data/lib/neverblock_io.rb +6 -0
  18. data/lib/system.rb +4 -0
  19. data/neverblock.gemspec +23 -21
  20. metadata +23 -20
  21. data/lib/active_record/connection_adapters/neverblock_mysql_adapter.rb +0 -68
  22. data/lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb +0 -85
  23. data/lib/never_block/db/fibered_db_connection.rb +0 -72
  24. data/lib/never_block/db/fibered_postgres_connection.rb +0 -64
  25. data/lib/never_block/frameworks/activerecord.rb +0 -37
  26. data/lib/never_block/frameworks/rails.rb +0 -65
  27. data/lib/never_block/pool/fiber_pool.rb +0 -74
  28. data/lib/never_block/servers/mongrel.rb +0 -236
  29. data/lib/never_block/servers/thin.rb +0 -32
  30. data/lib/neverblock-mysql.rb +0 -5
  31. data/lib/neverblock-pg.rb +0 -5
@@ -0,0 +1,63 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/../lib/neverblock/io/db/drivers/postgres')
2
+ require File.expand_path(File.dirname(__FILE__)+'/../lib/neverblock/io/db/connection')
3
+
4
+ module NeverBlock
5
+
6
+ module DB
7
+
8
+ # A modified postgres connection driver
9
+ # builds on the original pg driver.
10
+ # This driver is able to register the socket
11
+ # at a certain backend (Reacotr)
12
+ # and then whenever the query is executed
13
+ # within the scope of a friendly fiber (NB::Fiber)
14
+ # it will be done in async mode and the fiber
15
+ # will yield
16
+ class Postgres < PGconn
17
+
18
+ # Assuming the use of NeverBlock fiber extensions and that the exec is run in
19
+ # the context of a fiber. One that have the value :neverblock set to true.
20
+ # All neverblock IO classes check this value, setting it to false will force
21
+ # the execution in a blocking way.
22
+ def exec(sql)
23
+ # TODO Still not "killing the query process"-proof
24
+ # In some cases, the query is simply sent but the fiber never yields
25
+ if NB.neverblocking? && NB.reactor.running?
26
+ send_query sql
27
+ while is_busy
28
+ NB.wait(:read, IO.new(socket))
29
+ consume_input
30
+ end
31
+ res, data = 0, []
32
+ while res != nil
33
+ res = self.get_result
34
+ data << res unless res.nil?
35
+ end
36
+ data.last
37
+ else
38
+ super(sql)
39
+ end
40
+ end
41
+
42
+ alias_method :query, :exec
43
+
44
+ end #Postgres
45
+
46
+ class PooledPostgres < ::NeverBlock::DB::Connection
47
+
48
+ def initialize(*args)
49
+ options = {}
50
+ if args && (options = args.last).is_a? Hash
51
+ size = options[:size] || 4
52
+ eager = options[:eager] || true
53
+ args.pop
54
+ end
55
+ @pool = NB::DB::Pool.new(:size=>size, :eager=>eager) do
56
+ Postgres.new(*args)
57
+ end
58
+ end
59
+
60
+ end #PooledMySQL
61
+ end #DB
62
+
63
+ end #NeverBlock
@@ -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
@@ -7,10 +7,8 @@ module NeverBlock
7
7
  # This driver is able to register the socket at a certain backend (EM)
8
8
  # and then whenever the query is executed within the scope of a friendly
9
9
  # fiber. It will be done in async mode and the fiber will yield
10
- class FiberedMysqlConnection < Mysql
10
+ class FiberedMysqlConnection < Mysql
11
11
 
12
- include FiberedDBConnection
13
-
14
12
  # Initializes the connection and remembers the connection params
15
13
  def initialize(*args)
16
14
  @connection_params = args
@@ -31,21 +29,10 @@ module NeverBlock
31
29
  # All neverblock IO classes check this value, setting it to false will force
32
30
  # the execution in a blocking way.
33
31
  def query(sql)
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[Fiber.current[:current_pool_key]]) && c != self
36
- begin
37
- send_query 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
48
- end
32
+ if NB.neverblocking? && NB.reactor.running?
33
+ send_query sql
34
+ NB.wait(:read, IO.new(socket))
35
+ get_result
49
36
  else
50
37
  super(sql)
51
38
  end
@@ -1,5 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/../../../neverblock')
2
+
1
3
  module NeverBlock
2
- module Pool
4
+ module DB
3
5
  # This class represents a pool of connections,
4
6
  # you hold or release conncetions from the pool
5
7
  # hold requests that cannot be fullfiled will be queued
@@ -11,7 +13,7 @@ module NeverBlock
11
13
  #
12
14
  # Example:
13
15
  #
14
- # pool = NeverBlock::Pool::FiberedConnectionPool.new(:size=>16)do
16
+ # pool = NeverBlock::Pool.new(:size=>16)do
15
17
  # # connection creation code goes here
16
18
  # end
17
19
  # 32.times do
@@ -23,11 +25,9 @@ module NeverBlock
23
25
  # end.resume
24
26
  # end
25
27
  #
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
28
  # It is the responsibility of client code to release the connection
30
- class FiberedConnectionPool
29
+ # using pool.release(conn)
30
+ class Pool
31
31
 
32
32
  attr_reader :size
33
33
 
@@ -50,7 +50,7 @@ module NeverBlock
50
50
  end
51
51
 
52
52
  def replace_acquired_connection
53
- fiber = Fiber.current
53
+ fiber = NB::Fiber.current
54
54
  conn = @connection_proc.call
55
55
  @busy_connections[fiber] = conn
56
56
  fiber[connection_pool_key] = conn
@@ -58,10 +58,22 @@ module NeverBlock
58
58
 
59
59
  # If a connection is available, pass it to the block, otherwise pass
60
60
  # the fiber to the queue till a connection is available
61
- def hold()
62
- fiber = Fiber.current
61
+ def hold
62
+ fiber = NB::Fiber.current
63
63
  conn = acquire(fiber)
64
- yield conn
64
+ if block_given?
65
+ yield conn
66
+ release(conn)
67
+ else
68
+ conn
69
+ end
70
+ end
71
+
72
+ # Give the fiber's connection back to the pool
73
+ def release(conn)
74
+ @busy_connections.delete(conn.object_id)
75
+ @connections << conn unless @connections.include? conn
76
+ process_queue
65
77
  end
66
78
 
67
79
  def all_connections
@@ -74,37 +86,14 @@ module NeverBlock
74
86
  # Can we create one?
75
87
  # Wait in the queue then
76
88
  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
89
  conn = if !@connections.empty?
85
90
  @connections.shift
86
91
  elsif (@connections.length + @busy_connections.length) < @size
87
92
  @connection_proc.call
88
93
  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
94
+ NB::Fiber.yield @queue << fiber
107
95
  end
96
+ @busy_connections[conn.object_id] = conn
108
97
  end
109
98
 
110
99
  # Check if there are waiting fibers and
@@ -118,11 +107,7 @@ module NeverBlock
118
107
  fiber.resume @connections.shift
119
108
  end
120
109
  end
121
-
122
- def connection_pool_key
123
- @connection_pool_key ||= "connection_pool_#{object_id}".intern
124
- end
125
-
110
+
126
111
  end #FiberedConnectionPool
127
112
 
128
113
  end #Pool
@@ -0,0 +1,24 @@
1
+ # Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
2
+ # Copyright:: Copyright (c) 2009 eSpace, Inc.
3
+ # License:: Distributes under the same terms as Ruby
4
+
5
+ require 'thread'
6
+ require File.expand_path(File.dirname(__FILE__)+'/io')
7
+
8
+ class File < IO
9
+
10
+ def self.neverblock(*methods)
11
+ methods.each do |method|
12
+ class_eval %{
13
+ def #{method}(*args)
14
+ return rb_#{method}(*args) unless NB.neverblocking?
15
+ NB.defer(self, :#{method}, args)
16
+ end
17
+ }
18
+ end
19
+ end
20
+
21
+ neverblock :syswrite, :sysread, :write, :read, :readline,
22
+ :readlines, :readchar, :gets, :getc, :print
23
+
24
+ end
@@ -0,0 +1,219 @@
1
+ require 'fcntl'
2
+
3
+ # This is an extention to the Ruby IO class that makes it compatable with
4
+ # NeverBlocks event loop to avoid blocking IO calls. That's done by delegating
5
+ # Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
6
+ # Copyright:: Copyright (c) 2009 eSpace, Inc.
7
+ # License:: Distributes under the same terms as Ruby
8
+
9
+ $:.unshift
10
+
11
+ require File.expand_path(File.dirname(__FILE__)+'/../../neverblock')
12
+
13
+ class IO
14
+
15
+ NB_BUFFER_LENGTH = 128*1024
16
+
17
+ alias_method :rb_sysread, :sysread
18
+ alias_method :rb_syswrite, :syswrite
19
+ alias_method :rb_read, :read
20
+ alias_method :rb_write, :write
21
+ alias_method :rb_gets, :gets
22
+ alias_method :rb_getc, :getc
23
+ alias_method :rb_readchar, :readchar
24
+ alias_method :rb_readline, :readline
25
+ alias_method :rb_readlines, :readlines
26
+ alias_method :rb_print, :print
27
+
28
+ # This method is the delegation method which reads using read_nonblock()
29
+ # and registers the IO call with event loop if the call blocks. The value
30
+ # @immediate_result is used to get the value that method got before it was blocked.
31
+
32
+ def read_neverblock(*args)
33
+ res = ""
34
+ begin
35
+ old_flags = get_flags
36
+ res << read_nonblock(*args)
37
+ set_flags(old_flags)
38
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
39
+ set_flags(old_flags)
40
+ NB.wait(:read, self)
41
+ retry
42
+ end
43
+ res
44
+ end
45
+
46
+ # The is the main reading method that all other methods use.
47
+ # If the mode is set to neverblock it uses the delegation method.
48
+ # Otherwise it uses the original ruby read method.
49
+
50
+ def sysread(length)
51
+ self.neverblock? ? read_neverblock(length) : rb_sysread(length)
52
+ end
53
+
54
+ def read(length=nil, sbuffer=nil)
55
+ return rb_read(length, sbuffer) if self.file?
56
+ return '' if length == 0
57
+ if sbuffer.nil?
58
+ sbuffer = ''
59
+ else
60
+ sbuffer = sbuffer.to_str
61
+ sbuffer.delete!(sbuffer)
62
+ end
63
+ if length.nil?
64
+ # we need to read till end of stream
65
+ loop do
66
+ begin
67
+ sbuffer << sysread(NB_BUFFER_LENGTH)
68
+ rescue EOFError
69
+ break
70
+ end
71
+ end
72
+ return sbuffer
73
+ else # length != nil
74
+ if self.buffer.length >= length
75
+ sbuffer << self.buffer.slice!(0, length)
76
+ return sbuffer
77
+ elsif self.buffer.length > 0
78
+ sbuffer << self.buffer
79
+ end
80
+ self.buffer = ''
81
+ remaining_length = length - sbuffer.length
82
+ while sbuffer.length < length && remaining_length > 0
83
+ begin
84
+ sbuffer << sysread(NB_BUFFER_LENGTH < remaining_length ? remaining_length : NB_BUFFER_LENGTH)
85
+ remaining_length = remaining_length - sbuffer.length
86
+ rescue EOFError
87
+ break
88
+ end #begin
89
+ end #while
90
+ end #if length
91
+ return nil if sbuffer.length.zero? && length > 0
92
+ return sbuffer if sbuffer.length <= length
93
+ self.buffer << sbuffer.slice!(length, sbuffer.length-1)
94
+ return sbuffer
95
+ end
96
+
97
+ def readpartial(length=nil,sbuffer=nil)
98
+ raise ArgumentError if !length.nil? && length < 0
99
+ if sbuffer.nil?
100
+ sbuffer = ''
101
+ else
102
+ sbuffer = sbuffer.to_str
103
+ sbuffer.delete!(sbuffer)
104
+ end
105
+
106
+ if self.buffer.length >= length
107
+ sbuffer << self.buffer.slice!(0, length)
108
+ elsif self.buffer.length > 0
109
+ sbuffer << self.buffer.slice!(0, self.buffer.length-1)
110
+ else
111
+ sbuffer << rb_sysread(length)
112
+ end
113
+ return sbuffer
114
+ end
115
+
116
+ def write_neverblock(data)
117
+ written = 0
118
+ begin
119
+ old_flags = get_flags
120
+ written = written + write_nonblock(data[written,data.length])
121
+ set_flags(old_flags)
122
+ raise Errno::EAGAIN if written < data.length
123
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
124
+ set_flags(old_flags)
125
+ NB.wait(:write, self)
126
+ retry
127
+ end
128
+ written
129
+ end
130
+
131
+ def syswrite(*args)
132
+ return rb_syswrite(*args) unless self.neverblock?
133
+ write_neverblock(*args)
134
+ end
135
+
136
+ def write(data)
137
+ return 0 if data.to_s.empty?
138
+ return rb_write(data) if self.file?
139
+ syswrite(data)
140
+ end
141
+
142
+ def gets(sep=$/)
143
+ return rb_gets(sep) if self.file?
144
+ res = ""
145
+ sep = "\n\n" if sep == ""
146
+ sep = $/ if sep.nil?
147
+ while res.index(sep).nil?
148
+ break if (c = read(1)).nil?
149
+ res << c
150
+ end
151
+ $_ = res
152
+ res
153
+ end
154
+
155
+ def readlines(sep=$/)
156
+ return rb_readlines(sep) if self.file?
157
+ res = []
158
+ begin
159
+ loop{res << readline(sep)}
160
+ rescue EOFError
161
+ end
162
+ res
163
+ end
164
+
165
+ def readchar
166
+ return rb_readchar if self.file?
167
+ ch = read(1)
168
+ raise EOFError if ch.nil?
169
+ ch
170
+ end
171
+
172
+ def getc
173
+ return rb_getc if self.file?
174
+ begin
175
+ res = readchar
176
+ rescue EOFError
177
+ res = nil
178
+ end
179
+ end
180
+
181
+ def readline(sep = $/)
182
+ return rb_readline(sep) if self.file?
183
+ res = gets(sep)
184
+ raise EOFError if res == nil
185
+ res
186
+ end
187
+
188
+ def print(*args)
189
+ return rb_print if self.file?
190
+ args.each{|element|syswrite(element)}
191
+ end
192
+
193
+ protected
194
+
195
+ def get_flags
196
+ self.fcntl(Fcntl::F_GETFL, 0)
197
+ end
198
+
199
+ def set_flags(flags)
200
+ self.fcntl(Fcntl::F_SETFL, flags)
201
+ end
202
+
203
+ def buffer
204
+ @buffer ||= ""
205
+ end
206
+
207
+ def buffer=(value)
208
+ @buffer = value
209
+ end
210
+
211
+ def file?
212
+ @file ||= self.stat.file?
213
+ end
214
+
215
+ def neverblock?
216
+ !file? && NB.neverblocking?
217
+ end
218
+
219
+ end