jaws 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/Rakefile +2 -2
  2. data/VERSION +1 -1
  3. data/lib/jaws/server.rb +71 -18
  4. metadata +9 -9
data/Rakefile CHANGED
@@ -11,8 +11,8 @@ begin
11
11
  gem.homepage = "http://github.com/stormbrew/jaws"
12
12
  gem.authors = ["Graham Batty"]
13
13
  gem.add_development_dependency "rspec", ">= 1.2.9"
14
- gem.add_dependency "http_parser", ">= 0.1.2"
15
- gem.add_dependency "rack", ">= 1.1.0"
14
+ gem.add_dependency "http_parser", "= 0.1.3"
15
+ gem.add_dependency "rack", ">= 1.0.0"
16
16
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
17
  end
18
18
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.1
1
+ 0.6.0
@@ -11,6 +11,7 @@ module Jaws
11
11
  name.gsub(%r{(^|_)([a-z])}) { $2.upcase }
12
12
  end
13
13
 
14
+ class GracefulExit < RuntimeError; end
14
15
  class Server
15
16
  DefaultOptions = {
16
17
  :Host => '0.0.0.0',
@@ -75,9 +76,10 @@ module Jaws
75
76
  # an object that responds to accept() by returning an open connection to a
76
77
  # client. It also has to respond to synchronize and yield to the block
77
78
  # given to that method and be thread safe in that block. It must also
78
- # respond to close() by immediately terminating any waiting accept() calls
79
- # and responding to closed? with true thereafter. Close may be called
80
- # from outside the object's synchronize block.
79
+ # respond to close() by refusing to accept any further connections and
80
+ # returning true from closed?() thereafter. The accept() call may be interrupted
81
+ # by a GracefulExit error, it should not block or do anything special with this
82
+ # error.
81
83
  def create_listener(options)
82
84
  l = TCPServer.new(@host, @port)
83
85
  # let 10 requests back up for each request we can handle concurrently.
@@ -249,16 +251,19 @@ module Jaws
249
251
  # the connection closes.
250
252
  def process_client(app)
251
253
  loop do
254
+ client = nil
252
255
  begin
253
- client = @listener.synchronize do
254
- begin
255
- @listener && @listener.accept()
256
- rescue => e
257
- return # this means we've been turned off, so exit the loop.
256
+ make_interruptable do
257
+ client = @listener.synchronize do
258
+ begin
259
+ @listener && @listener.accept()
260
+ rescue => e
261
+ return # this means we've been turned off, so exit the loop.
262
+ end
263
+ end
264
+ if (!client)
265
+ return # nil return means we're quitting, exit loop.
258
266
  end
259
- end
260
- if (!client)
261
- return # nil return means we're quitting, exit loop.
262
267
  end
263
268
 
264
269
  req = Http::Parser.new()
@@ -285,8 +290,10 @@ module Jaws
285
290
  client.close_write
286
291
  end
287
292
  end
288
- rescue Errno::EPIPE
293
+ rescue Errno::EPIPE
289
294
  # do nothing, just let the connection close.
295
+ rescue SystemExit, GracefulExit
296
+ raise # pass it on.
290
297
  rescue Object => e
291
298
  $stderr.puts("Unhandled error #{e}:")
292
299
  e.backtrace.each do |line|
@@ -299,31 +306,69 @@ module Jaws
299
306
  end
300
307
  private :process_client
301
308
 
309
+ # Sets the current thread as interruptable. This happens around
310
+ # the listen part of the thread. This means the thread is receptive
311
+ # to t.raise.
312
+ def make_interruptable
313
+ begin
314
+ @interruptable.synchronize do
315
+ @interruptable << Thread.current
316
+ end
317
+ yield
318
+ ensure
319
+ @interruptable.synchronize do
320
+ @interruptable.delete(Thread.current)
321
+ end
322
+ end
323
+ end
324
+
302
325
  # Runs the application through the configured handler.
303
326
  # Can only be run once at a time. If you try to run it more than
304
327
  # once, the second run will block until the first finishes.
305
328
  def run(app)
306
329
  synchronize do
330
+ @interruptable = []
331
+ int_orig = trap "INT" do
332
+ stop()
333
+ end
334
+ term_orig = trap "TERM" do
335
+ stop()
336
+ end
307
337
  begin
308
338
  @listener = create_listener(@options)
339
+ @interruptable.extend Mutex_m
309
340
  if (@max_clients > 1)
310
341
  @master = Thread.current
311
342
  @workers = (0...@max_clients).collect do
312
343
  Thread.new do
313
- process_client(app)
344
+ begin
345
+ process_client(app)
346
+ rescue GracefulExit, SystemExit => e
347
+ # let it exit.
348
+ rescue => e
349
+ $stderr.puts("Handler thread unexpectedly died with #{e}:", e.backtrace)
350
+ end
314
351
  end
315
352
  end
316
353
  @workers.each do |worker|
317
354
  worker.join
318
355
  end
319
356
  else
320
- @master = Thread.current
321
- @workers = [Thread.current]
322
- process_client(app)
357
+ begin
358
+ @master = Thread.current
359
+ @workers = [Thread.current]
360
+ process_client(app)
361
+ rescue GracefulExit, SystemExit => e
362
+ # let it exit
363
+ rescue => e
364
+ $stderr.puts("Handler thread unexpectedly died with #{e}:", e.backtrace)
365
+ end
323
366
  end
324
367
  ensure
368
+ trap "INT", int_orig
369
+ trap "TERM", term_orig
325
370
  @listener.close if (@listener && !@listener.closed?)
326
- @listener = @master = @workers = nil
371
+ @interruptable = @listener = @master = @workers = nil
327
372
  end
328
373
  end
329
374
  end
@@ -332,7 +377,15 @@ module Jaws
332
377
  # close the connection, the handler threads will exit
333
378
  # the next time they try to load.
334
379
  # TODO: Make it force them to exit after a timeout.
335
- @listener.close if !@listener.closed?
380
+ $stderr.puts("Terminating request threads. To force immediate exit, send sigkill.")
381
+ @interruptable.synchronize do
382
+ @listener.close if !@listener.closed?
383
+ @workers.each do |worker|
384
+ if (@interruptable.include?(worker))
385
+ worker.raise GracefulExit, "Exiting"
386
+ end
387
+ end
388
+ end
336
389
  end
337
390
 
338
391
  def running?
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 5
8
- - 1
9
- version: 0.5.1
7
+ - 6
8
+ - 0
9
+ version: 0.6.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Graham Batty
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-17 00:00:00 -06:00
17
+ date: 2010-03-19 00:00:00 -06:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -36,13 +36,13 @@ dependencies:
36
36
  prerelease: false
37
37
  requirement: &id002 !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ">="
39
+ - - "="
40
40
  - !ruby/object:Gem::Version
41
41
  segments:
42
42
  - 0
43
43
  - 1
44
- - 2
45
- version: 0.1.2
44
+ - 3
45
+ version: 0.1.3
46
46
  type: :runtime
47
47
  version_requirements: *id002
48
48
  - !ruby/object:Gem::Dependency
@@ -54,9 +54,9 @@ dependencies:
54
54
  - !ruby/object:Gem::Version
55
55
  segments:
56
56
  - 1
57
- - 1
58
57
  - 0
59
- version: 1.1.0
58
+ - 0
59
+ version: 1.0.0
60
60
  type: :runtime
61
61
  version_requirements: *id003
62
62
  description: A Ruby web server designed to have a predictable and simple concurrency model, and to be capable of running in a pure-ruby environment.