oldmoe-neverblock 0.1.6 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/never_block.rb +5 -78
- data/lib/neverblock.rb +3 -1
- data/lib/{never_block/extensions/fiber_extensions.rb → neverblock/core/fiber.rb} +20 -6
- data/lib/neverblock/core/pool.rb +72 -0
- data/lib/neverblock/core/reactor.rb +50 -0
- data/lib/neverblock/core/system/system.rb +38 -0
- data/lib/neverblock/core/system/timeout.rb +67 -0
- data/lib/{never_block/db/pooled_db_connection.rb → neverblock/io/db/connection.rb} +16 -2
- data/lib/neverblock/io/db/drivers/mysql.rb +73 -0
- data/lib/neverblock/io/db/drivers/postgres.rb +63 -0
- data/lib/neverblock/io/db/fibered_connection_pool.rb +130 -0
- data/lib/{never_block → neverblock/io}/db/fibered_mysql_connection.rb +5 -18
- data/lib/{never_block/pool/fibered_connection_pool.rb → neverblock/io/db/pool.rb} +25 -40
- data/lib/neverblock/io/file.rb +24 -0
- data/lib/neverblock/io/io.rb +219 -0
- data/lib/neverblock/io/socket.rb +75 -0
- data/lib/neverblock_io.rb +6 -0
- data/lib/system.rb +4 -0
- data/neverblock.gemspec +23 -21
- metadata +23 -20
- data/lib/active_record/connection_adapters/neverblock_mysql_adapter.rb +0 -68
- data/lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb +0 -85
- data/lib/never_block/db/fibered_db_connection.rb +0 -72
- data/lib/never_block/db/fibered_postgres_connection.rb +0 -64
- data/lib/never_block/frameworks/activerecord.rb +0 -37
- data/lib/never_block/frameworks/rails.rb +0 -65
- data/lib/never_block/pool/fiber_pool.rb +0 -74
- data/lib/never_block/servers/mongrel.rb +0 -236
- data/lib/never_block/servers/thin.rb +0 -32
- data/lib/neverblock-mysql.rb +0 -5
- data/lib/neverblock-pg.rb +0 -5
@@ -1,64 +0,0 @@
|
|
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
|
@@ -1,37 +0,0 @@
|
|
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(Fiber.current['start_db_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
|
-
|
@@ -1,65 +0,0 @@
|
|
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
|
@@ -1,74 +0,0 @@
|
|
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
|
-
|
@@ -1,236 +0,0 @@
|
|
1
|
-
# This module rewrites pieces of the very good Mongrel web server in
|
2
|
-
# order to change it from a threaded application to an event based
|
3
|
-
# application running inside an EventMachine event loop. It should
|
4
|
-
# be compatible with the existing Mongrel handlers for Rails,
|
5
|
-
# Camping, Nitro, etc....
|
6
|
-
|
7
|
-
# NeverBlock support added
|
8
|
-
|
9
|
-
begin
|
10
|
-
load_attempted ||= false
|
11
|
-
require 'eventmachine'
|
12
|
-
rescue LoadError
|
13
|
-
unless load_attempted
|
14
|
-
load_attempted = true
|
15
|
-
require 'rubygems'
|
16
|
-
retry
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
require 'rubygems'
|
21
|
-
require 'neverblock'
|
22
|
-
require 'mongrel'
|
23
|
-
|
24
|
-
module Mongrel
|
25
|
-
class MongrelProtocol < EventMachine::Connection
|
26
|
-
def post_init
|
27
|
-
@parser = HttpParser.new
|
28
|
-
@params = HttpParams.new
|
29
|
-
@nparsed = 0
|
30
|
-
@request = nil
|
31
|
-
@request_len = nil
|
32
|
-
@linebuffer = ''
|
33
|
-
end
|
34
|
-
|
35
|
-
def receive_data data
|
36
|
-
@linebuffer << data
|
37
|
-
@nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished?
|
38
|
-
if @parser.finished?
|
39
|
-
if @request_len.nil?
|
40
|
-
@request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
|
41
|
-
script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
|
42
|
-
if handlers
|
43
|
-
@params[::Mongrel::Const::PATH_INFO] = path_info
|
44
|
-
@params[::Mongrel::Const::SCRIPT_NAME] = script_name
|
45
|
-
@params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] || ::Socket.unpack_sockaddr_in(get_peername)[1]
|
46
|
-
@notifiers = handlers.select { |h| h.request_notify }
|
47
|
-
end
|
48
|
-
if @request_len > ::Mongrel::Const::MAX_BODY
|
49
|
-
new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE)
|
50
|
-
new_buffer.binmode
|
51
|
-
new_buffer << @linebuffer[@nparsed..-1]
|
52
|
-
@linebuffer = new_buffer
|
53
|
-
else
|
54
|
-
@linebuffer = StringIO.new(@linebuffer[@nparsed..-1])
|
55
|
-
@linebuffer.pos = @linebuffer.length
|
56
|
-
end
|
57
|
-
end
|
58
|
-
if @linebuffer.length >= @request_len
|
59
|
-
@linebuffer.rewind
|
60
|
-
::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self)
|
61
|
-
@linebuffer.delete if Tempfile === @linebuffer
|
62
|
-
end
|
63
|
-
elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER
|
64
|
-
close_connection
|
65
|
-
raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.")
|
66
|
-
end
|
67
|
-
rescue ::Mongrel::HttpParserError
|
68
|
-
if $mongrel_debug_client
|
69
|
-
STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
|
70
|
-
STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
|
71
|
-
end
|
72
|
-
close_connection
|
73
|
-
rescue Exception => e
|
74
|
-
close_connection
|
75
|
-
raise e
|
76
|
-
end
|
77
|
-
|
78
|
-
def write data
|
79
|
-
send_data data
|
80
|
-
end
|
81
|
-
|
82
|
-
def closed?
|
83
|
-
false
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
class HttpServer
|
89
|
-
DEFAULT_FIBER_POOL_SIZE = 20
|
90
|
-
def initialize(host, port, num_processors=950, x=0, y=nil) # Deal with Mongrel 1.0.1 or earlier, as well as later.
|
91
|
-
@socket = nil
|
92
|
-
@classifier = URIClassifier.new
|
93
|
-
@host = host
|
94
|
-
@port = port
|
95
|
-
@workers = ThreadGroup.new
|
96
|
-
if y
|
97
|
-
@throttle = x
|
98
|
-
@timeout = y || 60
|
99
|
-
else
|
100
|
-
@timeout = x
|
101
|
-
end
|
102
|
-
@num_processors = num_processors #num_processors is pointless for evented....
|
103
|
-
@death_time = 60
|
104
|
-
self.class.const_set(:Instance,self)
|
105
|
-
end
|
106
|
-
|
107
|
-
def fiber_pool
|
108
|
-
@fiber_pool ||= NB::Pool::FiberPool.new(DEFAULT_FIBER_POOL_SIZE)
|
109
|
-
end
|
110
|
-
|
111
|
-
def run
|
112
|
-
trap('INT') { raise StopServer }
|
113
|
-
trap('TERM') { raise StopServer }
|
114
|
-
@acceptor = Thread.new do
|
115
|
-
EventMachine.epoll
|
116
|
-
EventMachine.set_descriptor_table_size(4096)
|
117
|
-
EventMachine.run do
|
118
|
-
begin
|
119
|
-
EventMachine.start_server(@host,@port,MongrelProtocol)
|
120
|
-
rescue StopServer
|
121
|
-
EventMachine.stop_event_loop
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def process_http_request(params,linebuffer,client)
|
128
|
-
if not params[Const::REQUEST_PATH]
|
129
|
-
uri = URI.parse(params[Const::REQUEST_URI])
|
130
|
-
params[Const::REQUEST_PATH] = uri.request_uri
|
131
|
-
end
|
132
|
-
|
133
|
-
raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
|
134
|
-
|
135
|
-
script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH])
|
136
|
-
|
137
|
-
if handlers
|
138
|
-
notifiers = handlers.select { |h| h.request_notify }
|
139
|
-
request = HttpRequest.new(params, linebuffer, notifiers)
|
140
|
-
|
141
|
-
# request is good so far, continue processing the response
|
142
|
-
response = HttpResponse.new(client)
|
143
|
-
|
144
|
-
# Process each handler in registered order until we run out or one finalizes the response.
|
145
|
-
fiber_pool.spawn do
|
146
|
-
dispatch_to_handlers(handlers,request,response)
|
147
|
-
end
|
148
|
-
# And finally, if nobody closed the response off, we finalize it.
|
149
|
-
unless response.done
|
150
|
-
response.finished
|
151
|
-
else
|
152
|
-
response.close_connection_after_writing
|
153
|
-
end
|
154
|
-
else
|
155
|
-
# Didn't find it, return a stock 404 response.
|
156
|
-
client.send_data(Const::ERROR_404_RESPONSE)
|
157
|
-
client.close_connection_after_writing
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def dispatch_to_handlers(handlers,request,response)
|
162
|
-
handlers.each do |handler|
|
163
|
-
handler.process(request, response)
|
164
|
-
break if response.done
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
class HttpRequest
|
170
|
-
def initialize(params, linebuffer, dispatchers)
|
171
|
-
@params = params
|
172
|
-
@dispatchers = dispatchers
|
173
|
-
@body = linebuffer
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
class HttpResponse
|
178
|
-
def send_file(path, small_file = false)
|
179
|
-
File.open(path, "rb") do |f|
|
180
|
-
while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0
|
181
|
-
begin
|
182
|
-
write(chunk)
|
183
|
-
rescue Object => exc
|
184
|
-
break
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
@body_sent = true
|
189
|
-
end
|
190
|
-
|
191
|
-
def write(data)
|
192
|
-
@socket.send_data data
|
193
|
-
end
|
194
|
-
|
195
|
-
def close_connection_after_writing
|
196
|
-
@socket.close_connection_after_writing
|
197
|
-
end
|
198
|
-
|
199
|
-
def socket_error(details)
|
200
|
-
@socket.close_connection
|
201
|
-
done = true
|
202
|
-
raise details
|
203
|
-
end
|
204
|
-
|
205
|
-
def finished
|
206
|
-
send_status
|
207
|
-
send_header
|
208
|
-
send_body
|
209
|
-
@socket.close_connection_after_writing
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
class Configurator
|
214
|
-
# This version fixes a bug in the regular Mongrel version by adding
|
215
|
-
# initialization of groups.
|
216
|
-
def change_privilege(user, group)
|
217
|
-
if user and group
|
218
|
-
log "Initializing groups for {#user}:{#group}."
|
219
|
-
Process.initgroups(user,Etc.getgrnam(group).gid)
|
220
|
-
end
|
221
|
-
|
222
|
-
if group
|
223
|
-
log "Changing group to #{group}."
|
224
|
-
Process::GID.change_privilege(Etc.getgrnam(group).gid)
|
225
|
-
end
|
226
|
-
|
227
|
-
if user
|
228
|
-
log "Changing user to #{user}."
|
229
|
-
Process::UID.change_privilege(Etc.getpwnam(user).uid)
|
230
|
-
end
|
231
|
-
rescue Errno::EPERM
|
232
|
-
log "FAILED to change user:group #{user}:#{group}: #$!"
|
233
|
-
exit 1
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|