No need for a wrapper, since we've been Ruby 2.0+ for a while, now. --- test/helper.rb | 4 ---- test/test_bin.rb | 2 +- test/test_server.rb | 2 +- test/test_wbuf.rb | 6 +++--- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/test/helper.rb b/test/helper.rb index f9370a4..c4403dd 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -124,10 +124,6 @@ def nread end end if ! IO.method_defined?(:nread) && RUBY_PLATFORM =~ /linux/ -def cloexec_pipe - IO.pipe -end - def require_exec(cmd) ENV["PATH"].split(/:/).each do |path| return true if File.executable?("#{path}/#{cmd}") diff --git a/test/test_bin.rb b/test/test_bin.rb index fc85992..e7c55ad 100644 --- a/test/test_bin.rb +++ b/test/test_bin.rb @@ -76,7 +76,7 @@ def bin_daemon(worker, inherit) cfg.puts " listen ENV['YAHNS_TEST_LISTEN']" cfg.puts "end" @cmd.concat(%W(-D -c #{cfg.path})) - addr = cloexec_pipe + addr = IO.pipe pid = xfork do opts = { close_others: true } addr[0].close diff --git a/test/test_server.rb b/test/test_server.rb index 75e1857..29803fb 100644 --- a/test/test_server.rb +++ b/test/test_server.rb @@ -185,7 +185,7 @@ def test_check_client_connection msgs = %w(ZZ zz) err = @err cfg = Yahns::Config.new - bpipe = cloexec_pipe + bpipe = IO.pipe cfg.instance_eval do ru = lambda { |e| case e['PATH_INFO'] diff --git a/test/test_wbuf.rb b/test/test_wbuf.rb index 0135958..101f6c5 100644 --- a/test/test_wbuf.rb +++ b/test/test_wbuf.rb @@ -34,7 +34,7 @@ def test_wbuf assert_equal "HIHI", b.read(4) nr.times { wbuf.wbuf_write(a, buf) } assert_equal :wait_writable, wbuf.wbuf_flush(a) - done = cloexec_pipe + done = IO.pipe thr = Thread.new do rv = [] until rv[-1] == persist @@ -109,7 +109,7 @@ def test_wbuf_blocked end def test_wbuf_flush_close - pipe = cloexec_pipe + pipe = IO.pipe persist = true wbuf = Yahns::Wbuf.new(pipe[0], persist) refute wbuf.respond_to?(:close) # we don't want this for HttpResponse body @@ -126,7 +126,7 @@ def test_wbuf_flush_close assert_equal thr, thr.join(5) assert_equal :wait_writable, rv - done = cloexec_pipe + done = IO.pipe thr = Thread.new do rv = [] until rv[-1] == persist
It is causing _blocked_zombie to fail on rtype=11 due to the addition of Content-Length making the client persistent when we didn't actually want it to be (for the test). --- test/test_extras_exec_cgi.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_extras_exec_cgi.rb b/test/test_extras_exec_cgi.rb index 426409d..4fa928d 100644 --- a/test/test_extras_exec_cgi.rb +++ b/test/test_extras_exec_cgi.rb @@ -136,7 +136,7 @@ def _blocked_zombie(block_on, rtype) Yahns::HttpClient.__send__(:include, TrywriteBlocked) require './extras/exec_cgi' cfg.instance_eval do - stack = Rack::ContentLength.new(Rack::Chunked.new(ExecCgi.new(RUNME))) + stack = Rack::Chunked.new(ExecCgi.new(RUNME)) app(:rack, stack) { listen "#{host}:#{port}" } stderr_path err.path worker_processes 1 @@ -170,6 +170,7 @@ def _blocked_zombie(block_on, rtype) assert_match %r{\A\d+\n\z}, body exec_pid = body.to_i poke_until_dead exec_pid + # lack of Content-Length should trigger EOF here: assert_raises(EOFError) { c.readpartial(666) } else raise "BUG in test, bad rtype"
Rack::ContentLength now unconditionally captures bodies and doesn't dup the data yield by body#each. Thus we can't reuse buffers anymore. --- extras/exec_cgi.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extras/exec_cgi.rb b/extras/exec_cgi.rb index 8a1939d..a04087d 100644 --- a/extras/exec_cgi.rb +++ b/extras/exec_cgi.rb @@ -34,14 +34,15 @@ def initialize(rd) def each buf = @body_tip yield buf unless buf.empty? + buf = @body_tip = nil - case tmp = @rd.read_nonblock(8192, buf, exception: false) + case tmp = @rd.read_nonblock(8192, exception: false) when :wait_readable @rd.wait_readable when nil break else # String - yield tmp + yield tmp.freeze end while true self ensure @@ -117,7 +118,7 @@ def call(env) tmp.clear end head, body = head.split(/\r?\n\r?\n/, 2) - io.body_tip = body + io.body_tip = body.freeze env["HTTP_VERSION"] ||= "HTTP/1.0" # stop Rack::Chunked for HTTP/0.9
Rack::ContentLength was causing problems with ExecCGI in the extras/ problem. Rack 3 is another can of worms I'll still have to deal with :< Eric Wong (2): extras/exec_cgi: fix for newer Rack::ContentLength test/test_extras_exec_cgi: drop Rack::ContentLength extras/exec_cgi.rb | 7 ++++--- test/test_extras_exec_cgi.rb | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-)
Needed for Ruby 3.1, and likely 3.2, as well... --- test/test_ssl.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_ssl.rb b/test/test_ssl.rb index 7909094..9442ec8 100644 --- a/test/test_ssl.rb +++ b/test/test_ssl.rb @@ -178,7 +178,7 @@ def test_ssl_hijack s.any_old_invalid_test_method s.puts "FAIL" rescue => e - s.puts "#{e.class}: #{e.message}" + s.puts("#{e.class}: #{e.message}".split("\n")[0]) end when nil s.close
This is the size used by Ruby internally for IO.copy_stream. 8192 seems too small nowadays with the higher cost of syscalls. --- lib/yahns/http_client.rb | 2 +- lib/yahns/proxy_http_response.rb | 4 ++-- lib/yahns/req_res.rb | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/yahns/http_client.rb b/lib/yahns/http_client.rb index b6b6035..826eb8d 100644 --- a/lib/yahns/http_client.rb +++ b/lib/yahns/http_client.rb @@ -315,7 +315,7 @@ def app_hijacked?(env, res) end def do_pread(io, count, offset) - count = 0x4000 if count > 0x4000 + count = 16384 if count > 16384 buf = Thread.current[:yahns_sfbuf] ||= ''.dup if io.respond_to?(:pread) io.pread(count, offset, buf) diff --git a/lib/yahns/proxy_http_response.rb b/lib/yahns/proxy_http_response.rb index d4a3dda..db9c4b7 100644 --- a/lib/yahns/proxy_http_response.rb +++ b/lib/yahns/proxy_http_response.rb @@ -147,7 +147,7 @@ def proxy_res_headers(res, req_res) end def read_len(len) - max = 0x2000 + max = 16384 max = len if len && len < max max end @@ -206,7 +206,7 @@ def proxy_read_trailers(kcar, req_res) wbuf = req_res.resbuf until kcar.trailers(tlr, chunk) - case rv = req_res.kgio_tryread(0x2000, rbuf) + case rv = req_res.kgio_tryread(16384, rbuf) when String chunk << rv when :wait_readable diff --git a/lib/yahns/req_res.rb b/lib/yahns/req_res.rb index 4ad8e5c..283fea8 100644 --- a/lib/yahns/req_res.rb +++ b/lib/yahns/req_res.rb @@ -29,7 +29,7 @@ def yahns_step # yahns event loop entry point case resbuf = @resbuf # where are we at the response? when nil # common case, catch the response header in a single read - case rv = kgio_tryread(0x2000, buf) + case rv = kgio_tryread(16384, buf) when String if res = req.headers(@hdr = [], rv) return c.proxy_response_start(res, rv, req, self) @@ -48,7 +48,7 @@ def yahns_step # yahns event loop entry point when String # continue reading trickled response headers from upstream - case rv = kgio_tryread(0x2000, buf) + case rv = kgio_tryread(16384, buf) when String then res = req.headers(@hdr, resbuf << rv) and break when :wait_readable then return rv when nil @@ -114,7 +114,7 @@ def send_req_body(req) # @rrstate == [ (str|vec), rack.input, chunked? ] # we should not be waiting on a slow network resource when reading # input. However, some weird configs may disable this on LANs # and we may wait indefinitely on input.read here... - while input.read(0x2000, rbuf) + while input.read(16384, rbuf) if chunked buf[0] = "#{rbuf.size.to_s(16)}\r\n".freeze buf[1] = rbuf
We don't want to overread in case a broken HTTP backend sends us excessive data. --- lib/yahns/proxy_http_response.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/yahns/proxy_http_response.rb b/lib/yahns/proxy_http_response.rb index 7df2834..d4a3dda 100644 --- a/lib/yahns/proxy_http_response.rb +++ b/lib/yahns/proxy_http_response.rb @@ -146,6 +146,12 @@ def proxy_res_headers(res, req_res) have_body end + def read_len(len) + max = 0x2000 + max = len if len && len < max + max + end + def proxy_read_body(tip, kcar, req_res) chunk = ''.dup if kcar.chunked? len = kcar.body_bytes_left @@ -153,7 +159,7 @@ def proxy_read_body(tip, kcar, req_res) alive = req_res.alive wbuf = req_res.resbuf - case tmp = tip.shift || req_res.kgio_tryread(0x2000, rbuf) + case tmp = tip.shift || req_res.kgio_tryread(read_len(len), rbuf) when String if len kcar.body_bytes_left -= tmp.size # progress for body_eof? => true
Dreading what breakage Rack 3.0 and Ruby 3.2 brings :< Eric Wong (4): test_buffer_tmpdir: drop fragile assertions http_response: clamp body read size standardize on 16384-byte reads test/test_ssl: workaround multi-line exception messages lib/yahns/http_client.rb | 2 +- lib/yahns/proxy_http_response.rb | 10 ++++++++-- lib/yahns/req_res.rb | 6 +++--- test/test_buffer_tmpdir.rb | 2 -- test/test_ssl.rb | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-)
When tweaking buffer sizes, another IN_CREATE event can happen soon after the delete. --- test/test_buffer_tmpdir.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_buffer_tmpdir.rb b/test/test_buffer_tmpdir.rb index bac8017..39029ed 100644 --- a/test/test_buffer_tmpdir.rb +++ b/test/test_buffer_tmpdir.rb @@ -67,7 +67,6 @@ def test_output_buffer_tmpdir event = @ino.take assert_equal [:DELETE], event.events assert_equal name, event.name - refute File.exist?("#@tmpdir/#{name}") end ensure c.close if c @@ -101,7 +100,6 @@ def input_buffer(btype) event = ino.take assert_equal [:DELETE], event.events assert_equal name, event.name - refute File.exist?("#{tmpdir}/#{name}") end ensure c.close if c
Some minor updates and fixes, mainly to workaround for a (now-fixed upstream) Linux kernel bug. I doubt anybody uses this, yet, or ever will at this point... Changes: 8 changes since 1.17.0 (2019-04-22): worker: workaround old F_SETPIPE_SZ bug doc: favor File.read over IO.read to ease review proxy_pass: document as a public API doc: include Yahns/ directory on website extras/try_gzip_static: set "Vary: Accept-Encoding" on gzip do not sleep if signals are pending server: workaround Linux v5.5..v5.13 epoll bug gemspec: allow unicorn 6.x * homepage: https://yhbt.net/yahns/ * git clone https://yhbt.net/yahns.git *zzzzzzzz*
epoll_wait() wakeups from QueueQuitter got lost during graceful shutdown since there's multiple worker threads operating off the same FD. Workaround the problem by re-arming the eventfd for every worker thread reaped. Link: https://yhbt.net/lore/lkml/20210405231025.33829-1-dave@stgolabs.net/ --- lib/yahns/queue_epoll.rb | 4 ++++ lib/yahns/server.rb | 17 ++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/yahns/queue_epoll.rb b/lib/yahns/queue_epoll.rb index 9e4271a..a198fbf 100644 --- a/lib/yahns/queue_epoll.rb +++ b/lib/yahns/queue_epoll.rb @@ -32,6 +32,10 @@ def queue_mod(io, flags) epoll_ctl(Epoll::CTL_MOD, io, flags) end + def queue_del(io) + epoll_ctl(Epoll::CTL_DEL, io, 0) + end + def thr_init Thread.current[:yahns_rbuf] = ''.dup Thread.current[:yahns_fdmap] = @fdmap diff --git a/lib/yahns/server.rb b/lib/yahns/server.rb index 208b5ee..74eeb7e 100644 --- a/lib/yahns/server.rb +++ b/lib/yahns/server.rb @@ -438,25 +438,28 @@ def quit_enter(alive) # This just injects the QueueQuitter object which acts like a # monkey wrench thrown into a perfectly good engine :) def quit_finish - quitter = Yahns::QueueQuitter.new + # we must not let quitters get GC-ed if we have any worker threads leftover + @quitter = Yahns::QueueQuitter.new # throw the monkey wrench into the worker threads - @queues.each { |q| q.queue_add(quitter, Yahns::Queue::QEV_QUIT) } + @queues.each { |q| q.queue_add(@quitter, Yahns::Queue::QEV_QUIT) } # watch the monkey wrench destroy all the threads! # Ugh, this may fail if we have dedicated threads trickling # response bodies out (e.g. "tail -F") Oh well, have a timeout begin @wthr.delete_if { |t| t.join(0.01) } + # Workaround Linux 5.5+ bug (fixed in 5.13+) + # https://yhbt.net/lore/lkml/20210405231025.33829-1-dave@stgolabs.net/ + @wthr[0] && @queues[0].respond_to?(:queue_del) and @queues.each do |q| + q.queue_del(@quitter) + q.queue_add(@quitter, Yahns::Queue::QEV_QUIT) + end end while @wthr[0] && Yahns.now <= @shutdown_expire # cleanup, our job is done @queues.each(&:close).clear - - # we must not let quitter get GC-ed if we have any worker threads leftover - @quitter = quitter - - quitter.close + @quitter.close # keep object around in case @wthr isn't empty rescue => e Yahns::Log.exception(@logger, "quit finish", e) ensure
The changes in unicorn 6.x don't affect us --- yahns.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yahns.gemspec b/yahns.gemspec index 5752ad8..3808061 100644 --- a/yahns.gemspec +++ b/yahns.gemspec @@ -15,7 +15,7 @@ s.add_dependency(%q<kgio>, '~> 2.9') s.add_dependency(%q<sleepy_penguin>, '~> 3.2') - s.add_dependency(%q<unicorn>, '>= 4.6.3', '< 6.0') + s.add_dependency(%q<unicorn>, '>= 4.6.3', '< 7.0') # s.add_dependency(%q<kgio-sendfile>, '~> 1.2') # optional # minitest is standard in Ruby 2.0, 4.3 is packaged with Ruby 2.0.0,
It's been a while since I've run the test suite, turns out there was a Linux kernel bug introduced and fixed which was causing shutdown timeouts. A couple of other minor things, too... Eric Wong (3): do not sleep if signals are pending server: workaround Linux v5.5..v5.13 epoll bug gemspec: allow unicorn 6.x lib/yahns/queue_epoll.rb | 4 ++++ lib/yahns/server.rb | 20 ++++++++++++-------- lib/yahns/server_mp.rb | 3 ++- yahns.gemspec | 2 +- 4 files changed, 19 insertions(+), 10 deletions(-)
This should prevent missed/delayed wakeups if repeatedly kill(2)-ed. --- lib/yahns/server.rb | 3 ++- lib/yahns/server_mp.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/yahns/server.rb b/lib/yahns/server.rb index d13c57e..208b5ee 100644 --- a/lib/yahns/server.rb +++ b/lib/yahns/server.rb @@ -476,7 +476,8 @@ def reap_reexec end def sp_sig_handle(alive) - @sev.wait_readable(alive ? nil : 0.01) + tout = alive ? (@sig_queue.empty? ? nil : 0) : 0.01 + @sev.wait_readable(tout) @sev.yahns_step case sig = @sig_queue.shift when :QUIT, :TERM, :INT diff --git a/lib/yahns/server_mp.rb b/lib/yahns/server_mp.rb index 5467674..d56d1ed 100644 --- a/lib/yahns/server_mp.rb +++ b/lib/yahns/server_mp.rb @@ -157,7 +157,8 @@ def run_mp_worker(worker) def mp_sig_handle(watch, alive) # not performance critical watch.delete_if { |io| io.to_io.closed? } - if r = select(watch, nil, nil, alive ? nil : 0.1) + tout = alive ? (@sig_queue.empty? ? nil : 0) : 0.01 + if r = select(watch, nil, nil, tout) r[0].each(&:yahns_step) end case @sig_queue.shift
Some rare clients may not support gzip. So ensure cache layers don't inadvertantly serve cached content to them. --- extras/try_gzip_static.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extras/try_gzip_static.rb b/extras/try_gzip_static.rb index 4dd435a..a2dd155 100644 --- a/extras/try_gzip_static.rb +++ b/extras/try_gzip_static.rb @@ -79,7 +79,10 @@ def fheader(env, path, st, gz_st = nil, len = nil) "Accept-Ranges" => "bytes", } h["Cache-Control"] = "no-transform" unless mime =~ %r{\Atext\/} - h["Content-Encoding"] = "gzip" if gz_st + if gz_st + h["Content-Encoding"] = "gzip" + h["Vary"] = "Accept-Encoding" + end h end
Eric Wong <e@80x24.org> wrote:
> While a major design factor of this server was based
> load-balancing with blocking accept() in a dedicated thread,
> non-blocking accept() has become fairer with EPOLLEXCLUSIVE in
> Linux 4.5+.
Actually... not sure if it matters, even. EPOLLEXCLUSIVE still
seems worse off than a blocking accept4() thread. The process
which gets woken from epoll_wait can still be stuck dealing with
application processing or other I/O (the kernel has no way of
knowing that).
Blocking on accept4() is fairer in that regard since it
round-robins the processes.
On a related note, having a multi_accept parameter (like nginx)
should be beneficial to single-process deployments.
While a major design factor of this server was based load-balancing with blocking accept() in a dedicated thread, non-blocking accept() has become fairer with EPOLLEXCLUSIVE in Linux 4.5+. Additionally, most C10K servers rely on non-blocking listen sockets. For users of systemd (or similar) socket activation, it is now possible to run yahns alongside another C10K server while sharing the same socket with no change to the socket itself. This means yahns and another server can split traffic without extra load balancing. The main purpose of this change is to make zero downtime migrations, both to and from yahns possible without an external load balancer. This also allows users of small installations (zero or one worker process who don't need load balancing) to avoid needing a dedicated thread to accept connections. --- Needs more tests... Documentation/yahns_config.pod | 18 ++++++++++++++++++ lib/yahns.rb | 1 + lib/yahns/acceptor.rb | 23 +++++++++++++++++++++++ lib/yahns/config.rb | 2 +- lib/yahns/queue_epoll.rb | 10 ++++++++++ lib/yahns/queue_kqueue.rb | 7 +++++++ lib/yahns/server.rb | 24 +++++++++++++++++++++--- lib/yahns/socket_helper.rb | 1 + test/test_server.rb | 33 +++++++++++++++++++++++++++++++++ 9 files changed, 115 insertions(+), 4 deletions(-) diff --git a/Documentation/yahns_config.pod b/Documentation/yahns_config.pod index 08c2e27..359a4af 100644 --- a/Documentation/yahns_config.pod +++ b/Documentation/yahns_config.pod @@ -481,6 +481,24 @@ This has no effect on TCP listeners. Default: 0000 (world-read/writable) +=item nonblock: BOOLEAN + +Bind a non-blocking listen socket. Do not set this if you +are using multiple worker_processes as it can cause unfair +load balancing between workers. + +For small deployments with zero or one worker process, this can +save a few megabytes of memory by avoiding a dedicated listener +thread. + +This option does NOT apply to inherited sockets because chaing that +flag can break (non-yahns) servers it shares a listen socket with. +yahns 2.0+ supports inheriting either blocking or nonblocking +listeners so it can inherit (from systemd or similar) sockets meant +for other servers to ease migrations from/to yahns. + +Default: false + =back =item logger LOGGER diff --git a/lib/yahns.rb b/lib/yahns.rb index 4cf911e..766b7d3 100644 --- a/lib/yahns.rb +++ b/lib/yahns.rb @@ -6,6 +6,7 @@ require 'unicorn' # pulls in raindrops, kgio, fcntl, etc, stringio, and logger require 'sleepy_penguin' require 'io/wait' +require 'io/nonblock' # kill off some unicorn internals we don't need # we'll probably just make kcar into a server parser so we don't depend diff --git a/lib/yahns/acceptor.rb b/lib/yahns/acceptor.rb index 7340a1a..471166f 100644 --- a/lib/yahns/acceptor.rb +++ b/lib/yahns/acceptor.rb @@ -41,6 +41,29 @@ def ac_quit return __ac_quit_done? end + # only for non-blocking sockets + def yahns_listen_init(ctx) + @ctx = ctx # aka client_class + end + + # runs if and only if non-blocking (and ideally with EPOLLEXCLUSIVE) + def yahns_step + if c = kgio_tryaccept(@ctx, Kgio::SOCK_NONBLOCK | Kgio::SOCK_CLOEXEC) + c.yahns_init + + # it is not safe to touch client in this thread after this, + # a worker thread may grab client right away + @ctx.queue.queue_add(c, @ctx.superclass::QEV_FLAGS) + end + rescue Errno::EMFILE, Errno::ENFILE => e + logger.error("#{e.message}, consider raising open file limits") + @ctx.queue.fdmap.desperate_expire(5) + rescue => e + Yahns::Log.exception(logger, 'accept (yahns_step)', e) + ensure + return :ignore + end + def spawn_acceptor(nr, logger, client_class) @quit = false @thrs = nr.times.map do diff --git a/lib/yahns/config.rb b/lib/yahns/config.rb index 441d3f9..50cc735 100644 --- a/lib/yahns/config.rb +++ b/lib/yahns/config.rb @@ -204,7 +204,7 @@ def listen(address, options = {}) value = options[key] and _check_int(key, value, 1) end - [ :ipv6only, :reuseport ].each do |key| + [ :ipv6only, :reuseport, :nonblock ].each do |key| (value = options[key]).nil? and next [ true, false ].include?(value) or raise ArgumentError, "#{var}: not boolean: #{key}=#{value.inspect}" diff --git a/lib/yahns/queue_epoll.rb b/lib/yahns/queue_epoll.rb index 9e4271a..343edfb 100644 --- a/lib/yahns/queue_epoll.rb +++ b/lib/yahns/queue_epoll.rb @@ -28,6 +28,16 @@ def queue_add(io, flags) epoll_ctl(Epoll::CTL_ADD, io, flags) end + # non-blocking listeners are level-trigger, + def queue_add_acceptor(io) + epoll_ctl(Epoll::CTL_ADD, io, Epoll::IN | Epoll::EXCLUSIVE) + true + # caller won't warn + rescue Errno::EINVAL, NameError + epoll_ctl(Epoll::CTL_ADD, io, Epoll::IN) + false # caller warns + end + def queue_mod(io, flags) epoll_ctl(Epoll::CTL_MOD, io, flags) end diff --git a/lib/yahns/queue_kqueue.rb b/lib/yahns/queue_kqueue.rb index 3c4c51c..27c68fc 100644 --- a/lib/yahns/queue_kqueue.rb +++ b/lib/yahns/queue_kqueue.rb @@ -31,6 +31,13 @@ def queue_add(io, flags) kevent(Kevent[io.fileno, flags, fflags, 0, 0, io]) end + # non-blocking listeners are level-trigger + def queue_add_acceptor(io) + kevent(Kevent[io.fileno, EvFilt::READ, Ev::ADD, 0, 0, io]) + # no EPOLLEXCLUSIVE analogy, so assume thundering herds :< + false + end + def queue_mod(io, flags) kevent(Kevent[io.fileno, flags, ADD_ONESHOT, 0, 0, io]) end diff --git a/lib/yahns/server.rb b/lib/yahns/server.rb index d13c57e..071500e 100644 --- a/lib/yahns/server.rb +++ b/lib/yahns/server.rb @@ -328,7 +328,14 @@ def inherit_listeners! io = server_cast(io, opts) set_server_sockopt(io, opts) name = sock_name(io) - @logger.info "inherited addr=#{name} fd=#{io.fileno}" + nb = io.nonblock? + @logger.info "inherited addr=#{name} fd=#{io.fileno} nonblock=#{nb}" + case c = opts[:nonblock] + when false, true + @logger.warn "inherited nonblock=#{nb}, but nonblock=#{c} in config" + @logger.warn "ignoring config, leaving as nonblock=#{nb}" + @logger.warn 'we cannot safely change nonblock flag on shared sockets' + end @config.register_inherited(name) io end @@ -402,8 +409,19 @@ def fdmap_init ssl_ctx.setup end ctx_list << ctx - # acceptors feed the the queues - l.spawn_acceptor(opts[:threads] || 1, @logger, ctx) + + # our whole design is was based on BLOCKING listeners; back in 2010 + # However, Linux 4.5 (2016-03-13) added EPOLLEXCLUSIVE, which + # (at least on Linux) allows non-blocking listeners to solve the + # same problem we solved by using blocking listeners. + if l.nonblock? + l.yahns_listen_init(ctx) + next if ctx.queue.queue_add_acceptor(l) + ((@worker_processes || 0) > 1) and @logger.warn( +'non-blocking listener w/o EPOLLEXCLUSIVE, balance degraded') + else # our original design, acceptors feed the the queues + l.spawn_acceptor(opts[:threads] || 1, @logger, ctx) + end end fdmap end diff --git a/lib/yahns/socket_helper.rb b/lib/yahns/socket_helper.rb index 963c9fa..02f2d15 100644 --- a/lib/yahns/socket_helper.rb +++ b/lib/yahns/socket_helper.rb @@ -79,6 +79,7 @@ def bind_listen(address, opt) else raise ArgumentError, "Don't know how to bind: #{address}" end + sock.nonblock = opt[:nonblock] || false set_server_sockopt(sock, opt) sock end diff --git a/test/test_server.rb b/test/test_server.rb index 75e1857..6083a2f 100644 --- a/test/test_server.rb +++ b/test/test_server.rb @@ -903,4 +903,37 @@ def test_inherit_tcp_nodelay_set ensure quit_wait(pid) end + + def kernel_major_minor + major, minor = Etc.uname[:release].split('.')[0,2].map(&:to_i) + ver_int(major, minor) + end + + def ver_int(major, minor) + (major << 24) | (minor << 16) + end + + def test_inherit_nonblocking + err = @err + cfg = Yahns::Config.new + host, port = @srv.addr[3], @srv.addr[1] + @srv.nonblock = true + cfg.instance_eval do + ru = lambda { |_| [ 200, { 'Content-Length' => '2' } , [ 'HI' ] ] } + GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } } + logger(Logger.new(err.path)) + end + pid = mkserver(cfg, @srv) { ENV["YAHNS_FD"] = "#{@srv.fileno}" } + run_client(host, port) { |res| assert_equal "HI", res.body } + assert_predicate @srv, :nonblock? + unless defined?(SleepyPenguin::Epoll::EXCLUSIVE) && + RUBY_PLATFORM =~ /linux/ && + kernel_major_minor >= ver_int(4,5) + err.flush + err.rewind + err.truncate(0) + end + ensure + quit_wait(pid) + end end -- EW
Otherwise, https://yhbt.net/yahns/Yahns/ProxyPass.html won't be accessible --- Rakefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Rakefile b/Rakefile index 65862f2..769356e 100644 --- a/Rakefile +++ b/Rakefile @@ -59,6 +59,11 @@ examples.concat(gzex) sh("rsync --chmod=Fugo=r -av #{examples.join(' ')} #{dest}/examples/") + + rdoc = apidoc.keys.grep(%r{\Adoc/Yahns/}) + gzex = rdoc.map { |txt| do_gzip.call(txt) } + examples.concat(gzex) + sh("rsync --chmod=Fugo=r -av #{rdoc.join(' ')} #{dest}/Yahns/") end def tags -- EW
Might as well... this has been in use at YHBT.net for ~4 years at this point. And given nginx has new corporate overlords, maybe a decidedly non-enterprisey alternative is worth "marketing" :P Previous discussion from 2016: https://YHBT.net/yahns-public/20160220081619.GA10850@dcvr.yhbt.net/ --- .document | 2 + .olddoc.yml | 8 +++ Documentation/yahns_config.pod | 4 +- Rakefile | 20 +++++++- examples/https_proxy_pass.conf.rb | 36 ++++++++++++++ examples/proxy_pass.ru | 11 +++++ extras/proxy_pass.rb | 9 ++-- lib/yahns.rb | 17 ++++--- lib/yahns/proxy_pass.rb | 82 +++++++++++++++++++++++++------ 9 files changed, 160 insertions(+), 29 deletions(-) create mode 100644 .document create mode 100644 .olddoc.yml create mode 100644 examples/https_proxy_pass.conf.rb create mode 100644 examples/proxy_pass.ru diff --git a/.document b/.document new file mode 100644 index 0000000..1880850 --- /dev/null +++ b/.document @@ -0,0 +1,2 @@ +lib/yahns.rb +lib/yahns/proxy_pass.rb diff --git a/.olddoc.yml b/.olddoc.yml new file mode 100644 index 0000000..7e8d2ad --- /dev/null +++ b/.olddoc.yml @@ -0,0 +1,8 @@ +--- +cgit_url: https://yhbt.net/yahns.git +git_url: https://yhbt.net/yahns.git +rdoc_url: https://yhbt.net/yahns/ +ml_url: https://yhbt.net/yahns-public/ +public_email: yahns-public@yhbt.net +nntp_url: + - nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns diff --git a/Documentation/yahns_config.pod b/Documentation/yahns_config.pod index 737e085..08c2e27 100644 --- a/Documentation/yahns_config.pod +++ b/Documentation/yahns_config.pod @@ -448,10 +448,10 @@ An example which seems to work is: ) # use defaults provided by Ruby on top of OpenSSL, - # but disable client certificate verification as it is rare: + # but disable client certificate verification as it is rare for servers: ssl_ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE) - # Built-in session cache (only works if worker_processes is nil or 1) + # Built-in session cache (only useful if worker_processes is nil or 1) ssl_ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_SERVER app(:rack, "/path/to/my/app/config.ru") do diff --git a/Rakefile b/Rakefile index 3eb0219..65862f2 100644 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,24 @@ require 'tempfile' include Rake::DSL -gendocs = %w(NEWS NEWS.atom.xml) +apidoc = { + 'doc/Yahns.html' => 'lib/yahns.rb', + 'doc/Yahns/ProxyPass.html' => 'lib/yahns/proxy_pass.rb' +} + +task apidoc.keys[0] => apidoc.values do + rdoc = ENV['rdoc'] || 'rdoc' + system("git", "set-file-times", *(apidoc.values)) + sh "#{rdoc} -f dark216" # dark216 requires olddoc 1.7+ + + apidoc.each do |dst, src| + src = File.stat(src) + File.utime(src.atime, src.mtime, dst) + end +end + +gendocs = %W(NEWS NEWS.atom.xml #{apidoc.keys[0]}) +task html: apidoc.keys[0] task rsync_docs: gendocs do dest = ENV["RSYNC_DEST"] || "yhbt.net:/srv/yhbt/yahns/" top = %w(INSTALL HACKING README COPYING) @@ -28,6 +45,7 @@ files = `git ls-files Documentation/*.txt`.split(/\n/) files.concat(top) files.concat(gendocs) + files.concat(%w(doc/Yahns.html)) files.concat(%w(yahns yahns-rackup yahns_config).map! { |x| "Documentation/#{x}.txt" }) diff --git a/examples/https_proxy_pass.conf.rb b/examples/https_proxy_pass.conf.rb new file mode 100644 index 0000000..f2fbc3a --- /dev/null +++ b/examples/https_proxy_pass.conf.rb @@ -0,0 +1,36 @@ +# To the extent possible under law, Eric Wong has waived all copyright and +# related or neighboring rights to this example. +# +# See examples/proxy_pass.ru for the complementary rackup file +# <https://yhbt.net/yahns.git/tree/examples/proxy_pass.ru> + +# Setup an OpenSSL context: +require 'openssl' +ssl_ctx = OpenSSL::SSL::SSLContext.new +ssl_ctx.cert = OpenSSL::X509::Certificate.new( + File.read('/etc/ssl/certs/example.crt') +) +ssl_ctx.extra_chain_cert = [ + OpenSSL::X509::Certificate.new( + File.read('/etc/ssl/certs/chain.crt') + ) +] +ssl_ctx.key = OpenSSL::PKey::RSA.new( + File.read('/etc/ssl/private/example.key') +) + +# use defaults provided by Ruby on top of OpenSSL, +# but disable client certificate verification as it is rare for servers: +ssl_ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE) + +# Built-in session cache (only useful if worker_processes is nil or 1) +ssl_ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_SERVER + +worker_processes 1 +app(:rack, "/path/to/proxy_pass.ru", preload: true) do + listen 443, ssl_ctx: ssl_ctx + listen '[::]:443', ipv6only: true, ssl_ctx: ssl_ctx +end + +stdout_path "/path/to/my_logs/out.log" +stderr_path "/path/to/my_logs/err.log" diff --git a/examples/proxy_pass.ru b/examples/proxy_pass.ru new file mode 100644 index 0000000..63ee6d9 --- /dev/null +++ b/examples/proxy_pass.ru @@ -0,0 +1,11 @@ +# To the extent possible under law, Eric Wong has waived all copyright and +# related or neighboring rights to this example. +# +# See examples/https_proxy_pass.conf.rb for the complementary rackup file +# <https://yhbt.net/yahns.git/tree/examples/https_proxy_pass.conf.rb> + +# optionally, intercept static requests with Rack::Static middleware: +# use Rack::Static, root: '/path/to/public', gzip: true + +require 'yahns/proxy_pass' +run Yahns::ProxyPass.new('http://127.0.0.1:6081') diff --git a/extras/proxy_pass.rb b/extras/proxy_pass.rb index af6fe01..40bf19a 100644 --- a/extras/proxy_pass.rb +++ b/extras/proxy_pass.rb @@ -10,12 +10,13 @@ require 'thread' require 'timeout' -# Totally synchronous and Rack 1.1-compatible, this will probably be rewritten. -# to take advantage of rack.hijack and use the non-blocking I/O facilities -# in yahns. yahns may have to grow a supported API for that... +# Totally synchronous and Rack 1.1-compatible. See Yahns::ProxyPass for +# the rewritten version which takes advantage of rack.hijack and uses +# the internal non-blocking I/O facilities in yahns. yahns may have to +# grow a supported API for that... +# # For now, we this blocks a worker thread; fortunately threads are reasonably # cheap on GNU/Linux... -# This is totally untested but currently doesn't serve anything important. class ProxyPass # :nodoc: class ConnPool def initialize diff --git a/lib/yahns.rb b/lib/yahns.rb index 08945ef..4cf911e 100644 --- a/lib/yahns.rb +++ b/lib/yahns.rb @@ -1,5 +1,5 @@ -# Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net> -# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright (C) 2013-2019 all contributors <yahns-public@yhbt.net> +# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true $stdout.sync = $stderr.sync = true @@ -16,12 +16,15 @@ Unicorn.__send__(:remove_const, sym) if Unicorn.const_defined?(sym) end -# yahns exposes no user-visible API outside of the config file. -# See https://yhbt.net/yahns.git/tree/examples/yahns_config.txt -# for the config documentation +# yahns exposes little user-visible API outside of the config file. +# See https://yhbt.net/yahns/yahns_config.txt +# for the config documentation (or yahns_config(5) manpage) # and https://yhbt.net/yahns.git/about/ for the homepage. -# Internals are subject to change. - +# +# Yahns::ProxyPass is currently the only public API. +# +# Documented APIs and options are supported forever, +# internals are subject to change. module Yahns # :stopdoc: # We populate this at startup so we can figure out how to reexecute diff --git a/lib/yahns/proxy_pass.rb b/lib/yahns/proxy_pass.rb index 2a37773..bc902f8 100644 --- a/lib/yahns/proxy_pass.rb +++ b/lib/yahns/proxy_pass.rb @@ -1,24 +1,76 @@ # -*- encoding: binary -*- -# Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net> -# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright (C) 2013-2019 all contributors <yahns-public@yhbt.net> +# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true require 'socket' require 'rack/request' -require 'timeout' - -# XXX consider this file and the proxy-related stuff in yahns -# unstable and experimental! It has never been documented and -# incompatible changes may still happen. -# -# However, it seems to be proxying for our mail archives well enough: -# https://yhbt.net/yahns-public/ +require 'timeout' # only for Timeout::Error require_relative 'proxy_http_response' require_relative 'req_res' -class Yahns::ProxyPass # :nodoc: - attr_reader :proxy_buffering, :response_headers +# Yahns::ProxyPass is a Rack (hijack) app which allows yahns to +# act as a fully-buffering reverse proxy to protect backends +# from slow HTTP clients. +# +# Yahns::ProxyPass relies on the default behavior of yahns to do +# full input and output buffering. Output buffering is lazy, +# meaning it allows streaming output in the best case and +# will only buffer if the client cannot keep up with the server. +# +# The goal of this reverse proxy is to act as a sponge on the same LAN +# or host to any backend HTTP server not optimized for slow clients. +# Yahns::ProxyPass accomplishes this by handling all the slow clients +# internally within yahns itself to minimize time spent in the backend +# HTTP server waiting on slow clients. +# +# It does not do load balancing (we rely on Varnish for that). +# Here is the exact config we use with Varnish, which uses +# the +:response_headers+ option to hide some Varnish headers +# from clients: +# +# run Yahns::ProxyPass.new('http://127.0.0.1:6081', +# response_headers: { +# 'Age' => :ignore, +# 'X-Varnish' => :ignore, +# 'Via' => :ignore +# }) +# +# This is NOT a generic Rack app and must be run with yahns. +# It uses +rack.hijack+, so compatibility with logging +# middlewares (e.g. Rack::CommonLogger) is not great and +# timing information gets lost. +# +# This provides HTTPS termination for our mail archives: +# https://yhbt.net/yahns-public/ +# +# See https://yhbt.net/yahns.git/tree/examples/https_proxy_pass.conf.rb +# and https://yhbt.net/yahns.git/tree/examples/proxy_pass.ru for examples +class Yahns::ProxyPass + attr_reader :proxy_buffering, :response_headers # :nodoc: - def initialize(dest, opts = {}) + # +dest+ must be an HTTP URL with optional variables prefixed with '$'. + # +dest+ may refer to the path to a Unix domain socket in the form: + # + # unix:/absolute/path/to/socket + # + # Variables which may be used in the +dest+ parameter include: + # + # - $url - the entire URL used to make the request + # - $path - the unescaped PATH_INFO of the HTTP request + # - $fullpath - $path with QUERY_STRING + # - $host - the hostname in the Host: header + # + # For Unix domain sockets, variables may be separated from the + # socket path via: ":/". For example: + # + # unix:/absolute/path/to/socket:/$host/$fullpath + # + # Currently :response_headers is the only +opts+ supported. + # :response_headers is a Hash containing a "from => to" mapping + # of response headers. The special value of +:ignore+ indicates + # the header from the backend HTTP server will be ignored instead + # of being blindly passed on to the client. + def initialize(dest, opts = { response_headers: { 'Server' => :ignore } }) case dest when %r{\Aunix:([^:]+)(?::(/.*))?\z} path = $2 @@ -41,7 +93,7 @@ def initialize(dest, opts = {}) init_path_vars(path) end - def init_path_vars(path) + def init_path_vars(path) # :nodoc: path ||= '$fullpath' # methods from Rack::Request we want: allow = %w(fullpath host_with_port host port url path) @@ -54,7 +106,7 @@ def init_path_vars(path) @path = path.gsub(%r{\A/(\$(?:fullpath|path))}, '\1') end - def call(env) + def call(env) # :nodoc: # 3-way handshake for TCP backends while we generate the request header rr = Yahns::ReqRes.start(@sockaddr) c = env['rack.hijack'].call # Yahns::HttpClient#call -- EW
IO.read may invoke subprocesses, which can set off security warnings. --- Documentation/yahns_config.pod | 6 +++--- test/helper.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/yahns_config.pod b/Documentation/yahns_config.pod index 01b1bf9..737e085 100644 --- a/Documentation/yahns_config.pod +++ b/Documentation/yahns_config.pod @@ -436,15 +436,15 @@ An example which seems to work is: require 'openssl' ssl_ctx = OpenSSL::SSL::SSLContext.new ssl_ctx.cert = OpenSSL::X509::Certificate.new( - IO.read('/etc/ssl/certs/example.crt') + File.read('/etc/ssl/certs/example.crt') ) ssl_ctx.extra_chain_cert = [ OpenSSL::X509::Certificate.new( - IO.read('/etc/ssl/certs/chain.crt') + File.read('/etc/ssl/certs/chain.crt') ) ] ssl_ctx.key = OpenSSL::PKey::RSA.new( - IO.read('/etc/ssl/private/example.key') + File.read('/etc/ssl/private/example.key') ) # use defaults provided by Ruby on top of OpenSSL, diff --git a/test/helper.rb b/test/helper.rb index 550a0f1..f9370a4 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -158,7 +158,7 @@ def skip_skb_mem [ [ '/proc/sys/net/ipv4/tcp_rmem', "4096 87380 6291456\n" ], [ '/proc/sys/net/ipv4/tcp_wmem', "4096 16384 4194304\n" ] ].each do |file, expect| - val = IO.read(file) + val = File.read(file) val == expect or skip "#{file} had: #{val}expected: #{expect}" end end -- EW
Linux before 4.9 (and before 3.16.57) failed to account for the existing size of a pipe before checking system resource limits and would return EPERM in that case. https://80x24.org/mirrors/linux.git/commit?id=b0b91d18e2e97b741b294af9333824ecc3fadfd8 https://lore.kernel.org/lkml/?q=s%3A%22fix+limit+checking+in+pipe_set_size%22 Based on a patch by Stephen Demjanenko for unicorn: https://bogomips.org/unicorn-public/1556922018-24096-1-git-send-email-sdemjanenko@gmail.com/ --- lib/yahns/worker.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/yahns/worker.rb b/lib/yahns/worker.rb index 0355629..ec5be23 100644 --- a/lib/yahns/worker.rb +++ b/lib/yahns/worker.rb @@ -14,8 +14,8 @@ def initialize(nr) # F_SETPIPE_SZ = 1031, PAGE_SIZE = 4096 # (fcntl will handle minimum size on platforms where PAGE_SIZE > 4096) @to_io.fcntl(1031, 4096) - rescue Errno::EINVAL - # old kernel + rescue SystemCallError + # old kernel (EINVAL, EPERM) end if RUBY_PLATFORM =~ /\blinux\b/ end -- EW
Earth day release of a server designed for low energy use! A Free Software, multi-threaded, non-blocking network application server designed for low _idle_ power consumption. It is primarily optimized for applications with occasional users which see little or no traffic. yahns currently hosts Rack/HTTP applications, but may eventually support other application types. Unlike some existing servers, yahns is extremely sensitive to fatal bugs in the applications it hosts. * git clone https://yhbt.net/yahns.git * https://yhbt.net/yahns.git/about/ * https://yhbt.net/yahns/NEWS.atom.xml (supported by most "RSS" readers) * we only accept plain-text email yahns-public@yhbt.net * and archive all the mail we receive: https://yhbt.net/yahns-public/ * nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns lrg nabgure ubeevoyl-anzrq freire :> Changes: This releases includes a few kqueue-related bugfixes from Lin Jen-Shin <godfat@godfat.org>. There's also some shutdown_timeout-related bugfixes and some cleanups to reduce dependencies on kgio. extras/exec_cgi and extras/autoindex both got some minor updates; the latter of which defaults to a dark scheme to save power on OLED and CRT displays (it is Earth Day, after all :>) Eric Wong (25): doc: fix git URLs in README and HACKING http_client: do not warn on do_pread overreach remove IO#close_on_exec= calls yahns/worker: shrink pipe under Linux test/test_extras_exec_cgi.rb: improve test reliability extras/exec_cgi: remove kgio dependency extras/exec_cgi: update copyright year and use SPDX extras/exec_cgi: @body_tip is always set extras/exec_cgi: support Process.spawn options (e.g. RLIMIT_*) server_mp: favor "Kernel#select" instead of "IO.select" server_mp: remove redundant srand call extras/exec_cgi: remove unecessary .freeze calls server: respect shutdown_timeout server: extra shutdown timeout debugging wbuf: use IO#write directly in Ruby 2.5+ for writev worker: rely on F_SETPIPE_SZ to set mininum value remove calls to kgio_wait_* able do not shutdown until listeners are all stopped README: remove SMTP-based subscription info sigevent_*: fixup non-eventfd path README: note HTTPS support for HTTP/1.1 extras/autoindex: use dark colors by default doc: update homepage to point to cgit install doc: update wording to avoid "mailing list" doc: remove inaccurate comment about "GNU-ism" Lin Jen-Shin (2): Add QueueQuitter#closed? to queue_quitter_pipe.rb @srv.shutdown could raise Errno::ENOTCONN Please note the disclaimer: yahns is extremely sensitive to fatal bugs in the apps it hosts. There is no (and never will be) any built-in "watchdog"-type feature to kill stuck processes/threads. Each yahns process may be handling thousands of clients; unexpectedly killing the process will abort _all_ of those connections. Lives may be lost! yahns hackers are not responsible for your application/library bugs. Use an application server which is tolerant of buggy applications if you cannot be bothered to fix all your fatal bugs. *zzzzzzz*
touch(1posix) documents the presence of "-r" --- Documentation/GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/GNUmakefile b/Documentation/GNUmakefile index ee27399..9f5c43f 100644 --- a/Documentation/GNUmakefile +++ b/Documentation/GNUmakefile @@ -60,7 +60,7 @@ all :: txt %.txt : %.pod $(pod2text) $< $@+ - -touch -r $< $@+ 2>/dev/null # GNU-ism + touch -r $< $@+ mv $@+ $@ clean:: -- EW
I've gotten cgit to work well for browsers without CSS support (patches posted to <cgit@lists.zx2c4.com>). For browsers with CSS support, the color scheme is now dark and can help save electricity for users of OLED and CRT displays. --- README | 2 +- lib/yahns.rb | 5 +++-- lib/yahns/config.rb | 4 ++-- yahns.gemspec | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README b/README index 68ed39d..0e4bc4a 100644 --- a/README +++ b/README @@ -71,7 +71,7 @@ This README is our homepage, we would rather be working on HTTP servers all day than worrying about the next browser vulnerability because HTML/CSS/JS is too complicated for us. -* https://yhbt.net/yahns/README +* https://yhbt.net/yahns.git/about/ Hacking ------- diff --git a/lib/yahns.rb b/lib/yahns.rb index 21464ca..08945ef 100644 --- a/lib/yahns.rb +++ b/lib/yahns.rb @@ -17,8 +17,9 @@ end # yahns exposes no user-visible API outside of the config file. -# See https://yhbt.net/yahns/yahns_config.txt for the config documentation -# and https://yhbt.net/yahns/ for the homepage. +# See https://yhbt.net/yahns.git/tree/examples/yahns_config.txt +# for the config documentation +# and https://yhbt.net/yahns.git/about/ for the homepage. # Internals are subject to change. module Yahns diff --git a/lib/yahns/config.rb b/lib/yahns/config.rb index e64cb77..441d3f9 100644 --- a/lib/yahns/config.rb +++ b/lib/yahns/config.rb @@ -4,8 +4,8 @@ # frozen_string_literal: true # # Implements a DSL for configuring a yahns server. -# See https://yhbt.net/yahns/examples/yahns_multi.conf.rb for a full -# example configuration file. +# See https://yhbt.net/yahns.git/tree/examples/yahns_multi.conf.rb +# for a full example configuration file. class Yahns::Config # :nodoc: # public within yahns itself, NOT a public interface for users outside # of yahns. See yahns/rack for usage example diff --git a/yahns.gemspec b/yahns.gemspec index b4af5b5..5752ad8 100644 --- a/yahns.gemspec +++ b/yahns.gemspec @@ -28,6 +28,6 @@ # for Rack::Utils::HeaderHash#each s.add_development_dependency(%q<rack>, '>= 1.1') - s.homepage = 'https://yhbt.net/yahns/README' + s.homepage = 'https://yhbt.net/yahns.git/about/' s.licenses = "GPL-3.0+" end -- EW
The concept of a "mailing list" infers the existence of a centralized subscriber list, and hurts forkability. The "public inbox" concept is a more accurate description and mostly centralization-resistant, aside from domain names. --- Documentation/yahns-rackup.pod | 4 ++-- Documentation/yahns.pod | 2 +- Documentation/yahns_config.pod | 4 ++-- HACKING | 11 +++++------ README | 13 +++++++------ 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Documentation/yahns-rackup.pod b/Documentation/yahns-rackup.pod index de2d4b6..617a514 100644 --- a/Documentation/yahns-rackup.pod +++ b/Documentation/yahns-rackup.pod @@ -166,8 +166,8 @@ See rackup documentation for more details. =head1 CONTACT All feedback welcome via plain-text mail to L<mailto:yahns-public@yhbt.net> -No subscription is necessary to post to the mailing list. -List archives are available at L<https://yhbt.net/yahns-public/> +No subscription is necessary to email us. +Mail archives are available at L<https://yhbt.net/yahns-public/> =head1 COPYRIGHT diff --git a/Documentation/yahns.pod b/Documentation/yahns.pod index 5d505ce..1954584 100644 --- a/Documentation/yahns.pod +++ b/Documentation/yahns.pod @@ -83,7 +83,7 @@ See L<yahns_config(5)> for documentation on the configuration file format. =head1 CONTACT All feedback welcome via plain-text mail to L<mailto:yahns-public@yhbt.net> -No subscription is necessary to post to the mailing list. +No subscription is necessary to email us. Mail archives are available at L<https://yhbt.net/yahns-public/> =head1 COPYRIGHT diff --git a/Documentation/yahns_config.pod b/Documentation/yahns_config.pod index 5d0e441..01b1bf9 100644 --- a/Documentation/yahns_config.pod +++ b/Documentation/yahns_config.pod @@ -661,8 +661,8 @@ See the examples/ directory in the git source tree. =head1 CONTACT All feedback welcome via plain-text mail to L<mailto:yahns-public@yhbt.net> -No subscription is necessary to post to the mailing list. -List archives are available at L<https://yhbt.net/yahns-public/> +No subscription is necessary to email us. +Mail archives are available at L<https://yhbt.net/yahns-public/> =head1 COPYRIGHT diff --git a/HACKING b/HACKING index 6d73fc1..5a63e7b 100644 --- a/HACKING +++ b/HACKING @@ -42,15 +42,14 @@ installing from git contact ------- -We use git(7) and develop yahns on a public mailing list like git -developers do. Please send patches via git-send-email(1) to the public -mailing list at <yahns-public@yhbt.net>. Pull requests should be -formatted using git-request-pull(1). +We use git(7) and develop yahns using email like git.git hackers do. +Please send patches via git-send-email(1) to us at <yahns-public@yhbt.net>. +Pull requests should be formatted using git-request-pull(1). All mail is archived publically at: https://yhbt.net/yahns-public/ Anonymous contributions will always be welcome. -No subscription is necessary to post to the mailing list. -Please remember to Cc: all recipients as subscription is optional. +No subscription is necessary to email us. +Please remember to reply-to-all as we do not encourage subscription. Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net> License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> diff --git a/README b/README index 0e4bc4a..2cd0e92 100644 --- a/README +++ b/README @@ -56,15 +56,16 @@ Contact We are happy to see feedback of all types via plain-text email. Please send comments, user/dev discussion, patches, bug reports, -and pull requests to the open-to-all mailing list at: +and pull requests to our public inbox at: yahns-public@yhbt.net -No subscription is necessary to post. Please Cc: all recipients as -subscription is not necessary. +Please use reply-to-all as we do not require any sort of subscription. +We archive all of our mail publically at: + + https://yhbt.net/yahns-public/ + nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns -Mailing list archives browsable via HTTPS: https://yhbt.net/yahns-public/ -Or NNTP: nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns Atom feed: https://yhbt.net/yahns-public/new.atom This README is our homepage, we would rather be working on HTTP servers @@ -77,7 +78,7 @@ Hacking ------- We use git and follow the same development model as git itself -(mailing list-oriented, benevolent dictator). +(email-oriented, benevolent dictator). git clone https://yhbt.net/yahns.git -- EW