pitchfork 0.1.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.

Potentially problematic release.


This version of pitchfork might be problematic. Click here for more details.

Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.git-blame-ignore-revs +3 -0
  3. data/.gitattributes +5 -0
  4. data/.github/workflows/ci.yml +30 -0
  5. data/.gitignore +23 -0
  6. data/COPYING +674 -0
  7. data/Dockerfile +4 -0
  8. data/Gemfile +9 -0
  9. data/Gemfile.lock +30 -0
  10. data/LICENSE +67 -0
  11. data/README.md +123 -0
  12. data/Rakefile +72 -0
  13. data/docs/Application_Timeouts.md +74 -0
  14. data/docs/CONFIGURATION.md +388 -0
  15. data/docs/DESIGN.md +86 -0
  16. data/docs/FORK_SAFETY.md +80 -0
  17. data/docs/PHILOSOPHY.md +90 -0
  18. data/docs/REFORKING.md +113 -0
  19. data/docs/SIGNALS.md +38 -0
  20. data/docs/TUNING.md +106 -0
  21. data/examples/constant_caches.ru +43 -0
  22. data/examples/echo.ru +25 -0
  23. data/examples/hello.ru +5 -0
  24. data/examples/nginx.conf +156 -0
  25. data/examples/pitchfork.conf.minimal.rb +5 -0
  26. data/examples/pitchfork.conf.rb +77 -0
  27. data/examples/unicorn.socket +11 -0
  28. data/exe/pitchfork +116 -0
  29. data/ext/pitchfork_http/CFLAGS +13 -0
  30. data/ext/pitchfork_http/c_util.h +116 -0
  31. data/ext/pitchfork_http/child_subreaper.h +25 -0
  32. data/ext/pitchfork_http/common_field_optimization.h +130 -0
  33. data/ext/pitchfork_http/epollexclusive.h +124 -0
  34. data/ext/pitchfork_http/ext_help.h +38 -0
  35. data/ext/pitchfork_http/extconf.rb +14 -0
  36. data/ext/pitchfork_http/global_variables.h +97 -0
  37. data/ext/pitchfork_http/httpdate.c +79 -0
  38. data/ext/pitchfork_http/pitchfork_http.c +4318 -0
  39. data/ext/pitchfork_http/pitchfork_http.rl +1024 -0
  40. data/ext/pitchfork_http/pitchfork_http_common.rl +76 -0
  41. data/lib/pitchfork/app/old_rails/static.rb +59 -0
  42. data/lib/pitchfork/children.rb +124 -0
  43. data/lib/pitchfork/configurator.rb +314 -0
  44. data/lib/pitchfork/const.rb +23 -0
  45. data/lib/pitchfork/http_parser.rb +206 -0
  46. data/lib/pitchfork/http_response.rb +63 -0
  47. data/lib/pitchfork/http_server.rb +822 -0
  48. data/lib/pitchfork/launcher.rb +9 -0
  49. data/lib/pitchfork/mem_info.rb +36 -0
  50. data/lib/pitchfork/message.rb +130 -0
  51. data/lib/pitchfork/mold_selector.rb +29 -0
  52. data/lib/pitchfork/preread_input.rb +33 -0
  53. data/lib/pitchfork/refork_condition.rb +21 -0
  54. data/lib/pitchfork/select_waiter.rb +9 -0
  55. data/lib/pitchfork/socket_helper.rb +199 -0
  56. data/lib/pitchfork/stream_input.rb +152 -0
  57. data/lib/pitchfork/tee_input.rb +133 -0
  58. data/lib/pitchfork/tmpio.rb +35 -0
  59. data/lib/pitchfork/version.rb +8 -0
  60. data/lib/pitchfork/worker.rb +244 -0
  61. data/lib/pitchfork.rb +158 -0
  62. data/pitchfork.gemspec +30 -0
  63. metadata +137 -0
@@ -0,0 +1,133 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module Pitchfork
4
+ # Acts like tee(1) on an input input to provide a input-like stream
5
+ # while providing rewindable semantics through a File/StringIO backing
6
+ # store. On the first pass, the input is only read on demand so your
7
+ # Rack application can use input notification (upload progress and
8
+ # like). This should fully conform to the Rack::Lint::InputWrapper
9
+ # specification on the public API. This class is intended to be a
10
+ # strict interpretation of Rack::Lint::InputWrapper functionality and
11
+ # will not support any deviations from it.
12
+ #
13
+ # When processing uploads, pitchfork exposes a TeeInput object under
14
+ # "rack.input" of the Rack environment by default.
15
+ class TeeInput < StreamInput
16
+ # The maximum size (in +bytes+) to buffer in memory before
17
+ # resorting to a temporary file. Default is 112 kilobytes.
18
+ @@client_body_buffer_size = Pitchfork::Const::MAX_BODY # :nodoc:
19
+
20
+ # sets the maximum size of request bodies to buffer in memory,
21
+ # amounts larger than this are buffered to the filesystem
22
+ def self.client_body_buffer_size=(bytes) # :nodoc:
23
+ @@client_body_buffer_size = bytes
24
+ end
25
+
26
+ # returns the maximum size of request bodies to buffer in memory,
27
+ # amounts larger than this are buffered to the filesystem
28
+ def self.client_body_buffer_size # :nodoc:
29
+ @@client_body_buffer_size
30
+ end
31
+
32
+ # for Rack::TempfileReaper in rack 1.6+
33
+ def new_tmpio # :nodoc:
34
+ tmpio = Pitchfork::TmpIO.new
35
+ (@parser.env['rack.tempfiles'] ||= []) << tmpio
36
+ tmpio
37
+ end
38
+
39
+ # Initializes a new TeeInput object. You normally do not have to call
40
+ # this unless you are writing an HTTP server.
41
+ def initialize(socket, request) # :nodoc:
42
+ @len = request.content_length
43
+ super
44
+ @tmp = @len && @len <= @@client_body_buffer_size ?
45
+ StringIO.new("") : new_tmpio
46
+ end
47
+
48
+ # :call-seq:
49
+ # ios.size => Integer
50
+ #
51
+ # Returns the size of the input. For requests with a Content-Length
52
+ # header value, this will not read data off the socket and just return
53
+ # the value of the Content-Length header as an Integer.
54
+ #
55
+ # For Transfer-Encoding:chunked requests, this requires consuming
56
+ # all of the input stream before returning since there's no other
57
+ # way to determine the size of the request body beforehand.
58
+ #
59
+ # This method is no longer part of the Rack specification as of
60
+ # Rack 1.2, so its use is not recommended. This method only exists
61
+ # for compatibility with Rack applications designed for Rack 1.1 and
62
+ # earlier. Most applications should only need to call +read+ with a
63
+ # specified +length+ in a loop until it returns +nil+.
64
+ def size
65
+ @len and return @len
66
+ pos = @tmp.pos
67
+ consume!
68
+ @tmp.pos = pos
69
+ @len = @tmp.size
70
+ end
71
+
72
+ # :call-seq:
73
+ # ios.read([length [, buffer ]]) => string, buffer, or nil
74
+ #
75
+ # Reads at most length bytes from the I/O stream, or to the end of
76
+ # file if length is omitted or is nil. length must be a non-negative
77
+ # integer or nil. If the optional buffer argument is present, it
78
+ # must reference a String, which will receive the data.
79
+ #
80
+ # At end of file, it returns nil or "" depend on length.
81
+ # ios.read() and ios.read(nil) returns "".
82
+ # ios.read(length [, buffer]) returns nil.
83
+ #
84
+ # If the Content-Length of the HTTP request is known (as is the common
85
+ # case for POST requests), then ios.read(length [, buffer]) will block
86
+ # until the specified length is read (or it is the last chunk).
87
+ # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
88
+ # ios.read(length [, buffer]) will return immediately if there is
89
+ # any data and only block when nothing is available (providing
90
+ # IO#readpartial semantics).
91
+ def read(*args)
92
+ @socket ? tee(super) : @tmp.read(*args)
93
+ end
94
+
95
+ # :call-seq:
96
+ # ios.gets => string or nil
97
+ #
98
+ # Reads the next ``line'' from the I/O stream; lines are separated
99
+ # by the global record separator ($/, typically "\n"). A global
100
+ # record separator of nil reads the entire unread contents of ios.
101
+ # Returns nil if called at the end of file.
102
+ # This takes zero arguments for strict Rack::Lint compatibility,
103
+ # unlike IO#gets.
104
+ def gets
105
+ @socket ? tee(super) : @tmp.gets
106
+ end
107
+
108
+ # :call-seq:
109
+ # ios.rewind => 0
110
+ #
111
+ # Positions the *ios* pointer to the beginning of input, returns
112
+ # the offset (zero) of the +ios+ pointer. Subsequent reads will
113
+ # start from the beginning of the previously-buffered input.
114
+ def rewind
115
+ return 0 if 0 == @tmp.size
116
+ consume! if @socket
117
+ @tmp.rewind # Rack does not specify what the return value is here
118
+ end
119
+
120
+ private
121
+
122
+ # consumes the stream of the socket
123
+ def consume!
124
+ junk = ""
125
+ nil while read(@@io_chunk_size, junk)
126
+ end
127
+
128
+ def tee(buffer)
129
+ @tmp.write(buffer) if buffer
130
+ buffer
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: binary -*-
2
+ # :stopdoc:
3
+ require 'tmpdir'
4
+
5
+ module Pitchfork
6
+ # some versions of Ruby had a broken Tempfile which didn't work
7
+ # well with unlinked files. This one is much shorter, easier
8
+ # to understand, and slightly faster.
9
+ class TmpIO < File
10
+
11
+ # creates and returns a new File object. The File is unlinked
12
+ # immediately, switched to binary mode, and userspace output
13
+ # buffering is disabled
14
+ def self.new
15
+ path = nil
16
+
17
+ # workaround File#path being tainted:
18
+ # https://bugs.ruby-lang.org/issues/14485
19
+ fp = begin
20
+ path = "#{Dir::tmpdir}/#{rand}"
21
+ super(path, RDWR|CREAT|EXCL, 0600)
22
+ rescue Errno::EEXIST
23
+ retry
24
+ end
25
+
26
+ unlink(path)
27
+ fp.binmode
28
+ fp.sync = true
29
+ fp
30
+ end
31
+
32
+ # pretend we're Tempfile for Rack::TempfileReaper
33
+ alias close! close
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pitchfork
4
+ VERSION = "0.1.0"
5
+ module Const
6
+ UNICORN_VERSION = '6.1.0'
7
+ end
8
+ end
@@ -0,0 +1,244 @@
1
+ # -*- encoding: binary -*-
2
+ require "raindrops"
3
+
4
+ module Pitchfork
5
+ # This class and its members can be considered a stable interface
6
+ # and will not change in a backwards-incompatible fashion between
7
+ # releases of pitchfork. Knowledge of this class is generally not
8
+ # not needed for most users of pitchfork.
9
+ #
10
+ # Some users may want to access it in the before_fork/after_fork hooks.
11
+ # See the Pitchfork::Configurator RDoc for examples.
12
+ class Worker
13
+ # :stopdoc:
14
+ EXIT_SIGNALS = [:QUIT, :TERM]
15
+ @generation = 0
16
+ attr_accessor :nr, :pid, :generation
17
+ attr_reader :master
18
+
19
+ def initialize(nr, pid: nil, generation: 0)
20
+ @nr = nr
21
+ @pid = pid
22
+ @generation = generation
23
+ @mold = false
24
+ @to_io = @master = nil
25
+ @exiting = false
26
+ if nr
27
+ build_raindrops(nr)
28
+ else
29
+ promoted!
30
+ end
31
+ end
32
+
33
+ def meminfo
34
+ @meminfo ||= MemInfo.new(pid) if pid
35
+ end
36
+
37
+ def refresh
38
+ meminfo&.update
39
+ end
40
+
41
+ def exiting?
42
+ @exiting
43
+ end
44
+
45
+ def update(message)
46
+ message.class.members.each do |member|
47
+ send("#{member}=", message.public_send(member))
48
+ end
49
+
50
+ case message
51
+ when Message::WorkerPromoted, Message::PromoteWorker
52
+ promoted!
53
+ end
54
+ end
55
+
56
+ def register_to_master(control_socket)
57
+ create_socketpair!
58
+ message = Message::WorkerSpawned.new(@nr, Process.pid, generation, @master)
59
+ control_socket.sendmsg(message)
60
+ @master.close
61
+ end
62
+
63
+ def acknowlege_promotion(control_socket)
64
+ message = Message::WorkerPromoted.new(@nr, Process.pid, generation)
65
+ control_socket.sendmsg(message)
66
+ end
67
+
68
+ def promote(generation)
69
+ send_message_nonblock(Message::PromoteWorker.new(generation))
70
+ end
71
+
72
+ def spawn_worker(new_worker)
73
+ send_message_nonblock(Message::SpawnWorker.new(new_worker.nr))
74
+ end
75
+
76
+ def promoted!
77
+ @mold = true
78
+ @nr = nil
79
+ @drop_offset = 0
80
+ @tick_drop = MOLD_DROP
81
+ end
82
+
83
+ def mold?
84
+ @mold
85
+ end
86
+
87
+ def to_io # IO.select-compatible
88
+ @to_io.to_io
89
+ end
90
+
91
+ # master fakes SIGQUIT using this
92
+ def quit # :nodoc:
93
+ @master = @master.close if @master
94
+ end
95
+
96
+ def master=(socket)
97
+ @master = MessageSocket.new(socket)
98
+ end
99
+
100
+ # call a signal handler immediately without triggering EINTR
101
+ # We do not use the more obvious Process.kill(sig, $$) here since
102
+ # that signal delivery may be deferred. We want to avoid signal delivery
103
+ # while the Rack app.call is running because some database drivers
104
+ # (e.g. ruby-pg) may cancel pending requests.
105
+ def fake_sig(sig) # :nodoc:
106
+ old_cb = trap(sig, "IGNORE")
107
+ old_cb.call
108
+ ensure
109
+ trap(sig, old_cb)
110
+ end
111
+
112
+ # master sends fake signals to children
113
+ def soft_kill(sig) # :nodoc:
114
+ signum = Signal.list[sig.to_s] or raise ArgumentError, "BUG: bad signal: #{sig.inspect}"
115
+
116
+ # Do not care in the odd case the buffer is full, here.
117
+ success = send_message_nonblock(Message::SoftKill.new(signum))
118
+ if success && EXIT_SIGNALS.include?(sig)
119
+ @exiting = true
120
+ end
121
+ success
122
+ end
123
+
124
+ # this only runs when the Rack app.call is not running
125
+ # act like a listener
126
+ def accept_nonblock(exception: nil) # :nodoc:
127
+ loop do
128
+ case buf = @to_io.recvmsg_nonblock(exception: false)
129
+ when :wait_readable # keep waiting
130
+ return false
131
+ when nil # EOF master died, but we are at a safe place to exit
132
+ fake_sig(:QUIT)
133
+ return false
134
+ when Message::SoftKill
135
+ # trigger the signal handler
136
+ fake_sig(buf.signum)
137
+ # keep looping, more signals may be queued
138
+ when Message
139
+ return buf
140
+ else
141
+ raise TypeError, "Unexpected recvmsg_nonblock returns: #{buf.inspect}"
142
+ end
143
+ end # loop, as multiple signals may be sent
144
+ rescue Errno::ECONNRESET
145
+ nil
146
+ end
147
+
148
+ # worker objects may be compared to just plain Integers
149
+ def ==(other) # :nodoc:
150
+ super || (!@nr.nil? && @nr == other)
151
+ end
152
+
153
+ # called in the worker process
154
+ def tick=(value) # :nodoc:
155
+ if mold?
156
+ MOLD_DROP[0] = value
157
+ else
158
+ @tick_drop[@drop_offset] = value
159
+ end
160
+ end
161
+
162
+ # called in the master process
163
+ def tick # :nodoc:
164
+ if mold?
165
+ MOLD_DROP[0]
166
+ else
167
+ @tick_drop[@drop_offset]
168
+ end
169
+ end
170
+
171
+ def reset
172
+ @requests_drop[@drop_offset] = 0
173
+ end
174
+
175
+ def requests_count
176
+ @requests_drop[@drop_offset]
177
+ end
178
+
179
+ def increment_requests_count
180
+ @requests_drop.incr(@drop_offset)
181
+ end
182
+
183
+ # called in both the master (reaping worker) and worker (SIGQUIT handler)
184
+ def close # :nodoc:
185
+ @master.close if @master
186
+ @to_io.close if @to_io
187
+ end
188
+
189
+ def create_socketpair!
190
+ @to_io, @master = Pitchfork.socketpair
191
+ end
192
+
193
+ def after_fork_in_child
194
+ @master.close
195
+ end
196
+
197
+ private
198
+
199
+ def pipe=(socket)
200
+ @master = MessageSocket.new(socket)
201
+ end
202
+
203
+ def send_message_nonblock(message)
204
+ success = false
205
+ begin
206
+ case @master.sendmsg_nonblock(message, exception: false)
207
+ when :wait_writable
208
+ else
209
+ success = true
210
+ end
211
+ rescue Errno::EPIPE
212
+ # worker will be reaped soon
213
+ end
214
+ success
215
+ end
216
+
217
+ MOLD_DROP = Raindrops.new(1)
218
+ PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
219
+ TICK_DROPS = []
220
+ REQUEST_DROPS = []
221
+
222
+ class << self
223
+ # Since workers are created from another process, we have to
224
+ # pre-allocate the drops so they are shared between everyone.
225
+ #
226
+ # However this doesn't account for TTIN signals that increase the
227
+ # number of workers, but we should probably remove that feature too.
228
+ def preallocate_drops(workers_count)
229
+ 0.upto(workers_count / PER_DROP) do |i|
230
+ TICK_DROPS[i] = Raindrops.new(PER_DROP)
231
+ REQUEST_DROPS[i] = Raindrops.new(PER_DROP)
232
+ end
233
+ end
234
+ end
235
+
236
+ def build_raindrops(drop_nr)
237
+ drop_index = drop_nr / PER_DROP
238
+ @drop_offset = drop_nr % PER_DROP
239
+ @tick_drop = TICK_DROPS[drop_index] ||= Raindrops.new(PER_DROP)
240
+ @requests_drop = REQUEST_DROPS[drop_index] ||= Raindrops.new(PER_DROP)
241
+ @tick_drop[@drop_offset] = @requests_drop[@drop_offset] = 0
242
+ end
243
+ end
244
+ end
data/lib/pitchfork.rb ADDED
@@ -0,0 +1,158 @@
1
+ # -*- encoding: binary -*-
2
+ require 'etc'
3
+ require 'stringio'
4
+ require 'raindrops'
5
+ require 'io/wait'
6
+
7
+ begin
8
+ require 'rack'
9
+ rescue LoadError
10
+ warn 'rack not available, functionality reduced'
11
+ end
12
+
13
+ # :stopdoc:
14
+ # Pitchfork module containing all of the classes (include C extensions) for
15
+ # running a Pitchfork web server. It contains a minimalist HTTP server with just
16
+ # enough functionality to service web application requests fast as possible.
17
+ # :startdoc:
18
+
19
+ # pitchfork exposes very little of an user-visible API and most of its
20
+ # internals are subject to change. pitchfork is designed to host Rack
21
+ # applications, so applications should be written against the Rack SPEC
22
+ # and not pitchfork internals.
23
+ module Pitchfork
24
+
25
+ # Raised inside TeeInput when a client closes the socket inside the
26
+ # application dispatch. This is always raised with an empty backtrace
27
+ # since there is nothing in the application stack that is responsible
28
+ # for client shutdowns/disconnects. This exception is visible to Rack
29
+ # applications unless PrereadInput middleware is loaded. This
30
+ # is a subclass of the standard EOFError class and applications should
31
+ # not rescue it explicitly, but rescue EOFError instead.
32
+ ClientShutdown = Class.new(EOFError)
33
+
34
+ BootFailure = Class.new(StandardError)
35
+
36
+ # :stopdoc:
37
+
38
+ # This returns a lambda to pass in as the app, this does not "build" the
39
+ # app The returned lambda will be called when it is
40
+ # time to build the app.
41
+ def self.builder(ru, op)
42
+ # allow Configurator to parse cli switches embedded in the ru file
43
+ op = Pitchfork::Configurator::RACKUP.merge!(:file => ru, :optparse => op)
44
+ if ru =~ /\.ru$/ && !defined?(Rack::Builder)
45
+ abort "rack and Rack::Builder must be available for processing #{ru}"
46
+ end
47
+
48
+ # always called after config file parsing, may be called after forking
49
+ lambda do |_, server|
50
+ inner_app = case ru
51
+ when /\.ru$/
52
+ raw = File.read(ru)
53
+ raw.sub!(/^__END__\n.*/, '')
54
+ eval("Rack::Builder.new {(\n#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
55
+ else
56
+ require ru
57
+ Object.const_get(File.basename(ru, '.rb').capitalize)
58
+ end
59
+
60
+ if $DEBUG
61
+ require 'pp'
62
+ pp({ :inner_app => inner_app })
63
+ end
64
+
65
+ return inner_app unless server.default_middleware
66
+
67
+ middleware = { # order matters
68
+ ContentLength: nil,
69
+ Chunked: nil,
70
+ CommonLogger: [ $stderr ],
71
+ ShowExceptions: nil,
72
+ Lint: nil,
73
+ TempfileReaper: nil,
74
+ }
75
+
76
+ # return value, matches rackup defaults based on env
77
+ # Pitchfork does not support persistent connections, but Rainbows!
78
+ # and Zbatery both do. Users accustomed to the Rack::Server default
79
+ # middlewares will need ContentLength/Chunked middlewares.
80
+ case ENV["RACK_ENV"]
81
+ when "development"
82
+ when "deployment"
83
+ middleware.delete(:ShowExceptions)
84
+ middleware.delete(:Lint)
85
+ else
86
+ return inner_app
87
+ end
88
+ Rack::Builder.new do
89
+ middleware.each do |m, args|
90
+ use(Rack.const_get(m), *args) if Rack.const_defined?(m)
91
+ end
92
+ run inner_app
93
+ end.to_app
94
+ end
95
+ end
96
+
97
+ # returns an array of strings representing TCP listen socket addresses
98
+ # and Unix domain socket paths. This is useful for use with
99
+ # Raindrops::Middleware under Linux: https://yhbt.net/raindrops/
100
+ def self.listener_names
101
+ Pitchfork::HttpServer::LISTENERS.map do |io|
102
+ Pitchfork::SocketHelper.sock_name(io)
103
+ end
104
+ end
105
+
106
+ def self.log_error(logger, prefix, exc)
107
+ message = exc.message
108
+ message = message.dump if /[[:cntrl:]]/ =~ message
109
+ logger.error "#{prefix}: #{message} (#{exc.class})"
110
+ exc.backtrace.each { |line| logger.error(line) }
111
+ end
112
+
113
+ F_SETPIPE_SZ = 1031 if RUBY_PLATFORM =~ /linux/
114
+
115
+ def self.pipe # :nodoc:
116
+ IO.pipe.each do |io|
117
+ # shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft
118
+ # limits.
119
+ if defined?(F_SETPIPE_SZ)
120
+ begin
121
+ io.fcntl(F_SETPIPE_SZ, Raindrops::PAGE_SIZE)
122
+ rescue Errno::EINVAL
123
+ # old kernel
124
+ rescue Errno::EPERM
125
+ # resizes fail if Linux is close to the pipe limit for the user
126
+ # or if the user does not have permissions to resize
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ @socket_type = :SOCK_SEQPACKET
133
+ def self.socketpair
134
+ pair = UNIXSocket.socketpair(@socket_type).map { |s| MessageSocket.new(s) }
135
+ pair[0].close_write
136
+ pair[1].close_read
137
+ pair
138
+ rescue Errno::EPROTONOSUPPORT
139
+ if @socket_type == :SOCK_SEQPACKET
140
+ # macOS and very old linuxes don't support SOCK_SEQPACKET (SCTP).
141
+ # In such case we can fallback to SOCK_STREAM (TCP)
142
+ warn("SEQPACKET (SCTP) isn't supported, falling back to STREAM")
143
+ @socket_type = :SOCK_STREAM
144
+ retry
145
+ else
146
+ raise
147
+ end
148
+ end
149
+ # :startdoc:
150
+ end
151
+ # :enddoc:
152
+
153
+ %w(
154
+ const socket_helper stream_input tee_input mem_info children message http_parser
155
+ refork_condition mold_selector configurator tmpio http_response worker http_server
156
+ ).each do |s|
157
+ require_relative "pitchfork/#{s}"
158
+ end
data/pitchfork.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/pitchfork/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{pitchfork}
7
+ s.version = Pitchfork::VERSION
8
+ s.authors = ['Jean Boussier']
9
+ s.email = ["jean.boussier@gmail.com"]
10
+ s.summary = 'Rack HTTP server for fast clients and Unix'
11
+ s.description = File.read('README.md').split("\n\n")[1]
12
+ s.extensions = %w(ext/pitchfork_http/extconf.rb)
13
+ s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
14
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features|bin|\.)/}) }
15
+ end
16
+ s.bindir = "exe"
17
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ s.homepage = 'https://github.com/Shopify/pitchfork'
19
+
20
+ s.required_ruby_version = ">= 2.5.0"
21
+
22
+ s.add_dependency(%q<raindrops>, '~> 0.7')
23
+ s.add_dependency(%q<rack>, '>= 2.0')
24
+
25
+ # Note: To avoid ambiguity, we intentionally avoid the SPDX-compatible
26
+ # 'Ruby' here since Ruby 1.9.3 switched to BSD-2-Clause, but we
27
+ # inherited our license from Mongrel when Ruby was at 1.8.
28
+ # We cannot automatically switch licenses when Ruby changes.
29
+ s.licenses = ['GPL-2.0+', 'Ruby']
30
+ end