jaws 0.5.1 → 0.6.0

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.
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.