pitchfork 0.11.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dc129b2e6e6e940be0f9e388400a376614a65a125a64b68c73b919744c433b3
4
- data.tar.gz: b12e1d5f360edc7159567060c095b40510e8e89c54dae058882742c29c830f78
3
+ metadata.gz: cd251198665823484f036ddf2cf11c9415c3c07c04c8e6e809c0e74b05c1055f
4
+ data.tar.gz: fc9475e4c4605b8bdf6cb7854946577413bbe980b5ac531a1638b43be4574d03
5
5
  SHA512:
6
- metadata.gz: 4a43b0204dc0e77a4d28007dbfd89e9645724a4198ea6ec7ffa0895aefaf075bd42d4303c37d0f7e61afb550b665b78ab6b22ca9f934ef2d3721883acf0a68cd
7
- data.tar.gz: 7eb41807d6971ac948ee264b1f70fd2e16afcf8f289655073c876b752a53a405b9705e97509f5be82c519ee12694ecf1910b18a20cce8aad3ce9a4ffd3af9ac6
6
+ metadata.gz: ba08c99f11e707e37af4265ac9f1dd7f15c62243b2c65ffc6dd2a3189db51dc3512f5b59234b16c98aff396fc88585d0f2c255ab7af9921653c9fdb4efbe7e61
7
+ data.tar.gz: '06790f57c21601cc73bcf34a01d89278c5bf6fb34b846a0534ab5d6982432c334ee101d50154a465e503fb92378c0e2809a5b94704e129aa4ee0cbfb5e88f058'
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
16
  - name: Check out code
17
- uses: actions/checkout@v3
17
+ uses: actions/checkout@v4
18
18
 
19
19
  - name: Set up Ruby
20
20
  uses: ruby/setup-ruby@v1
@@ -25,5 +25,5 @@ jobs:
25
25
  - name: Install packages
26
26
  run: sudo apt-get install -y ragel socat netcat
27
27
 
28
- - name: Tests
28
+ - name: Tests ${{ matrix.rubyopt }}
29
29
  run: bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.13.0
4
+
5
+ - Fix compatibility with `--enable-frozen-string-literal` in preparation for Ruby 3.4.
6
+ - Extend timed out workers deadline after sending signals to avoid flooding.
7
+ - Fix compilation with Ruby 2.7 and older on macOS.
8
+
9
+ # 0.12.0
10
+
11
+ - Disable IO tracking on Rubies older than 3.2.3 to avoid running into https://bugs.ruby-lang.org/issues/19531.
12
+ This Ruby bug can lead to memory corruption that cause VM crashes when calling various IO methods.
13
+ - Implement `rack.response_finished` (#97).
14
+ - Don't break the `rack.after_reply` callback chain if one callback raises (#97).
15
+
3
16
  # 0.11.1
4
17
 
5
18
  - Fix Ruby 3.4-dev compatibility.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pitchfork (0.11.1)
4
+ pitchfork (0.13.0)
5
5
  rack (>= 2.0)
6
6
  raindrops (~> 0.7)
7
7
 
@@ -12,7 +12,7 @@ GEM
12
12
  nio4r (2.7.0)
13
13
  puma (6.4.2)
14
14
  nio4r (~> 2.0)
15
- rack (3.0.9.1)
15
+ rack (3.0.10)
16
16
  raindrops (0.20.1)
17
17
  rake (13.0.6)
18
18
  rake-compiler (1.2.1)
@@ -31,4 +31,4 @@ DEPENDENCIES
31
31
  rake-compiler
32
32
 
33
33
  BUNDLED WITH
34
- 2.3.22
34
+ 2.3.27
data/benchmark/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Copy on Write Efficiency
4
4
 
5
- This benchmark aimed to compare real memory usage of differnet servers.
5
+ This benchmark aimed to compare real memory usage of different servers.
6
6
 
7
7
  For instance, Puma 2 workers + 2 threads:
8
8
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  require "net/http"
3
4
 
4
5
  app_path = File.expand_path('../examples/constant_caches.ru', __dir__)
@@ -188,7 +188,7 @@ The second "hard" timeout, is the sum of `timeout` and `cleanup`.
188
188
  Workers taking longer than this time period to be ready to handle a new
189
189
  request will be forcibly killed (via `SIGKILL`).
190
190
 
191
- Neither of these timeout mecanisms should be routinely relied on, and should
191
+ Neither of these timeout mechanisms should be routinely relied on, and should
192
192
  instead be considered as a last line of defense in case you application
193
193
  is impacted by bugs causing unexpectedly slow response time, or fully stuck
194
194
  processes.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative "../lib/pitchfork/mem_info"
2
3
 
3
4
  module App
data/examples/echo.ru CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Example application that echoes read data back to the HTTP client.
2
3
  # This emulates the old echo protocol people used to run.
3
4
  #
data/examples/hello.ru CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  run lambda { |env|
2
3
  /\A100-continue\z/i =~ env['HTTP_EXPECT'] and return [100, {}, []]
3
4
  body = "Hello World!\n"
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Minimal sample configuration file for Pitchfork
2
3
 
3
4
  # listen 2007 # by default Pitchfork listens on port 8080
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Sample verbose configuration file for Pitchfork
2
3
 
3
4
  # Use at least one worker per core if you're on a dedicated server,
data/exe/pitchfork CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # -*- encoding: binary -*-
3
4
  require 'pitchfork/launcher'
4
5
  require 'optparse'
@@ -11,10 +11,11 @@
11
11
  #if defined(HAVE_EPOLL_CREATE1)
12
12
  # include <sys/epoll.h>
13
13
  # include <errno.h>
14
- # include <ruby/io.h>
15
14
  # include <ruby/thread.h>
16
15
  #endif /* __linux__ */
17
16
 
17
+ #include <ruby/io.h>
18
+
18
19
  #if defined(EPOLLEXCLUSIVE) && defined(HAVE_EPOLL_CREATE1)
19
20
  # define USE_EPOLL (1)
20
21
  #else
@@ -50,7 +51,6 @@ static VALUE prep_readers(VALUE cls, VALUE readers)
50
51
  for (i = 0; i < RARRAY_LEN(readers); i++) {
51
52
  int rc;
52
53
  struct epoll_event e;
53
- rb_io_t *fptr;
54
54
  VALUE io = rb_ary_entry(readers, i);
55
55
 
56
56
  e.data.u64 = i; /* the reason readers shouldn't change */
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  require 'mkmf'
3
4
 
4
5
  have_const("PR_SET_CHILD_SUBREAPER", "sys/prctl.h")
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  # :enddoc:
3
4
  # This code is based on the original Rails handler in Mongrel
4
5
  # Copyright (c) 2005 Zed A. Shaw
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Pitchfork
4
5
  # This class keep tracks of the state of all the master children.
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  require 'logger'
3
4
 
4
5
  module Pitchfork
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Pitchfork
4
5
  module Const # :nodoc:
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'tempfile'
2
3
 
3
4
  module Pitchfork
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  # :enddoc:
3
4
  # no stable API here
4
5
 
@@ -19,10 +20,10 @@ module Pitchfork
19
20
  "SERVER_SOFTWARE" => "Pitchfork #{Pitchfork::Const::UNICORN_VERSION}"
20
21
  }
21
22
 
22
- NULL_IO = StringIO.new("")
23
+ NULL_IO = StringIO.new.binmode
23
24
 
24
25
  # :stopdoc:
25
- HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
26
+ HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ' ]
26
27
  EMPTY_ARRAY = [].freeze
27
28
  @@input_class = Pitchfork::TeeInput
28
29
  @@check_client_connection = false
@@ -104,7 +105,7 @@ module Pitchfork
104
105
  end
105
106
 
106
107
  def hijacked?
107
- env.include?('rack.hijack_io'.freeze)
108
+ env.include?('rack.hijack_io')
108
109
  end
109
110
 
110
111
  if Raindrops.const_defined?(:TCP_Info)
@@ -199,11 +200,11 @@ module Pitchfork
199
200
  val.downcase!
200
201
  end
201
202
 
202
- if vals.pop == 'chunked'.freeze
203
- return true unless vals.include?('chunked'.freeze)
203
+ if vals.pop == 'chunked'
204
+ return true unless vals.include?('chunked')
204
205
  raise Pitchfork::HttpParserError, 'double chunked', []
205
206
  end
206
- return false unless vals.include?('chunked'.freeze)
207
+ return false unless vals.include?('chunked')
207
208
  raise Pitchfork::HttpParserError, 'chunked not last', []
208
209
  end
209
210
  end
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  # :enddoc:
3
4
 
4
5
  module Pitchfork
@@ -48,10 +49,10 @@ module Pitchfork
48
49
  if headers
49
50
  code = status.to_i
50
51
  msg = STATUS_CODES[code]
51
- start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
52
+ start = req.response_start_sent ? '' : 'HTTP/1.1 '
52
53
  buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
53
54
  "Date: #{httpdate}\r\n" \
54
- "Connection: close\r\n"
55
+ "Connection: close\r\n".b
55
56
  headers.each do |key, value|
56
57
  case key
57
58
  when %r{\A(?:Date|Connection)\z}i
@@ -1,4 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
2
4
  require 'pitchfork/pitchfork_http'
3
5
  require 'pitchfork/flock'
4
6
  require 'pitchfork/soft_timeout'
@@ -91,7 +93,7 @@ module Pitchfork
91
93
  # in new projects
92
94
  LISTENERS = []
93
95
 
94
- NOOP = '.'.freeze
96
+ NOOP = '.'
95
97
 
96
98
  # :startdoc:
97
99
  # This Hash is considered a stable interface and changing its contents
@@ -512,6 +514,7 @@ module Pitchfork
512
514
  next
513
515
  else # worker is out of time
514
516
  next_sleep = 0
517
+ child.deadline = now + 1
515
518
  hard_timeout(child)
516
519
  end
517
520
  end
@@ -696,7 +699,7 @@ module Pitchfork
696
699
 
697
700
  def e103_response_write(client, headers)
698
701
  rss = @request.response_start_sent
699
- buf = rss ? "103 Early Hints\r\n" : "HTTP/1.1 103 Early Hints\r\n"
702
+ buf = (rss ? "103 Early Hints\r\n" : "HTTP/1.1 103 Early Hints\r\n").b
700
703
  headers.each { |key, value| append_header(buf, key, value) }
701
704
  buf << (rss ? "\r\nHTTP/1.1 ".freeze : "\r\n".freeze)
702
705
  client.write(buf)
@@ -710,7 +713,7 @@ module Pitchfork
710
713
  client.write(@request.response_start_sent ?
711
714
  "100 Continue\r\n\r\nHTTP/1.1 ".freeze :
712
715
  "HTTP/1.1 100 Continue\r\n\r\n".freeze)
713
- env.delete('HTTP_EXPECT'.freeze)
716
+ env.delete('HTTP_EXPECT')
714
717
  end
715
718
 
716
719
  # once a client is accepted, it is processed in its entirety here
@@ -732,7 +735,7 @@ module Pitchfork
732
735
  end
733
736
  end
734
737
 
735
- env["rack.after_reply"] = []
738
+ env["rack.response_finished"] = env["rack.after_reply"] = []
736
739
 
737
740
  status, headers, body = @app.call(env)
738
741
 
@@ -758,11 +761,21 @@ module Pitchfork
758
761
  client.close # flush and uncork socket immediately, no keepalive
759
762
  end
760
763
  env
761
- rescue => e
762
- handle_error(client, e)
764
+ rescue => application_error
765
+ handle_error(client, application_error)
763
766
  env
764
767
  ensure
765
- env["rack.after_reply"].each(&:call) if env
768
+ if env
769
+ env["rack.response_finished"].each do |callback|
770
+ if callback.arity == 0
771
+ callback.call
772
+ else
773
+ callback.call(env, status, headers, application_error)
774
+ end
775
+ rescue => callback_error
776
+ Pitchfork.log_error(@logger, "rack.after_reply error", callback_error)
777
+ end
778
+ end
766
779
  timeout_handler.finished
767
780
  env
768
781
  end
@@ -4,9 +4,6 @@ require 'pitchfork/shared_memory'
4
4
 
5
5
  module Pitchfork
6
6
  module Info
7
- @workers_count = 0
8
- @fork_safe = true
9
-
10
7
  class WeakSet # :nodoc
11
8
  def initialize
12
9
  @map = ObjectSpace::WeakMap.new
@@ -27,42 +24,68 @@ module Pitchfork
27
24
  end
28
25
  end
29
26
 
30
- @kept_ios = WeakSet.new
27
+ if RUBY_VERSION < "3.2.3" && RUBY_ENGINE == "ruby"
28
+ class << self
29
+ def keep_io(io)
30
+ io # noop
31
+ end
31
32
 
32
- class << self
33
- attr_accessor :workers_count
33
+ def keep_ios(ios)
34
+ ios # noop
35
+ end
34
36
 
35
- def keep_io(io)
36
- raise ArgumentError, "#{io.inspect} doesn't respond to :to_io" unless io.respond_to?(:to_io)
37
- @kept_ios << io
38
- io
39
- end
37
+ def close_all_ios!
38
+ raise NoMethodError, <<~MSG
39
+ Your Ruby version is subject to a bug that prevent `.close_all_ios!` from working.
40
+ See: https://bugs.ruby-lang.org/issues/19531.
40
41
 
41
- def keep_ios(ios)
42
- ios.each { |io| keep_io(io) }
42
+ Consider upgrading to Ruby 3.2.3+
43
+ MSG
44
+ end
43
45
  end
46
+ else
47
+ @kept_ios = WeakSet.new
48
+
49
+ class << self
50
+ def keep_io(io)
51
+ raise ArgumentError, "#{io.inspect} doesn't respond to :to_io" unless io.respond_to?(:to_io)
52
+ @kept_ios << io
53
+ io
54
+ end
44
55
 
45
- def close_all_ios!
46
- ignored_ios = [$stdin, $stdout, $stderr, STDIN, STDOUT, STDERR].uniq.compact
47
-
48
- @kept_ios.each do |io_like|
49
- ignored_ios << (io_like.is_a?(IO) ? io_like : io_like.to_io)
56
+ def keep_ios(ios)
57
+ ios.each { |io| keep_io(io) }
50
58
  end
51
59
 
52
- ObjectSpace.each_object(IO) do |io|
53
- if io_open?(io) && io_autoclosed?(io) && !ignored_ios.include?(io)
54
- if io.is_a?(TCPSocket)
55
- # If we inherited a TCP Socket, calling #close directly could send FIN or RST.
56
- # So we first reopen /dev/null to avoid that.
57
- io.reopen(File::NULL)
58
- end
59
- begin
60
- io.close
61
- rescue Errno::EBADF
60
+ def close_all_ios!
61
+ ignored_ios = [$stdin, $stdout, $stderr, STDIN, STDOUT, STDERR].uniq.compact
62
+
63
+ @kept_ios.each do |io_like|
64
+ ignored_ios << (io_like.is_a?(IO) ? io_like : io_like.to_io)
65
+ end
66
+
67
+ ObjectSpace.each_object(IO) do |io|
68
+ if io_open?(io) && io_autoclosed?(io) && !ignored_ios.include?(io)
69
+ if io.is_a?(TCPSocket)
70
+ # If we inherited a TCP Socket, calling #close directly could send FIN or RST.
71
+ # So we first reopen /dev/null to avoid that.
72
+ io.reopen(File::NULL)
73
+ end
74
+ begin
75
+ io.close
76
+ rescue Errno::EBADF
77
+ end
62
78
  end
63
79
  end
64
80
  end
65
81
  end
82
+ end
83
+
84
+ @workers_count = 0
85
+ @fork_safe = true
86
+
87
+ class << self
88
+ attr_accessor :workers_count
66
89
 
67
90
  def fork_safe?
68
91
  @fork_safe
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  # :enddoc:
4
5
  $stdout.sync = $stderr.sync = true
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  # :stopdoc:
4
5
  module Pitchfork
@@ -14,6 +15,7 @@ module Pitchfork
14
15
  FD = Struct.new(:index)
15
16
 
16
17
  def initialize(socket)
18
+ raise ArgumentError, "expected a socket, got: #{socket.inspect}" unless socket
17
19
  @socket = socket
18
20
  end
19
21
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Pitchfork
2
3
  # fallback for non-Linux and Linux <4.5 systems w/o EPOLLEXCLUSIVE
3
4
  class SelectWaiter # :nodoc:
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  # :enddoc:
3
4
  require 'socket'
4
5
 
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Pitchfork
4
5
  # When processing uploads, pitchfork may expose a StreamInput object under
@@ -17,7 +18,7 @@ module Pitchfork
17
18
  @socket = socket
18
19
  @parser = request
19
20
  @buf = request.buf
20
- @rbuf = ''
21
+ @rbuf = +''
21
22
  @bytes_read = 0
22
23
  filter_body(@rbuf, @buf) unless @buf.empty?
23
24
  end
@@ -41,7 +42,7 @@ module Pitchfork
41
42
  # ios.read(length [, buffer]) will return immediately if there is
42
43
  # any data and only block when nothing is available (providing
43
44
  # IO#readpartial semantics).
44
- def read(length = nil, rv = '')
45
+ def read(length = nil, rv = ''.b)
45
46
  if length
46
47
  if length <= @rbuf.size
47
48
  length < 0 and raise ArgumentError, "negative length #{length} given"
@@ -79,16 +80,16 @@ module Pitchfork
79
80
  # unlike IO#gets.
80
81
  def gets(sep = $/)
81
82
  if sep.nil?
82
- read_all(rv = '')
83
+ read_all(rv = ''.b)
83
84
  return rv.empty? ? nil : rv
84
85
  end
85
86
  re = /\A(.*?#{Regexp.escape(sep)})/
86
87
 
87
88
  begin
88
- @rbuf.sub!(re, '') and return $1
89
+ @rbuf.sub!(re, ''.b) and return $1
89
90
  return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof?
90
91
  @socket.readpartial(@@io_chunk_size, @buf) or eof!
91
- filter_body(once = '', @buf)
92
+ filter_body(once = ''.b, @buf)
92
93
  @rbuf << once
93
94
  end while true
94
95
  end
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Pitchfork
4
5
  # Acts like tee(1) on an input input to provide a input-like stream
@@ -42,7 +43,7 @@ module Pitchfork
42
43
  @len = request.content_length
43
44
  super
44
45
  @tmp = @len && @len <= @@client_body_buffer_size ?
45
- StringIO.new("") : new_tmpio
46
+ StringIO.new.binmode : new_tmpio
46
47
  end
47
48
 
48
49
  # :call-seq:
@@ -121,7 +122,7 @@ module Pitchfork
121
122
 
122
123
  # consumes the stream of the socket
123
124
  def consume!
124
- junk = ""
125
+ junk = "".b
125
126
  nil while read(@@io_chunk_size, junk)
126
127
  end
127
128
 
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  # :stopdoc:
3
4
  require 'tmpdir'
4
5
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pitchfork
4
- VERSION = "0.11.1"
4
+ VERSION = "0.13.0"
5
5
  module Const
6
6
  UNICORN_VERSION = '6.1.0'
7
7
  end
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  require 'pitchfork/shared_memory'
3
4
 
4
5
  module Pitchfork
data/lib/pitchfork.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  require 'etc'
3
4
  require 'stringio'
4
5
  require 'raindrops'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pitchfork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-06 00:00:00.000000000 Z
11
+ date: 2024-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raindrops