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,236 @@
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
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ require 'neverblock' unless defined?(NeverBlock)
3
+ require 'thin'
4
+
5
+ module Thin
6
+
7
+ # Patch the thin server to use NeverBlock::Pool::FiberPool to be able to
8
+ # wrap requests in fibers
9
+ class Server
10
+
11
+ DEFAULT_FIBER_POOL_SIZE = 20
12
+
13
+ def fiber_pool
14
+ @fiber_pool ||= NB::Pool::FiberPool.new(DEFAULT_FIBER_POOL_SIZE)
15
+ end
16
+
17
+ end # Server
18
+
19
+ # A request is processed by wrapping it in a fiber from the fiber pool.
20
+ # If all the fibers are busy the request will wait in a queue to be picked up
21
+ # later. Meanwhile, the server will still be processing requests
22
+ class Connection < EventMachine::Connection
23
+
24
+ def process
25
+ @request.threaded = false
26
+ @backend.server.fiber_pool.spawn {post_process(pre_process)}
27
+ end
28
+
29
+ end # Connection
30
+
31
+
32
+ end # Thin
@@ -0,0 +1,5 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+ require 'neverblock'
3
+ require 'never_block/db/fibered_db_connection'
4
+ require 'never_block/db/fibered_mysql_connection'
5
+ require 'never_block/db/pooled_db_connection'
@@ -0,0 +1,5 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+ require 'neverblock'
3
+ require 'never_block/db/fibered_db_connection'
4
+ require 'never_block/db/pooled_db_connection'
5
+ require 'never_block/db/fibered_postgres_connection'
@@ -0,0 +1,8 @@
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
+ $:.unshift File.expand_path(File.dirname(__FILE__))
6
+
7
+ require 'never_block'
8
+
@@ -0,0 +1,73 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{neverblock}
8
+ s.version = "0.1.6.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Muhammad A. Ali", "Ahmed Sobhi", "Osama Brekaa", "Nicholas Silva"]
12
+ s.date = %q{2010-02-03}
13
+ s.description = %q{NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.}
14
+ s.email = %q{conickal@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ "README",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "lib/active_record/connection_adapters/neverblock_mysql_adapter.rb",
23
+ "lib/active_record/connection_adapters/neverblock_postgresql_adapter.rb",
24
+ "lib/never_block.rb",
25
+ "lib/never_block/db/fibered_db_connection.rb",
26
+ "lib/never_block/db/fibered_mysql_connection.rb",
27
+ "lib/never_block/db/fibered_postgres_connection.rb",
28
+ "lib/never_block/db/pooled_db_connection.rb",
29
+ "lib/never_block/extensions/fiber_extensions.rb",
30
+ "lib/never_block/frameworks/activerecord.rb",
31
+ "lib/never_block/frameworks/rails.rb",
32
+ "lib/never_block/pool/fiber_pool.rb",
33
+ "lib/never_block/pool/fibered_connection_pool.rb",
34
+ "lib/never_block/servers/mongrel.rb",
35
+ "lib/never_block/servers/thin.rb",
36
+ "lib/neverblock-mysql.rb",
37
+ "lib/neverblock-pg.rb",
38
+ "lib/neverblock.rb",
39
+ "neverblock.gemspec",
40
+ "spec/fiber_extensions_spec.rb",
41
+ "spec/fiber_pool_spec.rb",
42
+ "spec/fibered_connection_pool_spec.rb",
43
+ "tasks/spec.rake",
44
+ "test/test_mysql.rb",
45
+ "test/test_pg.rb"
46
+ ]
47
+ s.homepage = %q{http://github.com/conickal/neverblock}
48
+ s.rdoc_options = ["--charset=UTF-8"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = %q{1.3.5}
51
+ s.summary = %q{Utilities for non-blocking stack components}
52
+ s.test_files = [
53
+ "spec/fiber_extensions_spec.rb",
54
+ "spec/fiber_pool_spec.rb",
55
+ "spec/fibered_connection_pool_spec.rb",
56
+ "test/test_mysql.rb",
57
+ "test/test_pg.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
65
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.2"])
66
+ else
67
+ s.add_dependency(%q<eventmachine>, [">= 0.12.2"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<eventmachine>, [">= 0.12.2"])
71
+ end
72
+ end
73
+
@@ -0,0 +1,24 @@
1
+ $:.unshift File.expand_path('..')
2
+ require 'lib/neverblock'
3
+
4
+ describe Fiber do
5
+ before(:all) do
6
+ @fiber = Fiber.new {puts "I'm a new fiber"}
7
+ end
8
+
9
+ it "should be able to set fiber local variable" do
10
+ @fiber[:x] = "wow"
11
+ end
12
+
13
+ it "should be able to retrieve an already set fiber local variable" do
14
+ @fiber[:x].should == "wow"
15
+ end
16
+
17
+ it "should return nil when trying to retrieve an unset fiber local variable" do
18
+ @fiber[:y].should == nil
19
+ end
20
+
21
+ after(:all) do
22
+ @fiber = nil
23
+ end
24
+ end
@@ -0,0 +1,60 @@
1
+ $:.unshift File.expand_path('..')
2
+ require 'lib/neverblock'
3
+
4
+ describe NeverBlock::Pool::FiberPool do
5
+ before(:each) do
6
+ @fiber_pool = NeverBlock::Pool::FiberPool.new(10)
7
+ end
8
+
9
+ it "should have all fibers ready and an empty queue initially" do
10
+ @fiber_pool.fibers.length.should == 10
11
+ @fiber_pool.instance_variable_get(:@queue).length.should == 0
12
+ end
13
+
14
+ it "should have fibers with :neverblock fiber variable set to true" do
15
+ @fiber_pool.fibers.each {|f| f[:neverblock].should == true}
16
+ end
17
+
18
+ it "should process a new block if there are available fibers" do
19
+ x = false
20
+ @fiber_pool.spawn {x = true}
21
+ x.should == true
22
+ end
23
+
24
+ it "should queue requests if requests are more than fibers" do
25
+ progress = Array.new(15, false)
26
+ fibers = []
27
+ @fiber_pool.fibers.each {|f| fibers << f}
28
+ 10.times do |i|
29
+ @fiber_pool.spawn {Fiber.yield; progress[i] = true}
30
+ end
31
+ @fiber_pool.fibers.length.should == 0
32
+ @fiber_pool.instance_variable_get(:@queue).length.should == 0
33
+
34
+ #it should now queue
35
+ (10..14).each {|i| @fiber_pool.spawn {progress[i] = true}}
36
+ @fiber_pool.fibers.length.should == 0
37
+ @fiber_pool.instance_variable_get(:@queue).length.should == 5
38
+
39
+ #resume the first fiber, this should also process the queued requests
40
+ fibers[0].resume
41
+ @fiber_pool.fibers.length.should == 1
42
+ @fiber_pool.instance_variable_get(:@queue).length.should == 0
43
+ [0,*10..14].each {|i| progress[i].should == true}
44
+ (1..9).to_a.each {|i| progress[i].should == false}
45
+ fibers_count = 1
46
+ (1..9).to_a.each do |i|
47
+ fibers[i].resume
48
+ fibers_count = fibers_count + 1
49
+ progress[i].should == true
50
+ @fiber_pool.fibers.length.should == fibers_count
51
+ end
52
+ @fiber_pool.instance_variable_get(:@queue).length.should == 0
53
+ end
54
+
55
+ after(:each) do
56
+ @fiber_pool = nil
57
+ end
58
+
59
+ end
60
+