espace-neverblock 0.1.2 → 0.1.3

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.
@@ -14,29 +14,28 @@ module NeverBlock
14
14
  end
15
15
  end
16
16
 
17
- # A proxy for the connection's exec method
17
+ # A proxy for the connection's query method
18
18
  # quries the pool to get a connection first
19
- def exec(query)
19
+ def query(query)
20
20
  @pool.hold do |conn|
21
21
  conn.query(query)
22
22
  end
23
23
  end
24
24
 
25
- alias :query :exec
26
25
  # This method must be called for transactions to work correctly.
27
26
  # One cannot just send "begin" as you never know which connection
28
27
  # will be available next. This method ensures you get the same connection
29
28
  # while in a transaction.
30
29
  def begin_db_transaction
31
30
  @pool.hold(true) do |conn|
32
- conn.exec("begin")
31
+ conn.query("begin")
33
32
  end
34
33
  end
35
34
 
36
35
  # see =begin_db_transaction
37
36
  def rollback_db_transaction
38
37
  @pool.hold do |conn|
39
- conn.exec("rollback")
38
+ conn.query("rollback")
40
39
  @pool.release(Fiber.current,conn)
41
40
  end
42
41
  end
@@ -44,7 +43,7 @@ module NeverBlock
44
43
  # see =begin_db_transaction
45
44
  def commit_db_transaction
46
45
  @pool.hold do |conn|
47
- conn.exec("commit")
46
+ conn.query("commit")
48
47
  @pool.release(Fiber.current,conn)
49
48
  end
50
49
  end
@@ -1,6 +1,4 @@
1
1
  require 'neverblock' unless defined?(NeverBlock)
2
- #require 'actionpack'
3
- #require 'action_controller'
4
2
 
5
3
  # Rails tries to protect dispatched actions
6
4
  # by wrapping them in a synchronized code
@@ -9,29 +7,59 @@ require 'neverblock' unless defined?(NeverBlock)
9
7
  # transform it (without it knowing) to
10
8
  # something more subtle
11
9
 
12
-
13
- =begin
14
- class ActionController::Dispatcher
15
-
16
- # let's show this guard who is
17
- # the man of the house
18
- @@guard = Object.new
19
-
20
- # now you synchronize
21
- def @@guard.synchronize(&block)
10
+ require 'thread'
11
+ # now you synchronize
12
+ class Mutex
13
+ def synchronize(&block)
22
14
  # now you don't!
23
15
  block.call
24
16
  end
25
17
  end
26
- =end
27
18
 
19
+ require 'action_controller'
20
+ class ActionController::Base
28
21
 
29
- require 'thread'
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
30
43
 
31
- # now you synchronize
32
- class Mutex
33
- def synchronize(&block)
34
- # now you don't!
35
- block.call
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
36
64
  end
37
65
  end
@@ -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
data/neverblock.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "neverblock"
3
- s.version = "0.1.2"
3
+ s.version = "0.1.3"
4
4
  s.date = "2008-09-04"
5
5
  s.summary = "Utilities for non-blocking stack components"
6
6
  s.email = "oldmoe@gmail.com"
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  "lib/never_block/frameworks/rails.rb",
22
22
  "lib/never_block/frameworks/activerecord.rb",
23
23
  "lib/never_block/servers/thin.rb",
24
+ "lib/never_block/servers/mongrel.rb",
24
25
  "lib/never_block/db/fibered_postgres_connection.rb",
25
26
  "lib/never_block/db/pooled_fibered_postgres_connection.rb",
26
27
  "lib/never_block/db/fibered_mysql_connection.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: espace-neverblock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muhammad A. Ali
@@ -36,6 +36,7 @@ files:
36
36
  - lib/never_block/frameworks/rails.rb
37
37
  - lib/never_block/frameworks/activerecord.rb
38
38
  - lib/never_block/servers/thin.rb
39
+ - lib/never_block/servers/mongrel.rb
39
40
  - lib/never_block/db/fibered_postgres_connection.rb
40
41
  - lib/never_block/db/pooled_fibered_postgres_connection.rb
41
42
  - lib/never_block/db/fibered_mysql_connection.rb