yahns 1.1.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a9749686719fc4ebab52f18c57c3c3b9cf00ab8
4
- data.tar.gz: 82df4e6686e4ee97cad0462fb1d0c809d19f9a10
3
+ metadata.gz: 7b73379a257ad0e09cd00b6d1b1b316ca6913992
4
+ data.tar.gz: 1e5203cb1d70720bdab3f9624b9766e3d49a9d51
5
5
  SHA512:
6
- metadata.gz: f5f385fabee64576cadcea538d77065ec625e50ee6c9c40ebba09d662995f6ad9c1740a422ee219078c1f1a7c7198120927625d08632b5d16d6f96dfd8039fb3
7
- data.tar.gz: 12080fba5d268e326f648da62708ec2927063b2de470a3590be6d55d21968ea5150e94d8b18165dba22ca9388fbb535c9f5180039926a75df372d4f602d60c76
6
+ metadata.gz: 1c7a49cdbe694c1a27bd3f26d6db10b4b1f8ac11cf9307036957ba9df60aab266ef621cf515dcfd9eb592675648a0864b9949ba9b11ffccf083627a392353879
7
+ data.tar.gz: 2250ca7bf3fdfffbbb42cbb332cbe08bca48aa4c0e6066d3414e3248a2e666094ba2da07ff27312021558b67e45de59bdb10428fcd089c72bbfaeabd9f3488da
data/GIT-VERSION-GEN CHANGED
@@ -4,7 +4,7 @@
4
4
  CONSTANT = "Yahns::VERSION"
5
5
  RVF = "lib/yahns/version.rb"
6
6
  GVF = "GIT-VERSION-FILE"
7
- DEF_VER = "v1.1.0"
7
+ DEF_VER = "v1.2.0"
8
8
  vn = DEF_VER
9
9
 
10
10
  # First see if there is a version file (included in release tarballs),
data/INSTALL CHANGED
@@ -6,3 +6,10 @@ No tarballs are currently provided.
6
6
 
7
7
  You may also install yahns from the git source, see the HACKING document for
8
8
  more details.
9
+
10
+ Debian GNU/kFreeBSD:
11
+
12
+ For now, you will need to define SENDFILE_BROKEN=1 in the env before
13
+ running yahns, and also install the "io-extra" RubyGem (tested on 1.2.7)
14
+
15
+ gem install -v 1.2.7 io-extra
data/README CHANGED
@@ -44,11 +44,11 @@ Supported Platforms
44
44
 
45
45
  yahns is developed primarily for modern GNU/Linux systems.
46
46
 
47
- We may support kqueue for FreeBSD/OpenBSD/NetBSD if there is significant
48
- interest. Non-Free systems/dependencies will never be supported
47
+ We have experimental support kqueue on FreeBSD (and possibly OpenBSD and
48
+ NetBSD). Non-Free systems/dependencies will never be supported.
49
49
 
50
50
  Supported Ruby implementations:
51
- * (Matz) Ruby 1.9.3 and later (we develop against trunk)
51
+ * (Matz) Ruby 1.9.3 and later (we develop (and host our website) on trunk)
52
52
  * Rubinius 2.0 or later (best-effort)
53
53
 
54
54
  Contact
data/lib/yahns.rb CHANGED
@@ -30,7 +30,7 @@ module Yahns # :nodoc:
30
30
  #
31
31
  # Yahns::START[0] = "/home/bofh/2.0.0/bin/yahns"
32
32
  START = {
33
- :argv => ARGV.map { |arg| arg.dup },
33
+ :argv => ARGV.map(&:dup),
34
34
  0 => $0.dup,
35
35
  }
36
36
 
@@ -57,7 +57,7 @@ end
57
57
 
58
58
  # FIXME: require lazily
59
59
  require_relative 'yahns/log'
60
- require_relative 'yahns/queue_epoll'
60
+ require_relative 'yahns/queue'
61
61
  require_relative 'yahns/stream_input'
62
62
  require_relative 'yahns/tee_input'
63
63
  require_relative 'yahns/queue_egg'
@@ -0,0 +1,82 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2014, Eric Wong <normalperson@yhbt.net> and all contributors
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ #
5
+ # This is the dangerous, low-level kqueue interface for sleepy_penguin
6
+ # It is safe as long as you're aware of all potential concurrency
7
+ # issues given multithreading, GC, and kqueue itself.
8
+ class Yahns::Queue < SleepyPenguin::Kqueue::IO # :nodoc:
9
+ include SleepyPenguin
10
+ attr_accessor :fdmap # Yahns::Fdmap
11
+
12
+ # public
13
+ QEV_QUIT = nil # Level Trigger for QueueQuitter
14
+ QEV_RD = EvFilt::READ
15
+ QEV_WR = EvFilt::WRITE
16
+
17
+ ADD_ONESHOT = Ev::ADD | Ev::ONESHOT # private
18
+
19
+ def self.new
20
+ rv = super
21
+ rv.close_on_exec = true
22
+ rv
23
+ end
24
+
25
+ # for HTTP and HTTPS servers, we rely on the io writing to us, first
26
+ # flags: QEV_RD/QEV_WR (usually QEV_RD)
27
+ def queue_add(io, flags)
28
+ # order is very important here, this thread cannot do anything with
29
+ # io once we've issued epoll_ctl() because another thread may use it
30
+ @fdmap.add(io)
31
+ fflags = ADD_ONESHOT
32
+ if flags == QEV_QUIT
33
+ fflags = Ev::ADD
34
+ flags = QEV_WR
35
+ end
36
+ kevent(Kevent[io.fileno, flags, fflags, 0, 0, io])
37
+ end
38
+
39
+ def thr_init
40
+ Thread.current[:yahns_rbuf] = ""
41
+ Thread.current[:yahns_fdmap] = @fdmap
42
+ end
43
+
44
+ def queue_del(io)
45
+ # do not bother with kevent EV_DELETE, it may be tricky to get right,
46
+ # we only did it in epoll since Eric knows the epoll internals well.
47
+ @fdmap.forget(io)
48
+ end
49
+
50
+ # returns an array of infinitely running threads
51
+ def worker_thread(logger, max_events)
52
+ Thread.new do
53
+ thr_init
54
+ begin
55
+ kevent(nil, max_events) do |_,_,_,_,_,io| # don't care for flags for now
56
+ # Note: we absolutely must not do anything with io after
57
+ # we've called epoll_ctl on it, io is exclusive to this
58
+ # thread only until epoll_ctl is called on it.
59
+ case rv = io.yahns_step
60
+ when :wait_readable
61
+ kevent(Kevent[io.fileno, QEV_RD, ADD_ONESHOT, 0, 0, io])
62
+ when :wait_writable
63
+ kevent(Kevent[io.fileno, QEV_WR, ADD_ONESHOT, 0, 0, io])
64
+ when :ignore # only used by rack.hijack
65
+ # we cannot EV_DELETE after hijacking, the hijacker
66
+ # may have already closed it Likewise, io.fileno is not
67
+ # expected to work, so we had to erase it from fdmap before hijack
68
+ when nil, :close
69
+ # this must be the ONLY place where we call IO#close on
70
+ # things that got inside the queue
71
+ @fdmap.sync_close(io)
72
+ else
73
+ raise "BUG: #{io.inspect}#yahns_step returned: #{rv.inspect}"
74
+ end
75
+ end
76
+ rescue => e
77
+ break if closed? # can still happen due to shutdown_timeout
78
+ Yahns::Log.exception(logger, 'queue loop', e)
79
+ end while true
80
+ end
81
+ end
82
+ end
@@ -5,9 +5,8 @@
5
5
  class Yahns::QueueQuitter # :nodoc:
6
6
  attr_reader :to_io
7
7
  def initialize
8
- reader, @to_io = IO.pipe
8
+ @reader, @to_io = IO.pipe
9
9
  @to_io.close_on_exec = true
10
- reader.close
11
10
  end
12
11
 
13
12
  def yahns_step
@@ -19,6 +18,7 @@ class Yahns::QueueQuitter # :nodoc:
19
18
  end
20
19
 
21
20
  def close
21
+ @reader.close
22
22
  @to_io.close
23
23
  end
24
24
  end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2009-2014, Eric Wong <normalperson@yhbt.net> et. al.
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ require 'io/extra' # gem install io-extra
5
+
6
+ module Yahns::SendfileCompat
7
+ def trysendfile(io, offset, count)
8
+ return 0 if count == 0
9
+ count = 0x4000 if count > 0x4000
10
+ str = IO.pread(io.fileno, count, offset)
11
+ if count > str.bytesize
12
+ raise EOFError, "end of file reached"
13
+ end
14
+ n = 0
15
+ case rv = kgio_trywrite(str)
16
+ when String # partial write, keep trying
17
+ n += (str.bytesize - rv.bytesize)
18
+ str = rv
19
+ when :wait_writable, :wait_readable
20
+ return n > 0 ? n : rv
21
+ when nil
22
+ return n + str.bytesize # yay!
23
+ end while true
24
+ end
25
+ end
26
+
27
+ class IO
28
+ include Yahns::SendfileCompat
29
+ end
@@ -8,8 +8,8 @@ class Yahns::Sigevent # :nodoc:
8
8
  @to_io.close_on_exec = @wr.close_on_exec = true
9
9
  end
10
10
 
11
- def kgio_wait_readable
12
- @to_io.kgio_wait_readable
11
+ def kgio_wait_readable(*args)
12
+ @to_io.kgio_wait_readable(*args)
13
13
  end
14
14
 
15
15
  def sev_signal
data/lib/yahns/wbuf.rb CHANGED
@@ -30,6 +30,14 @@ require_relative 'wbuf_common'
30
30
  class Yahns::Wbuf # :nodoc:
31
31
  include Yahns::WbufCommon
32
32
 
33
+ # TODO: Figure out why this hack is needed to pass output buffering tests.
34
+ # It could be a bug in our code, Ruby, the sendfile gem, or FreeBSD itself.
35
+ # Tested on FreeBSD fbsd 9.2-RELEASE FreeBSD 9.2-RELEASE #0 r255898
36
+ # We are able to use bypass mode on Linux to reduce buffering in some
37
+ # cases. Without bypass mode, we must always finish writing the entire
38
+ # response completely before sending more data to the client.
39
+ bypass_ok = RUBY_PLATFORM =~ /linux/
40
+
33
41
  def initialize(body, persist, tmpdir)
34
42
  @tmpio = Yahns::TmpIO.new(tmpdir)
35
43
  @sf_offset = @sf_count = 0
@@ -67,7 +75,12 @@ class Yahns::Wbuf # :nodoc:
67
75
  @tmpio.rewind
68
76
  @bypass = true
69
77
  nil
70
- end
78
+ end if bypass_ok
79
+
80
+ def wbuf_write(client, buf)
81
+ @sf_count += @tmpio.write(buf)
82
+ :wait_writable
83
+ end unless bypass_ok
71
84
 
72
85
  # called by last wbuf_flush
73
86
  def wbuf_close(client)
@@ -1,7 +1,12 @@
1
1
  # -*- encoding: binary -*-
2
2
  # Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
3
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
- require 'sendfile'
4
+ if ENV["SENDFILE_BROKEN"]
5
+ require_relative 'sendfile_compat'
6
+ else
7
+ require 'sendfile'
8
+ end
9
+
5
10
  module Yahns::WbufCommon # :nodoc:
6
11
  # returns nil on success, :wait_*able when blocked
7
12
  # currently, we rely on each thread having exclusive access to the
@@ -25,7 +30,8 @@ module Yahns::WbufCommon # :nodoc:
25
30
  raise "BUG: rv=#{rv.inspect} " \
26
31
  "on tmpio=#{@tmpio.inspect} " \
27
32
  "sf_offset=#@sf_offset sf_count=#@sf_count"
28
- end while true
33
+ end while @sf_count > 0
34
+ wbuf_close(client)
29
35
  end
30
36
 
31
37
  def wbuf_close_common(client)
data/test/helper.rb CHANGED
@@ -36,6 +36,12 @@ if ENV["COVERAGE"]
36
36
  # filter out stuff that's not in our project
37
37
  COVMATCH =~ filename or next
38
38
 
39
+ # For compatibility with https://bugs.ruby-lang.org/issues/9508
40
+ # TODO: support those features if that gets merged into mainline
41
+ unless Array === counts
42
+ counts = counts[:lines]
43
+ end
44
+
39
45
  merge = prev[filename] || []
40
46
  merge = merge
41
47
  counts.each_with_index do |count, i|
@@ -9,12 +9,18 @@ class TestBufferTmpdir < Testcase
9
9
  attr_reader :ino, :tmpdir
10
10
 
11
11
  def setup
12
- @ino = SleepyPenguin::Inotify.new(:CLOEXEC)
12
+ @ino = nil
13
+ begin
14
+ @ino = SleepyPenguin::Inotify.new(:CLOEXEC)
15
+ rescue
16
+ skip "test needs inotify"
17
+ end
13
18
  @tmpdir = Dir.mktmpdir
14
19
  server_helper_setup
15
20
  end
16
21
 
17
22
  def teardown
23
+ return unless @ino
18
24
  server_helper_teardown
19
25
  @ino.close
20
26
  FileUtils.rm_rf @tmpdir
@@ -100,4 +106,4 @@ class TestBufferTmpdir < Testcase
100
106
  c.close if c
101
107
  quit_wait(pid)
102
108
  end
103
- end if SleepyPenguin.const_defined?(:Inotify)
109
+ end
@@ -149,9 +149,17 @@ class TestClientExpire < Testcase
149
149
  Process.waitpid2(_pid)
150
150
  end
151
151
  end
152
+
153
+ # this seems to be needed in Debian GNU/kFreeBSD
154
+ linux = !!(RUBY_PLATFORM =~ /linux/)
155
+ sleep(1) unless linux
156
+
152
157
  [ f, s ].each do |io|
153
158
  assert_raises(Errno::EPIPE,Errno::ECONNRESET) do
154
- req.each_byte { |b| io.write(b.chr) }
159
+ req.each_byte do |b|
160
+ io.write(b.chr)
161
+ sleep(0.01) unless linux
162
+ end
155
163
  end
156
164
  io.close
157
165
  end
@@ -10,6 +10,8 @@ class TestMtAccept < Testcase
10
10
  alias teardown server_helper_teardown
11
11
 
12
12
  def test_mt_accept
13
+ skip "Linux kernel required" unless RUBY_PLATFORM =~ /linux/
14
+ skip "/proc not mounted" unless File.directory?("/proc")
13
15
  err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
14
16
  opts = { threads: 1 }
15
17
  cfg.instance_eval do
@@ -45,4 +47,4 @@ class TestMtAccept < Testcase
45
47
  ensure
46
48
  quit_wait(pid)
47
49
  end
48
- end if RUBY_PLATFORM =~ /linux/ && File.directory?("/proc")
50
+ end
data/test/test_server.rb CHANGED
@@ -178,11 +178,14 @@ class TestServer < Testcase
178
178
  end
179
179
 
180
180
  def test_check_client_connection
181
+ tmpdir = Dir.mktmpdir
182
+ sock = "#{tmpdir}/sock"
183
+ unix_srv = UNIXServer.new(sock)
184
+ unix_srv.close_on_exec = true
181
185
  msgs = %w(ZZ zz)
182
186
  err = @err
183
187
  cfg = Yahns::Config.new
184
188
  bpipe = cloexec_pipe
185
- host, port = @srv.addr[3], @srv.addr[1]
186
189
  cfg.instance_eval do
187
190
  ru = lambda { |e|
188
191
  case e['PATH_INFO']
@@ -205,7 +208,7 @@ class TestServer < Testcase
205
208
  }
206
209
  GTL.synchronize {
207
210
  app(:rack, ru) {
208
- listen "#{host}:#{port}"
211
+ listen sock
209
212
  check_client_connection true
210
213
  # needed to avoid concurrency with check_client_connection
211
214
  queue { worker_threads 1 }
@@ -223,12 +226,13 @@ class TestServer < Testcase
223
226
 
224
227
  pid = fork do
225
228
  bpipe[1].close
226
- ENV["YAHNS_FD"] = @srv.fileno.to_s
229
+ ENV["YAHNS_FD"] = unix_srv.fileno.to_s
227
230
  srv.start.join
228
231
  end
229
232
  bpipe[0].close
230
- a = get_tcp_client(host, port)
231
- b = get_tcp_client(host, port)
233
+ a = UNIXSocket.new(sock)
234
+ b = UNIXSocket.new(sock)
235
+ b.close_on_exec = a.close_on_exec = true
232
236
  a.write("GET /sleep HTTP/1.0\r\n\r\n")
233
237
  r = IO.select([a], nil, nil, 4)
234
238
  assert r, "nothing ready"
@@ -250,10 +254,19 @@ class TestServer < Testcase
250
254
  assert_equal msgs.join, buf.split(/\r\n\r\n/)[1]
251
255
 
252
256
  # do things still work?
253
- run_client(host, port) { |res| assert_equal "HI", res.body }
257
+ c = UNIXSocket.new(sock)
258
+ c.write "GET /\r\n\r\n"
259
+ assert_equal "HI", c.read
260
+ c.close
254
261
  a.close
262
+ rescue => e
263
+ warn e.class
264
+ warn e.message
265
+ warn e.backtrace.join("\n")
255
266
  ensure
267
+ unix_srv.close
256
268
  quit_wait(pid)
269
+ FileUtils.rm_rf(tmpdir)
257
270
  end
258
271
 
259
272
  def test_mp
data/test/test_wbuf.rb CHANGED
@@ -81,8 +81,8 @@ class TestWbuf < Testcase
81
81
  assert_equal b, IO.select([b], nil, nil, 5)[0][0]
82
82
  b.read(nr - 2) if nr > 2
83
83
  assert_equal b, IO.select([b], nil, nil, 5)[0][0]
84
- assert_equal "HI", b.read(2)
85
- assert_equal false, wbuf.wbuf_flush(a)
84
+ assert_equal "HI", b.read(2), "read the end of the response"
85
+ assert_equal true, wbuf.wbuf_flush(a)
86
86
  ensure
87
87
  a.close
88
88
  b.close
data/yahns.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
12
12
  s.files = manifest
13
13
  s.add_dependency(%q<kgio>, '~> 2.9')
14
14
  s.add_dependency(%q<sleepy_penguin>, '~> 3.2')
15
- s.add_dependency(%q<sendfile>, '~> 1.2.1')
15
+ s.add_dependency(%q<kgio-sendfile>, '~> 1.2')
16
16
  s.add_dependency(%q<unicorn>, '~> 4.6', '>= 4.6.3')
17
17
 
18
18
  # minitest is standard in Ruby 2.0, 4.3 is packaged with Ruby 2.0.0,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yahns
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yahns hackers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-04 00:00:00.000000000 Z
11
+ date: 2014-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: kgio
@@ -39,19 +39,19 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.2'
41
41
  - !ruby/object:Gem::Dependency
42
- name: sendfile
42
+ name: kgio-sendfile
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.2.1
47
+ version: '1.2'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.2.1
54
+ version: '1.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: unicorn
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -152,10 +152,12 @@ files:
152
152
  - lib/yahns/queue.rb
153
153
  - lib/yahns/queue_egg.rb
154
154
  - lib/yahns/queue_epoll.rb
155
+ - lib/yahns/queue_kqueue.rb
155
156
  - lib/yahns/queue_quitter.rb
156
157
  - lib/yahns/queue_quitter_pipe.rb
157
158
  - lib/yahns/rack.rb
158
159
  - lib/yahns/rackup_handler.rb
160
+ - lib/yahns/sendfile_compat.rb
159
161
  - lib/yahns/server.rb
160
162
  - lib/yahns/server_mp.rb
161
163
  - lib/yahns/sigevent.rb
@@ -223,7 +225,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
225
  version: '0'
224
226
  requirements: []
225
227
  rubyforge_project:
226
- rubygems_version: 2.2.0
228
+ rubygems_version: 2.2.2
227
229
  signing_key:
228
230
  specification_version: 4
229
231
  summary: sleepy, multi-threaded, non-blocking application server