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,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
+