tracks 0.1.0 → 0.2.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 (2) hide show
  1. data/lib/tracks.rb +35 -30
  2. metadata +6 -6
data/lib/tracks.rb CHANGED
@@ -169,18 +169,21 @@ class Tracks
169
169
  # responding to #call. options should be a hash, with the following optional
170
170
  # keys, as symbols
171
171
  #
172
- # [:host] the host to listen on, defaults to 0.0.0.0
173
- # [:port] the port to listen on, defaults to 9292
174
- # [:read_timeout] the maximum amount of time, in seconds, to wait on idle
175
- # connections, defaults to 30
172
+ # [:host] the host to listen on, defaults to 0.0.0.0
173
+ # [:port] the port to listen on, defaults to 9292
174
+ # [:read_timeout] the maximum amount of time, in seconds, to wait on idle
175
+ # connections, defaults to 30
176
+ # [:shutdown_timeout] the maximum amount of time, in seconds, to wait for
177
+ # in process requests to complete when signalled to shut
178
+ # down, defaults to 30
176
179
  #
177
180
  def initialize(app, options={})
178
181
  @host = options[:host] || options[:Host] || "0.0.0.0"
179
182
  @port = (options[:port] || options[:Port] || "9292").to_s
180
183
  @read_timeout = options[:read_timeout] || 30
184
+ @shutdown_timeout = options[:shutdown_timeout] || 30
181
185
  @app = app
182
186
  @shutdown_signal, @signal_shutdown = IO.pipe
183
- @threads = ThreadGroup.new
184
187
  end
185
188
 
186
189
  # :call-seq: Tracks.run(rack_app[, options]) -> nil
@@ -191,36 +194,25 @@ class Tracks
191
194
  new(app, options).listen
192
195
  end
193
196
 
194
- # :call-seq: Tracks.shutdown([wait_time]) -> bool
197
+ # :call-seq: Tracks.shutdown -> nil
195
198
  #
196
- # Shut down all running Tracks servers, after waiting wait_time seconds for
197
- # each to gracefully shutdown. See #shutdown for more.
199
+ # Signal all running Tracks servers to shutdown.
198
200
  #
199
- def self.shutdown(wait=30)
200
- running.dup.inject(true) {|memo, s| s.shutdown(wait) && memo}
201
+ def self.shutdown
202
+ running.dup.each {|s| s.shutdown} && nil
201
203
  end
202
204
 
203
- # :call-seq: server.shutdown(wait_time) -> bool
205
+ # :call-seq: server.shutdown -> nil
204
206
  #
205
- # Shut down the server, after waiting wait_time seconds for graceful
206
- # shutdown. Returns true on graceful shutdown, false otherwise.
207
+ # Signal the server to shut down.
207
208
  #
208
- # wait_time defaults to 30 seconds.
209
- #
210
- # A return value of false indicates there were threads left running after
211
- # wait_time had expired which were forcibly killed. This may leave resources
212
- # in an inconsistant state, and it is advised you exit the process in this
213
- # case (likely what you were planning anyway).
214
- #
215
- def shutdown(wait=30)
209
+ def shutdown
216
210
  @shutdown = true
217
211
  self.class.running.delete(self)
218
- @signal_shutdown << "x"
219
- wait -= sleep 1 until @threads.list.empty? || wait <= 0
220
- @threads.list.each {|thread| thread.kill}.empty?
212
+ @signal_shutdown << "x" && nil
221
213
  end
222
214
 
223
- # :call-seq: server.listen([socket_server]) -> nil
215
+ # :call-seq: server.listen([socket_server]) -> bool
224
216
  #
225
217
  # Start listening for/accepting connections on socket_server. socket_server
226
218
  # defaults to a TCP server listening on the host and port supplied to ::new.
@@ -231,19 +223,27 @@ class Tracks
231
223
  # This method will block until #shutdown is called. The socket_server will
232
224
  # be closed when this method returns.
233
225
  #
226
+ # A return value of false indicates there were threads left running after
227
+ # shutdown_timeout had expired which were forcibly killed. This may leave
228
+ # resources in an inconsistant state, and it is advised you exit the process
229
+ # in this case (likely what you were planning anyway).
230
+ #
234
231
  def listen(server=TCPServer.new(@host, @port))
235
232
  @shutdown = false
236
233
  server.listen(1024) if server.respond_to?(:listen)
237
234
  @port, @host = server.addr[1,2].map{|e| e.to_s} if server.respond_to?(:addr)
238
235
  servers = [server, @shutdown_signal]
236
+ threads = ThreadGroup.new
239
237
  self.class.running << self
240
238
  puts "Tracks HTTP server available at #{@host}:#{@port}"
241
239
  while select(servers, nil, nil) && !@shutdown
242
- @threads.add(Thread.new(server.accept) {|sock| on_connection(sock)})
240
+ threads.add(Thread.new(server.accept) {|sock| on_connection(sock)})
243
241
  end
244
- @shutdown_signal.sysread(1) && nil
245
- ensure
246
242
  server.close
243
+ wait = @shutdown_timeout
244
+ wait -= sleep 1 until threads.list.empty? || wait <= 0
245
+ @shutdown_signal.sysread(1)
246
+ threads.list.each {|thread| thread.kill}.empty?
247
247
  end
248
248
 
249
249
  # :call-seq: server.on_connection(socket) -> nil
@@ -260,10 +260,14 @@ class Tracks
260
260
  def on_connection(socket)
261
261
  parser = HTTPTools::Parser.new
262
262
  buffer = ""
263
- sockets = [socket]
263
+ sockets = [socket, @shutdown_signal]
264
+ idle = false
264
265
  reader = Proc.new do
265
266
  readable, = select(sockets, nil, nil, @read_timeout)
266
267
  return unless readable
268
+ sockets.delete(@shutdown_signal) if @shutdown
269
+ return if idle && @shutdown
270
+ idle = false
267
271
  begin
268
272
  socket.sysread(16384, buffer)
269
273
  parser << buffer
@@ -301,11 +305,12 @@ class Tracks
301
305
  body.each {|chunk| socket << chunk}
302
306
  body.close if body.respond_to?(:close)
303
307
 
304
- if keep_alive
308
+ if keep_alive && !@shutdown
305
309
  reader.call until parser.finished?
306
310
  input.reset
307
311
  remainder = parser.rest.lstrip
308
312
  parser.reset << remainder
313
+ idle = true
309
314
  else
310
315
  break
311
316
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tracks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-21 00:00:00.000000000Z
12
+ date: 2012-05-26 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
16
- requirement: &70357509168700 !ruby/object:Gem::Requirement
16
+ requirement: &70193797609180 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70357509168700
24
+ version_requirements: *70193797609180
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: http_tools
27
- requirement: &70357509168220 !ruby/object:Gem::Requirement
27
+ requirement: &70193797608700 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: 0.4.1
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70357509168220
35
+ version_requirements: *70193797608700
36
36
  description: A bare-bones Ruby HTTP server that talks Rack and uses a thread per connection
37
37
  model of concurrency.
38
38
  email: mat@sourcetagsandcodes.com