Skip to content

Commit

Permalink
Merge branch 'master' into log_formatter_config
Browse files Browse the repository at this point in the history
  • Loading branch information
ylecuyer committed Jul 11, 2019
2 parents 8be0b65 + 3b48d32 commit 82274d6
Show file tree
Hide file tree
Showing 21 changed files with 105 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
@@ -1,6 +1,6 @@
AllCops:
DisabledByDefault: true
TargetRubyVersion: 2.5
TargetRubyVersion: 2.2
DisplayCopNames: true
StyleGuideCopsOnly: false
Exclude:
Expand Down
12 changes: 11 additions & 1 deletion .travis.yml
Expand Up @@ -8,7 +8,8 @@ before_install:
r_eng="$(ruby -e 'STDOUT.write RUBY_ENGINE')";
rv="$(ruby -e 'STDOUT.write RUBY_VERSION')";
if [ "$r_eng" == "ruby" ]; then
if [ "$rv" \< "2.6" ]; then gem update --system --no-document --conservative
if [ "$rv" \< "2.3" ]; then gem update --system 2.7.9 --no-document
elif [ "$rv" \< "2.6" ]; then gem update --system --no-document --conservative
fi
fi
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
Expand All @@ -24,15 +25,24 @@ script:
- bundle exec rake

rvm:
- 2.2.10
- 2.3.8
- 2.4.6
- 2.5.5
- 2.6.3
- ruby-head

matrix:
fast_finish: true
include:
- rvm: 2.2
dist: trusty
env: NOTES="Trusty OpenSSL 1.0.1"
- rvm: ruby-head
env: RUBYOPT="--jit"
- rvm: 2.4.6
os: osx
osx_image: xcode10.2
- rvm: 2.5.5
os: osx
osx_image: xcode10.2
Expand Down
19 changes: 15 additions & 4 deletions History.md
@@ -1,6 +1,17 @@
## Master

* x features
x features

* 2 bugfixes
* Socket removed after reload (#1829)
* Add extconf tests for DTLS_method & TLS_server_method, use in minissl.rb. (#1832)
* 1 feature
* Add log_formatter configuration


## 4.0.0 / 2019-06-25

* 9 features
* Add support for disabling TLSv1.0 (#1562)
* Request body read time metric (#1569)
* Add out_of_band hook (#1648)
Expand All @@ -9,9 +20,9 @@
* Add option to suppress SignalException on SIGTERM (#1690)
* Allow mutual TLS CA to be set using `ssl_bind` DSL (#1689)
* Reactor now uses nio4r instead of `select` (#1728)
* Minimum Ruby version now >= 2.5 (#1813)
* Add log_formatter configuration
* x bugfixes
* Add status to pumactl with pidfile (#1824)

* 9 bugfixes
* Do not accept new requests on shutdown (#1685, #1808)
* Fix 3 corner cases when request body is chunked (#1508)
* Change pid existence check's condition branches (#1650)
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -3,7 +3,7 @@ require "rake/testtask"
require "rake/extensiontask"
require "rake/javaextensiontask"
require "rubocop/rake_task"
require 'puma/detect'
require_relative 'lib/puma/detect'
require 'rubygems/package_task'
require 'bundler/gem_tasks'

Expand Down
8 changes: 8 additions & 0 deletions ext/puma_http11/extconf.rb
Expand Up @@ -9,6 +9,14 @@
%w'ssl ssleay32'.find {|ssl| have_library(ssl, 'SSL_CTX_new')}

have_header "openssl/bio.h"

# below is yes for 1.0.2 & later
have_func "DTLS_method" , "openssl/ssl.h"

# below are yes for 1.1.0 & later, may need to check func rather than macro
# with versions after 1.1.1
have_func "TLS_server_method" , "openssl/ssl.h"
have_macro "SSL_CTX_set_min_proto_version", "openssl/ssl.h"
end
end

Expand Down
10 changes: 8 additions & 2 deletions ext/puma_http11/mini_ssl.c
Expand Up @@ -168,8 +168,11 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
ID sym_no_tlsv1 = rb_intern("no_tlsv1");
VALUE no_tlsv1 = rb_funcall(mini_ssl_ctx, sym_no_tlsv1, 0);


#ifdef HAVE_TLS_SERVER_METHOD
ctx = SSL_CTX_new(TLS_server_method());
#else
ctx = SSL_CTX_new(SSLv23_server_method());
#endif
conn->ctx = ctx;

SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert));
Expand Down Expand Up @@ -232,8 +235,11 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
VALUE engine_init_client(VALUE klass) {
VALUE obj;
ms_conn* conn = engine_alloc(klass, &obj);

#ifdef HAVE_DTLS_METHOD
conn->ctx = SSL_CTX_new(DTLS_method());
#else
conn->ctx = SSL_CTX_new(DTLSv1_method());
#endif
conn->ssl = SSL_new(conn->ctx);
SSL_set_app_data(conn->ssl, NULL);
SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL);
Expand Down
7 changes: 4 additions & 3 deletions lib/puma/binder.rb
Expand Up @@ -53,7 +53,8 @@ def close
@unix_paths.each do |i|
# Errno::ENOENT is intermittently raised
begin
File.unlink i
unix_socket = UNIXSocket.new i
unix_socket.close
rescue Errno::ENOENT
end
end
Expand All @@ -63,7 +64,7 @@ def import_from_env
remove = []

ENV.each do |k,v|
if /PUMA_INHERIT_\d+/.match?(k)
if k =~ /PUMA_INHERIT_\d+/
fd, url = v.split(":", 2)
@inherited_fds[url] = fd.to_i
remove << k
Expand All @@ -75,7 +76,7 @@ def import_from_env
key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
rescue ArgumentError
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
if /\:/.match?(addr)
if addr =~ /\:/
addr = "[#{addr}]"
end
key = [ :tcp, addr, port ]
Expand Down
7 changes: 4 additions & 3 deletions lib/puma/client.rb
Expand Up @@ -27,9 +27,10 @@ class ConnectionError < RuntimeError; end
# For example a web request from a browser or from CURL. This
#
# An instance of `Puma::Client` can be used as if it were an IO object
# for example it is passed into `IO.select` inside of the `Puma::Reactor`.
# This is accomplished by the `to_io` method which gets called on any
# non-IO objects being used with the IO api such as `IO.select.
# by the reactor, that's because the latter is expected to call `#to_io`
# on any non-IO objects it polls. For example nio4r internally calls
# `IO::try_convert` (which may call `#to_io`) when a new socket is
# registered.
#
# Instances of this class are responsible for knowing if
# the header and body are fully buffered via the `try_to_finish` method.
Expand Down
2 changes: 1 addition & 1 deletion lib/puma/configuration.rb
Expand Up @@ -348,7 +348,7 @@ def self.random_token
end

if bytes
token = +""
token = "".dup
bytes.each_byte { |b| token << b.to_s(16) }
else
token = (0..count).to_a.map { rand(255).to_s(16) }.join
Expand Down
4 changes: 2 additions & 2 deletions lib/puma/const.rb
Expand Up @@ -100,8 +100,8 @@ class UnsupportedOption < RuntimeError
# too taxing on performance.
module Const

PUMA_VERSION = VERSION = "3.12.1".freeze
CODE_NAME = "Llamas in Pajamas".freeze
PUMA_VERSION = VERSION = "4.0.0".freeze
CODE_NAME = "4 Fast 4 Furious".freeze
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze

FAST_TRACK_KA_TIMEOUT = 0.2
Expand Down
10 changes: 10 additions & 0 deletions lib/puma/control_cli.rb
Expand Up @@ -206,6 +206,16 @@ def send_signal
when "phased-restart"
Process.kill "SIGUSR1", @pid

when "status"
begin
Process.kill 0, @pid
puts "Puma is started"
rescue Errno::ESRCH
raise "Puma is not running"
end

return

else
return
end
Expand Down
4 changes: 2 additions & 2 deletions lib/puma/launcher.rb
Expand Up @@ -254,7 +254,7 @@ def restart!

argv = restart_args
Dir.chdir(@restart_dir)
argv += [redirects] if RUBY_VERSION >= '1.9'
argv += [redirects]
Kernel.exec(*argv)
end
end
Expand Down Expand Up @@ -283,7 +283,7 @@ def prune_bundler
wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
# Ruby 2.0+ defaults to true which breaks socket activation
args += [{:close_others => false}] if RUBY_VERSION >= '2.0'
args += [{:close_others => false}]
Kernel.exec(*args)
end
end
Expand Down
32 changes: 16 additions & 16 deletions lib/puma/reactor.rb
Expand Up @@ -20,10 +20,11 @@ module Puma
#
# ## Reactor Flow
#
# A request comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance.
# The reactor stores the request in an array and calls `IO.select` on the array in a loop.
# A connection comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance,
# which stores it in an array and waits for any of the connections to be ready for reading.
#
# When the request is written to by the client then the `IO.select` will "wake up" and
# The waiting/wake up is performed with nio4r, which will use the apropriate backend (libev, Java NIO or
# just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
# return the references to any objects that caused it to "wake". The reactor
# then loops through each of these request objects, and sees if they're complete. If they
# have a full header and body then the reactor passes the request to a thread pool.
Expand Down Expand Up @@ -69,19 +70,18 @@ def initialize(server, app_pool)

private


# Until a request is added via the `add` method this method will internally
# loop, waiting on the `sockets` array objects. The only object in this
# array at first is the `@ready` IO object, which is the read end of a pipe
# connected to `@trigger` object. When `@trigger` is written to, then the loop
# will break on `IO.select` and return an array.
# will break on `NIO::Selector#select` and return an array.
#
# ## When a request is added:
#
# When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
# Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
#
# When that happens, the internal loop stops blocking at `IO.select` and returns a reference
# When that happens, the internal loop stops blocking at `NIO::Selector#select` and returns a reference
# to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
# When `@trigger` is written-to, the loop "wakes" and the `ready`
# variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
Expand All @@ -97,7 +97,7 @@ def initialize(server, app_pool)
# to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
#
# Since the `Puma::Client` in this example has data that has not been read yet,
# the `IO.select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
# the `NIO::Selector#select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
# `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
#
# Each element in the first entry is iterated over. The `Puma::Client` object is not
Expand All @@ -109,12 +109,12 @@ def initialize(server, app_pool)
#
# If the request body is not present then nothing will happen, and the loop will iterate
# again. When the client sends more data to the socket the `Puma::Client` object will
# wake up the `IO.select` and it can again be checked to see if it's ready to be
# wake up the `NIO::Selector#select` and it can again be checked to see if it's ready to be
# passed to the thread pool.
#
# ## Time Out Case
#
# In addition to being woken via a write to one of the sockets the `IO.select` will
# In addition to being woken via a write to one of the sockets the `NIO::Selector#select` will
# periodically "time out" of the sleep. One of the functions of this is to check for
# any requests that have "timed out". At the end of the loop it's checked to see if
# the first element in the `@timeout` array has exceed its allowed time. If so,
Expand All @@ -124,7 +124,7 @@ def initialize(server, app_pool)
#
# This behavior loops until all the objects that have timed out have been removed.
#
# Once all the timeouts have been processed, the next duration of the `IO.select` sleep
# Once all the timeouts have been processed, the next duration of the `NIO::Selector#select` sleep
# will be set to be equal to the amount of time it will take for the next timeout to occur.
# This calculation happens in `calculate_sleep`.
def run_internal
Expand Down Expand Up @@ -320,7 +320,7 @@ def run_in_thread
end
end

# The `calculate_sleep` sets the value that the `IO.select` will
# The `calculate_sleep` sets the value that the `NIO::Selector#select` will
# sleep for in the main reactor loop when no sockets are being written to.
#
# The values kept in `@timeouts` are sorted so that the first timeout
Expand Down Expand Up @@ -351,18 +351,18 @@ def calculate_sleep
# object.
#
# The main body of the reactor loop is in `run_internal` and it
# will sleep on `IO.select`. When a new connection is added to the
# will sleep on `NIO::Selector#select`. When a new connection is added to the
# reactor it cannot be added directly to the `sockets` array, because
# the `IO.select` will not be watching for it yet.
# the `NIO::Selector#select` will not be watching for it yet.
#
# Instead what needs to happen is that `IO.select` needs to be woken up,
# Instead what needs to happen is that `NIO::Selector#select` needs to be woken up,
# the contents of `@input` added to the `sockets` array, and then
# another call to `IO.select` needs to happen. Since the `Puma::Client`
# another call to `NIO::Selector#select` needs to happen. Since the `Puma::Client`
# object can be read immediately, it does not block, but instead returns
# right away.
#
# This behavior is accomplished by writing to `@trigger` which wakes up
# the `IO.select` and then there is logic to detect the value of `*`,
# the `NIO::Selector#select` and then there is logic to detect the value of `*`,
# pull the contents from `@input` and add them to the sockets array.
#
# If the object passed in has a timeout value in `timeout_at` then
Expand Down
2 changes: 1 addition & 1 deletion lib/puma/server.rb
Expand Up @@ -105,7 +105,7 @@ def tcp_mode!
# On Linux, use TCP_CORK to better control how the TCP stack
# packetizes our stream. This improves both latency and throughput.
#
if RUBY_PLATFORM.match?(/linux/)
if RUBY_PLATFORM =~ /linux/
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze

# 6 == Socket::IPPROTO_TCP
Expand Down
5 changes: 1 addition & 4 deletions puma.gemspec
Expand Up @@ -17,8 +17,5 @@ Gem::Specification.new do |s|
%w[History.md LICENSE README.md]
s.homepage = "http://puma.io"
s.license = "BSD-3-Clause"

# We will guarantee to support the last 2 major releases,
# and may choose to support further back as we see fit.
s.required_ruby_version = Gem::Requirement.new(">= 2.5")
s.required_ruby_version = Gem::Requirement.new(">= 2.2")
end
2 changes: 1 addition & 1 deletion test/shell/t1.rb
Expand Up @@ -11,7 +11,7 @@
File.unlink "t1-stdout" if File.file? "t1-stdout"
File.unlink "t1-pid" if File.file? "t1-pid"

if %r!GET / HTTP/1\.1!.match?(log)
if log =~ %r!GET / HTTP/1\.1!
exit 0
else
exit 1
Expand Down
4 changes: 3 additions & 1 deletion test/shell/t2.rb
Expand Up @@ -2,6 +2,8 @@
sleep 5
system "curl http://localhost:10103/"

out=`ruby -rrubygems -Ilib bin/pumactl -F test/shell/t2_conf.rb status`

system "ruby -rrubygems -Ilib bin/pumactl -F test/shell/t2_conf.rb stop"

sleep 1
Expand All @@ -10,7 +12,7 @@

File.unlink "t2-stdout" if File.file? "t2-stdout"

if log =~ %r(GET / HTTP/1\.1) && !File.file?("t2-pid")
if log =~ %r(GET / HTTP/1\.1) && !File.file?("t2-pid") && out == "Puma is started\n"
exit 0
else
exit 1
Expand Down
3 changes: 2 additions & 1 deletion test/test_integration.rb
Expand Up @@ -186,7 +186,7 @@ def test_phased_restart_via_pumactl
until done
@events.stdout.rewind
log = @events.stdout.readlines.join("")
if /- Worker \d \(pid: \d+\) booted, phase: 1/.match?(log)
if log =~ /- Worker \d \(pid: \d+\) booted, phase: 1/
assert_match(/TERM sent/, log)
assert_match(/- Worker \d \(pid: \d+\) booted, phase: 1/, log)
done = true
Expand All @@ -197,6 +197,7 @@ def test_phased_restart_via_pumactl
ccli.run

assert_kind_of Thread, t.join, "server didn't stop"
assert File.exist? @bind_path
end

def test_kill_unknown_via_pumactl
Expand Down
2 changes: 1 addition & 1 deletion test/test_persistent.rb
Expand Up @@ -37,7 +37,7 @@ def teardown
end

def lines(count, s=@client)
str = +""
str = "".dup
Timeout.timeout(5) do
count.times { str << s.gets }
end
Expand Down

0 comments on commit 82274d6

Please sign in to comment.