yahns 0.0.0TP1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/COPYING +674 -0
- data/GIT-VERSION-GEN +41 -0
- data/GNUmakefile +90 -0
- data/README +127 -0
- data/Rakefile +60 -0
- data/bin/yahns +32 -0
- data/examples/README +3 -0
- data/examples/init.sh +76 -0
- data/examples/logger_mp_safe.rb +28 -0
- data/examples/logrotate.conf +32 -0
- data/examples/yahns_multi.conf.rb +89 -0
- data/examples/yahns_rack_basic.conf.rb +27 -0
- data/lib/yahns.rb +73 -0
- data/lib/yahns/acceptor.rb +28 -0
- data/lib/yahns/client_expire.rb +40 -0
- data/lib/yahns/client_expire_portable.rb +39 -0
- data/lib/yahns/config.rb +344 -0
- data/lib/yahns/daemon.rb +51 -0
- data/lib/yahns/fdmap.rb +90 -0
- data/lib/yahns/http_client.rb +198 -0
- data/lib/yahns/http_context.rb +65 -0
- data/lib/yahns/http_response.rb +184 -0
- data/lib/yahns/log.rb +73 -0
- data/lib/yahns/queue.rb +7 -0
- data/lib/yahns/queue_egg.rb +23 -0
- data/lib/yahns/queue_epoll.rb +57 -0
- data/lib/yahns/rack.rb +80 -0
- data/lib/yahns/server.rb +336 -0
- data/lib/yahns/server_mp.rb +181 -0
- data/lib/yahns/sigevent.rb +7 -0
- data/lib/yahns/sigevent_efd.rb +18 -0
- data/lib/yahns/sigevent_pipe.rb +29 -0
- data/lib/yahns/socket_helper.rb +117 -0
- data/lib/yahns/stream_file.rb +34 -0
- data/lib/yahns/stream_input.rb +150 -0
- data/lib/yahns/tee_input.rb +114 -0
- data/lib/yahns/tmpio.rb +27 -0
- data/lib/yahns/wbuf.rb +36 -0
- data/lib/yahns/wbuf_common.rb +32 -0
- data/lib/yahns/worker.rb +58 -0
- data/test/covshow.rb +29 -0
- data/test/helper.rb +115 -0
- data/test/server_helper.rb +65 -0
- data/test/test_bin.rb +97 -0
- data/test/test_client_expire.rb +132 -0
- data/test/test_config.rb +56 -0
- data/test/test_fdmap.rb +19 -0
- data/test/test_output_buffering.rb +291 -0
- data/test/test_queue.rb +59 -0
- data/test/test_rack.rb +28 -0
- data/test/test_serve_static.rb +42 -0
- data/test/test_server.rb +415 -0
- data/test/test_stream_file.rb +30 -0
- data/test/test_wbuf.rb +136 -0
- data/yahns.gemspec +19 -0
- metadata +165 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
|
3
|
+
# License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
|
4
|
+
|
5
|
+
# acts like tee(1) on an input input to provide a input-like stream
|
6
|
+
# while providing rewindable semantics through a File/StringIO backing
|
7
|
+
# store. On the first pass, the input is only read on demand so your
|
8
|
+
# Rack application can use input notification (upload progress and
|
9
|
+
# like). This should fully conform to the Rack::Lint::InputWrapper
|
10
|
+
# specification on the public API. This class is intended to be a
|
11
|
+
# strict interpretation of Rack::Lint::InputWrapper functionality and
|
12
|
+
# will not support any deviations from it.
|
13
|
+
#
|
14
|
+
# When processing uploads, Yahns exposes a TeeInput object under
|
15
|
+
# "rack.input" of the Rack environment.
|
16
|
+
class Yahns::TeeInput < Yahns::StreamInput # :nodoc:
|
17
|
+
# Initializes a new TeeInput object. You normally do not have to call
|
18
|
+
# this unless you are writing an HTTP server.
|
19
|
+
def initialize(client, request)
|
20
|
+
@len = request.content_length
|
21
|
+
super
|
22
|
+
@tmp = client.class.tmpio_for(@len)
|
23
|
+
end
|
24
|
+
|
25
|
+
# :call-seq:
|
26
|
+
# ios.size => Integer
|
27
|
+
#
|
28
|
+
# Returns the size of the input. For requests with a Content-Length
|
29
|
+
# header value, this will not read data off the socket and just return
|
30
|
+
# the value of the Content-Length header as an Integer.
|
31
|
+
#
|
32
|
+
# For Transfer-Encoding:chunked requests, this requires consuming
|
33
|
+
# all of the input stream before returning since there's no other
|
34
|
+
# way to determine the size of the request body beforehand.
|
35
|
+
#
|
36
|
+
# This method is no longer part of the Rack specification as of
|
37
|
+
# Rack 1.2, so its use is not recommended. This method only exists
|
38
|
+
# for compatibility with Rack applications designed for Rack 1.1 and
|
39
|
+
# earlier. Most applications should only need to call +read+ with a
|
40
|
+
# specified +length+ in a loop until it returns +nil+.
|
41
|
+
def size
|
42
|
+
@len and return @len
|
43
|
+
pos = @tmp.pos
|
44
|
+
consume!
|
45
|
+
@tmp.pos = pos
|
46
|
+
@len = @tmp.size
|
47
|
+
end
|
48
|
+
|
49
|
+
# :call-seq:
|
50
|
+
# ios.read([length [, buffer ]]) => string, buffer, or nil
|
51
|
+
#
|
52
|
+
# Reads at most length bytes from the I/O stream, or to the end of
|
53
|
+
# file if length is omitted or is nil. length must be a non-negative
|
54
|
+
# integer or nil. If the optional buffer argument is present, it
|
55
|
+
# must reference a String, which will receive the data.
|
56
|
+
#
|
57
|
+
# At end of file, it returns nil or "" depend on length.
|
58
|
+
# ios.read() and ios.read(nil) returns "".
|
59
|
+
# ios.read(length [, buffer]) returns nil.
|
60
|
+
#
|
61
|
+
# If the Content-Length of the HTTP request is known (as is the common
|
62
|
+
# case for POST requests), then ios.read(length [, buffer]) will block
|
63
|
+
# until the specified length is read (or it is the last chunk).
|
64
|
+
# Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
|
65
|
+
# ios.read(length [, buffer]) will return immediately if there is
|
66
|
+
# any data and only block when nothing is available (providing
|
67
|
+
# IO#readpartial semantics).
|
68
|
+
def read(*args)
|
69
|
+
@client ? tee(super) : @tmp.read(*args)
|
70
|
+
end
|
71
|
+
|
72
|
+
# :call-seq:
|
73
|
+
# ios.gets => string or nil
|
74
|
+
#
|
75
|
+
# Reads the next ``line'' from the I/O stream; lines are separated
|
76
|
+
# by the global record separator ($/, typically "\n"). A global
|
77
|
+
# record separator of nil reads the entire unread contents of ios.
|
78
|
+
# Returns nil if called at the end of file.
|
79
|
+
# This takes zero arguments for strict Rack::Lint compatibility,
|
80
|
+
# unlike IO#gets.
|
81
|
+
def gets
|
82
|
+
@client ? tee(super) : @tmp.gets
|
83
|
+
end
|
84
|
+
|
85
|
+
# :call-seq:
|
86
|
+
# ios.rewind => 0
|
87
|
+
#
|
88
|
+
# Positions the *ios* pointer to the beginning of input, returns
|
89
|
+
# the offset (zero) of the +ios+ pointer. Subsequent reads will
|
90
|
+
# start from the beginning of the previously-buffered input.
|
91
|
+
def rewind
|
92
|
+
return 0 if 0 == @tmp.size
|
93
|
+
consume! if @client
|
94
|
+
@tmp.rewind # Rack does not specify what the return value is here
|
95
|
+
end
|
96
|
+
|
97
|
+
# consumes the stream of the socket
|
98
|
+
def consume!
|
99
|
+
junk = ""
|
100
|
+
rsize = __rsize
|
101
|
+
nil while read(rsize, junk)
|
102
|
+
end
|
103
|
+
|
104
|
+
def tee(buffer)
|
105
|
+
if buffer && buffer.size > 0
|
106
|
+
@tmp.write(buffer)
|
107
|
+
end
|
108
|
+
buffer
|
109
|
+
end
|
110
|
+
|
111
|
+
def close # returns nil
|
112
|
+
@tmp = @tmp.close
|
113
|
+
end
|
114
|
+
end
|
data/lib/yahns/tmpio.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
|
3
|
+
# License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
# some versions of Ruby had a broken Tempfile which didn't work
|
7
|
+
# well with unlinked files. This one is much shorter, easier
|
8
|
+
# to understand, and slightly faster (no delegation).
|
9
|
+
class Yahns::TmpIO < File # :nodoc:
|
10
|
+
|
11
|
+
# creates and returns a new File object. The File is unlinked
|
12
|
+
# immediately, switched to binary mode, and userspace output
|
13
|
+
# buffering is disabled
|
14
|
+
def self.new
|
15
|
+
fp = begin
|
16
|
+
super("#{Dir.tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
|
17
|
+
rescue Errno::EEXIST
|
18
|
+
retry
|
19
|
+
end
|
20
|
+
unlink(fp.path)
|
21
|
+
fp.binmode
|
22
|
+
fp.sync = true
|
23
|
+
fp
|
24
|
+
end
|
25
|
+
|
26
|
+
alias discard close
|
27
|
+
end
|
data/lib/yahns/wbuf.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> et. al.
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require_relative 'wbuf_common'
|
5
|
+
|
6
|
+
class Yahns::Wbuf # :nodoc:
|
7
|
+
include Yahns::WbufCommon
|
8
|
+
|
9
|
+
def initialize(body, persist)
|
10
|
+
@tmpio = Yahns::TmpIO.new
|
11
|
+
@sf_offset = @sf_count = 0
|
12
|
+
@wbuf_persist = persist # whether or not we keep the connection alive
|
13
|
+
@body = body
|
14
|
+
end
|
15
|
+
|
16
|
+
def wbuf_write(client, buf)
|
17
|
+
@sf_count += @tmpio.write(buf)
|
18
|
+
case rv = client.trysendfile(@tmpio, @sf_offset, @sf_count)
|
19
|
+
when Integer
|
20
|
+
@sf_count -= rv
|
21
|
+
@sf_offset += rv
|
22
|
+
when :wait_writable, :wait_readable
|
23
|
+
return rv
|
24
|
+
else
|
25
|
+
raise "BUG: #{rv.nil ? "EOF" : rv.inspect} on tmpio " \
|
26
|
+
"sf_offset=#@sf_offset sf_count=#@sf_count"
|
27
|
+
end while @sf_count > 0
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# called by last wbuf_flush
|
32
|
+
def wbuf_close(client)
|
33
|
+
@tmpio = @tmpio.close
|
34
|
+
wbuf_close_common(client)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require 'sendfile'
|
5
|
+
module Yahns::WbufCommon # :nodoc:
|
6
|
+
# returns nil on success, :wait_*able when blocked
|
7
|
+
# currently, we rely on each thread having exclusive access to the
|
8
|
+
# client socket, so this is never called concurrently with wbuf_write
|
9
|
+
def wbuf_flush(client)
|
10
|
+
case rv = client.trysendfile(@tmpio, @sf_offset, @sf_count)
|
11
|
+
when Integer
|
12
|
+
return wbuf_close(client) if (@sf_count -= rv) == 0 # all sent!
|
13
|
+
|
14
|
+
@sf_offset += rv # keep going otherwise
|
15
|
+
when :wait_writable, :wait_readable
|
16
|
+
return rv
|
17
|
+
else
|
18
|
+
raise "BUG: #{rv.nil? ? "EOF" : rv.inspect} on tmpio=#{@tmpio.inspect} " \
|
19
|
+
"sf_offset=#@sf_offset sf_count=#@sf_count"
|
20
|
+
end while true
|
21
|
+
end
|
22
|
+
|
23
|
+
def wbuf_close_common(client)
|
24
|
+
@body.close if @body.respond_to?(:close)
|
25
|
+
if @wbuf_persist.respond_to?(:call) # hijack
|
26
|
+
@wbuf_persist.call(client)
|
27
|
+
:delete
|
28
|
+
else
|
29
|
+
@wbuf_persist # true or false or Yahns::StreamFile
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/yahns/worker.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
class Yahns::Worker # :nodoc:
|
5
|
+
attr_accessor :nr
|
6
|
+
attr_reader :to_io
|
7
|
+
|
8
|
+
def initialize(nr)
|
9
|
+
@nr = nr
|
10
|
+
@to_io, @wr = Kgio::Pipe.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def atfork_child
|
14
|
+
@wr = @wr.close # nil @wr to save space in worker process
|
15
|
+
end
|
16
|
+
|
17
|
+
def atfork_parent
|
18
|
+
@to_io = @to_io.close
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# used in the worker process.
|
23
|
+
# This causes the worker to gracefully exit if the master
|
24
|
+
# dies unexpectedly.
|
25
|
+
def yahns_step
|
26
|
+
@to_io.kgio_tryread(11) == nil and Process.kill(:QUIT, $$)
|
27
|
+
:wait_readable
|
28
|
+
end
|
29
|
+
|
30
|
+
# worker objects may be compared to just plain Integers
|
31
|
+
def ==(other_nr) # :nodoc:
|
32
|
+
@nr == other_nr
|
33
|
+
end
|
34
|
+
|
35
|
+
# Changes the worker process to the specified +user+ and +group+
|
36
|
+
# This is only intended to be called from within the worker
|
37
|
+
# process from the +after_fork+ hook. This should be called in
|
38
|
+
# the +after_fork+ hook after any privileged functions need to be
|
39
|
+
# run (e.g. to set per-worker CPU affinity, niceness, etc)
|
40
|
+
#
|
41
|
+
# Any and all errors raised within this method will be propagated
|
42
|
+
# directly back to the caller (usually the +after_fork+ hook.
|
43
|
+
# These errors commonly include ArgumentError for specifying an
|
44
|
+
# invalid user/group and Errno::EPERM for insufficient privileges
|
45
|
+
def user(user, group = nil)
|
46
|
+
# we do not protect the caller, checking Process.euid == 0 is
|
47
|
+
# insufficient because modern systems have fine-grained
|
48
|
+
# capabilities. Let the caller handle any and all errors.
|
49
|
+
uid = Etc.getpwnam(user).uid
|
50
|
+
gid = Etc.getgrnam(group).gid if group
|
51
|
+
Yahns::Log.chown_all(uid, gid)
|
52
|
+
if gid && Process.egid != gid
|
53
|
+
Process.initgroups(user, gid)
|
54
|
+
Process::GID.change_privilege(gid)
|
55
|
+
end
|
56
|
+
Process.euid != uid and Process::UID.change_privilege(uid)
|
57
|
+
end
|
58
|
+
end
|
data/test/covshow.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
#
|
4
|
+
# this works with the __covmerge method in test/helper.rb
|
5
|
+
# run this file after all tests are run
|
6
|
+
|
7
|
+
# load the merged dump data
|
8
|
+
res = Marshal.load(IO.binread("coverage.dump"))
|
9
|
+
|
10
|
+
# Dirty little text formatter. I tried simplecov but the default
|
11
|
+
# HTML+JS is unusable without a GUI (I hate GUIs :P) and it would've
|
12
|
+
# taken me longer to search the Internets to find a plain-text
|
13
|
+
# formatter I like...
|
14
|
+
res.keys.sort.each do |filename|
|
15
|
+
cov = res[filename]
|
16
|
+
puts "==> #{filename} <=="
|
17
|
+
File.readlines(filename).each_with_index do |line, i|
|
18
|
+
n = cov[i]
|
19
|
+
if n == 0 # BAD
|
20
|
+
print(" *** 0 #{line}")
|
21
|
+
elsif n
|
22
|
+
printf("% 7u %s", n, line)
|
23
|
+
elsif line =~ /\S/ # probably a line with just "end" in it
|
24
|
+
print(" #{line}")
|
25
|
+
else # blank line
|
26
|
+
print "\n" # don't output trailing whitespace on blank lines
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
$stdout.sync = $stderr.sync = Thread.abort_on_exception = true
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
# Global Test Lock, to protect:
|
7
|
+
# Process.wait*, Dir.chdir, ENV, trap, require, etc...
|
8
|
+
GTL = Mutex.new
|
9
|
+
|
10
|
+
# fork-aware coverage data gatherer, see also test/covshow.rb
|
11
|
+
if ENV["COVERAGE"]
|
12
|
+
require "coverage"
|
13
|
+
COVMATCH = %r{/lib/yahns\b.*rb\z}
|
14
|
+
COVTMP = File.open("coverage.dump", IO::CREAT|IO::RDWR)
|
15
|
+
COVTMP.binmode
|
16
|
+
COVTMP.sync = true
|
17
|
+
|
18
|
+
def __covmerge
|
19
|
+
res = Coverage.result
|
20
|
+
|
21
|
+
# we own this file (at least until somebody tries to use NFS :x)
|
22
|
+
COVTMP.flock(File::LOCK_EX)
|
23
|
+
|
24
|
+
COVTMP.rewind
|
25
|
+
prev = COVTMP.read
|
26
|
+
prev = prev.empty? ? {} : Marshal.load(prev)
|
27
|
+
res.each do |filename, counts|
|
28
|
+
# filter out stuff that's not in our project
|
29
|
+
COVMATCH =~ filename or next
|
30
|
+
|
31
|
+
merge = prev[filename] || []
|
32
|
+
merge = merge
|
33
|
+
counts.each_with_index do |count, i|
|
34
|
+
count or next
|
35
|
+
merge[i] = (merge[i] || 0) + count
|
36
|
+
end
|
37
|
+
prev[filename] = merge
|
38
|
+
end
|
39
|
+
COVTMP.rewind
|
40
|
+
COVTMP.truncate(0)
|
41
|
+
COVTMP.write(Marshal.dump(prev))
|
42
|
+
COVTMP.flock(File::LOCK_UN)
|
43
|
+
end
|
44
|
+
|
45
|
+
Coverage.start
|
46
|
+
at_exit { at_exit { __covmerge } }
|
47
|
+
end
|
48
|
+
|
49
|
+
gem 'minitest'
|
50
|
+
require 'minitest/autorun'
|
51
|
+
require "tempfile"
|
52
|
+
|
53
|
+
Testcase = begin
|
54
|
+
Minitest::Test # minitest 5
|
55
|
+
rescue NameError
|
56
|
+
Minitest::Unit::TestCase # minitest 4
|
57
|
+
end
|
58
|
+
|
59
|
+
FIFOS = []
|
60
|
+
def tmpfifo
|
61
|
+
tmp = Tempfile.new(%w(yahns-test .fifo))
|
62
|
+
path = tmp.path
|
63
|
+
tmp.close!
|
64
|
+
assert system(*%W(mkfifo #{path})), "mkfifo #{path}"
|
65
|
+
|
66
|
+
GTL.synchronize do
|
67
|
+
if FIFOS.empty?
|
68
|
+
at_exit do
|
69
|
+
FIFOS.each { |(pid,_path)| File.unlink(_path) if $$ == pid }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
FIFOS << [ $$, path ]
|
73
|
+
end
|
74
|
+
path
|
75
|
+
end
|
76
|
+
|
77
|
+
require 'tmpdir'
|
78
|
+
class Dir
|
79
|
+
require 'fileutils'
|
80
|
+
def Dir.mktmpdir
|
81
|
+
begin
|
82
|
+
d = "#{Dir.tmpdir}/#$$.#{rand}"
|
83
|
+
Dir.mkdir(d)
|
84
|
+
rescue Errno::EEXIST
|
85
|
+
end while true
|
86
|
+
begin
|
87
|
+
yield d
|
88
|
+
ensure
|
89
|
+
FileUtils.remove_entry(d)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end unless Dir.respond_to?(:mktmpdir)
|
93
|
+
|
94
|
+
def tmpfile(*args)
|
95
|
+
tmp = Tempfile.new(*args)
|
96
|
+
tmp.sync = true
|
97
|
+
tmp.binmode
|
98
|
+
tmp
|
99
|
+
end
|
100
|
+
|
101
|
+
require 'io/wait'
|
102
|
+
# needed for Rubinius 2.0.0, we only use IO#nread in tests
|
103
|
+
class IO
|
104
|
+
# this ignores buffers
|
105
|
+
def nread
|
106
|
+
buf = "\0" * 8
|
107
|
+
ioctl(0x541B, buf)
|
108
|
+
buf.unpack("l_")[0]
|
109
|
+
end
|
110
|
+
end if ! IO.method_defined?(:nread) && RUBY_PLATFORM =~ /linux/
|
111
|
+
|
112
|
+
require 'yahns'
|
113
|
+
|
114
|
+
# needed for parallel (MT) tests)
|
115
|
+
require 'yahns/rack'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'timeout'
|
5
|
+
require 'socket'
|
6
|
+
require 'net/http'
|
7
|
+
|
8
|
+
module ServerHelper
|
9
|
+
def check_err(err = @err)
|
10
|
+
err = File.open(err.path, "r") if err.respond_to?(:path)
|
11
|
+
err.rewind
|
12
|
+
lines = err.readlines.delete_if { |l| l =~ /INFO/ }
|
13
|
+
assert lines.empty?, lines.join("\n")
|
14
|
+
err.close! if err == @err
|
15
|
+
end
|
16
|
+
|
17
|
+
def poke_until_dead(pid)
|
18
|
+
Timeout.timeout(10) do
|
19
|
+
begin
|
20
|
+
Process.kill(0, pid)
|
21
|
+
sleep(0.01)
|
22
|
+
rescue Errno::ESRCH
|
23
|
+
break
|
24
|
+
end while true
|
25
|
+
end
|
26
|
+
assert_raises(Errno::ESRCH) { Process.kill(0, pid) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def quit_wait(pid)
|
30
|
+
pid or return
|
31
|
+
Process.kill(:QUIT, pid)
|
32
|
+
_, status = Timeout.timeout(10) { Process.waitpid2(pid) }
|
33
|
+
assert status.success?, status.inspect
|
34
|
+
rescue Timeout::Error
|
35
|
+
if RUBY_PLATFORM =~ /linux/
|
36
|
+
system("lsof -p #{pid}")
|
37
|
+
warn "#{pid} failed to die, waiting for user to inspect"
|
38
|
+
sleep
|
39
|
+
end
|
40
|
+
raise
|
41
|
+
end
|
42
|
+
|
43
|
+
# only use for newly bound sockets
|
44
|
+
def get_tcp_client(host, port, tries = 500)
|
45
|
+
begin
|
46
|
+
c = TCPSocket.new(host, port)
|
47
|
+
return c
|
48
|
+
rescue Errno::ECONNREFUSED
|
49
|
+
raise if tries < 0
|
50
|
+
tries -= 1
|
51
|
+
end while sleep(0.01)
|
52
|
+
end
|
53
|
+
|
54
|
+
def server_helper_teardown
|
55
|
+
@srv.close unless @srv.closed?
|
56
|
+
@ru.close! if @ru
|
57
|
+
check_err
|
58
|
+
end
|
59
|
+
|
60
|
+
def server_helper_setup
|
61
|
+
@srv = TCPServer.new(ENV["TEST_HOST"] || "127.0.0.1", 0)
|
62
|
+
@err = tmpfile(%w(srv .err))
|
63
|
+
@ru = nil
|
64
|
+
end
|
65
|
+
end
|