pitchfork 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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