pitchfork 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +7 -0
- data/Dockerfile +1 -1
- data/Gemfile.lock +7 -6
- data/Rakefile +10 -2
- data/docs/CONFIGURATION.md +30 -32
- data/examples/pitchfork.conf.rb +2 -2
- data/exe/pitchfork +1 -8
- data/ext/pitchfork_http/epollexclusive.h +13 -17
- data/ext/pitchfork_http/pitchfork_http.c +196 -192
- data/ext/pitchfork_http/pitchfork_http.rl +23 -19
- data/lib/pitchfork/configurator.rb +28 -36
- data/lib/pitchfork/flock.rb +51 -0
- data/lib/pitchfork/http_server.rb +87 -70
- data/lib/pitchfork/refork_condition.rb +3 -3
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork/worker.rb +21 -16
- data/lib/pitchfork.rb +25 -31
- metadata +4 -4
- data/lib/pitchfork/mold_selector.rb +0 -29
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f9f15127612a30958ee43b9667990a0728b5fdfc46e13285bdfa1706085eb67d
         | 
| 4 | 
            +
              data.tar.gz: '08ba863ccf209644093b83ee905305615cfef046c4cd5e11e74f16540c722ad7'
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6c73e64f686acde16c5bb50df83a3a8596d9a3f578318cdc55bc3d8dbee1a0d7dd8534e627b5baa4994f3f30d4917ffe7d4e5d405850d068f7523cf52c21113b
         | 
| 7 | 
            +
              data.tar.gz: 80c872414fe8289c7c98cb9949309f76aeec63b3f3de85f09349839f873721cdb41c12bfa4620c7863d62526533c7e4ee900b7f5461917d348fa79d4c5977e4a
         | 
    
        data/.github/workflows/ci.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,12 @@ | |
| 1 1 | 
             
            # Unreleased
         | 
| 2 2 |  | 
| 3 | 
            +
            - Remove default middlewares.
         | 
| 4 | 
            +
            - Refork indefinitely when `refork_after` is set, unless the last element is `false`.
         | 
| 5 | 
            +
            - Remove `mold_selector`. The promotion logic has been moved inside workers (#38).
         | 
| 6 | 
            +
            - Add the `after_promotion` callback.
         | 
| 7 | 
            +
            - Removed the `before_fork` callback.
         | 
| 8 | 
            +
            - Fork workers and molds with a clean stack to allow more generations. (#30)
         | 
| 9 | 
            +
             | 
| 3 10 | 
             
            # 0.1.2
         | 
| 4 11 |  | 
| 5 12 | 
             
            - Improve Ruby 3.2 and Rack 3 compatibility.
         | 
    
        data/Dockerfile
    CHANGED
    
    
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,26 +1,27 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: .
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                pitchfork (0. | 
| 4 | 
            +
                pitchfork (0.2.0)
         | 
| 5 5 | 
             
                  rack (>= 2.0)
         | 
| 6 6 | 
             
                  raindrops (~> 0.7)
         | 
| 7 7 |  | 
| 8 8 | 
             
            GEM
         | 
| 9 9 | 
             
              remote: https://rubygems.org/
         | 
| 10 10 | 
             
              specs:
         | 
| 11 | 
            -
                minitest (5. | 
| 11 | 
            +
                minitest (5.15.0)
         | 
| 12 12 | 
             
                nio4r (2.5.8)
         | 
| 13 | 
            -
                puma ( | 
| 13 | 
            +
                puma (6.1.1)
         | 
| 14 14 | 
             
                  nio4r (~> 2.0)
         | 
| 15 | 
            -
                rack (3.0. | 
| 16 | 
            -
                raindrops (0.20. | 
| 15 | 
            +
                rack (3.0.7)
         | 
| 16 | 
            +
                raindrops (0.20.1)
         | 
| 17 17 | 
             
                rake (13.0.6)
         | 
| 18 | 
            -
                rake-compiler (1.2. | 
| 18 | 
            +
                rake-compiler (1.2.1)
         | 
| 19 19 | 
             
                  rake
         | 
| 20 20 |  | 
| 21 21 | 
             
            PLATFORMS
         | 
| 22 22 | 
             
              aarch64-linux
         | 
| 23 23 | 
             
              arm64-darwin-21
         | 
| 24 | 
            +
              arm64-darwin-22
         | 
| 24 25 | 
             
              x86_64-linux
         | 
| 25 26 |  | 
| 26 27 | 
             
            DEPENDENCIES
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -11,6 +11,14 @@ Rake::TestTask.new("test:unit") do |t| | |
| 11 11 | 
             
              t.warning = true
         | 
| 12 12 | 
             
            end
         | 
| 13 13 |  | 
| 14 | 
            +
            Rake::TestTask.new("test:integration") do |t|
         | 
| 15 | 
            +
              t.libs << "test"
         | 
| 16 | 
            +
              t.libs << "lib"
         | 
| 17 | 
            +
              t.test_files = FileList["test/integration/**/test_*.rb"]
         | 
| 18 | 
            +
              t.options = '-v' if ENV['CI'] || ENV['VERBOSE']
         | 
| 19 | 
            +
              t.warning = true
         | 
| 20 | 
            +
            end
         | 
| 21 | 
            +
             | 
| 14 22 | 
             
            namespace :test do
         | 
| 15 23 | 
             
              # It's not so much that these tests are slow, but they tend to fork
         | 
| 16 24 | 
             
              # and/or register signal handlers, so they if something goes wrong
         | 
| @@ -32,7 +40,7 @@ namespace :test do | |
| 32 40 | 
             
              # It's quite hard to work with and it would be good to convert all this
         | 
| 33 41 | 
             
              # to Ruby integration tests, but while pitchfork is a moving target it's
         | 
| 34 42 | 
             
              # preferable to edit the test suite as little as possible.
         | 
| 35 | 
            -
              task  | 
| 43 | 
            +
              task legacy_integration: :compile do
         | 
| 36 44 | 
             
                File.write("test/integration/random_blob", File.read("/dev/random", 1_000_000))
         | 
| 37 45 | 
             
                lib = File.expand_path("lib", __dir__)
         | 
| 38 46 | 
             
                path = "#{File.expand_path("exe", __dir__)}:#{ENV["PATH"]}"
         | 
| @@ -67,6 +75,6 @@ task :ragel do | |
| 67 75 | 
             
              end
         | 
| 68 76 | 
             
            end
         | 
| 69 77 |  | 
| 70 | 
            -
            task test: %i(test:unit test:slow test:integration)
         | 
| 78 | 
            +
            task test: %i(test:unit test:slow test:integration test:legacy_integration)
         | 
| 71 79 |  | 
| 72 80 | 
             
            task default: %i(ragel compile test)
         | 
    
        data/docs/CONFIGURATION.md
    CHANGED
    
    | @@ -238,20 +238,27 @@ application that are used in hooks. | |
| 238 238 | 
             
            `pitchfork` also don't attempt to rescue hook errors. Raising from a worker hook will crash the worker,
         | 
| 239 239 | 
             
            and raising from a master hook will bring the whole cluster down.
         | 
| 240 240 |  | 
| 241 | 
            -
            ### ` | 
| 241 | 
            +
            ### `after_promotion`
         | 
| 242 242 |  | 
| 243 243 | 
             
            ```ruby
         | 
| 244 | 
            -
             | 
| 244 | 
            +
            after_promotion do |server, mold|
         | 
| 245 245 | 
             
              Database.disconnect!
         | 
| 246 246 | 
             
            end
         | 
| 247 247 | 
             
            ```
         | 
| 248 248 |  | 
| 249 | 
            -
            Called in the context of the  | 
| 249 | 
            +
            Called in the context of the mold when initially spawned or promotted.
         | 
| 250 | 
            +
             | 
| 251 | 
            +
            It's usage is similar to a `before_fork` callback found on other servers
         | 
| 252 | 
            +
            but it is called once on promotion rather than before forking each worker.
         | 
| 253 | 
            +
             | 
| 250 254 | 
             
            For most protocols connections can be closed after fork, but some
         | 
| 251 255 | 
             
            stateful protocols require to close connections before fork.
         | 
| 252 256 |  | 
| 253 257 | 
             
            That is the case for instance of many SQL databases protocols.
         | 
| 254 258 |  | 
| 259 | 
            +
            This is also the callback in which memory optimizations, such as
         | 
| 260 | 
            +
            heap compaction should be done.
         | 
| 261 | 
            +
             | 
| 255 262 | 
             
            ### `after_fork`
         | 
| 256 263 |  | 
| 257 264 | 
             
            ```ruby
         | 
| @@ -263,6 +270,19 @@ end | |
| 263 270 | 
             
            Called in the worker after forking. Generally used to close inherited connections
         | 
| 264 271 | 
             
            or to restart backgrounds threads for libraries that don't do it automatically.
         | 
| 265 272 |  | 
| 273 | 
            +
            ### `after_promotion`
         | 
| 274 | 
            +
             | 
| 275 | 
            +
            ```ruby
         | 
| 276 | 
            +
            after_promotion do |server, mold|
         | 
| 277 | 
            +
              NetworkClient.disconnect!
         | 
| 278 | 
            +
              4.times { GC.start } # promote surviving objects to oldgen
         | 
| 279 | 
            +
              GC.compact
         | 
| 280 | 
            +
            end
         | 
| 281 | 
            +
            ```
         | 
| 282 | 
            +
             | 
| 283 | 
            +
            Called in the worker after it was promoted into a mold. Generally used to shutdown
         | 
| 284 | 
            +
            open connections and file descriptors, as well as to perform memory optimiations
         | 
| 285 | 
            +
            such as compacting the heap, trimming memory etc.
         | 
| 266 286 |  | 
| 267 287 | 
             
            ### `after_worker_ready`
         | 
| 268 288 |  | 
| @@ -302,9 +322,15 @@ once at least one worker processed `50` requests. | |
| 302 322 |  | 
| 303 323 | 
             
            Each element is a limit for the next generation. On the example above a new generation
         | 
| 304 324 | 
             
            is triggered when a worker has processed 50 requests, then the second generation when
         | 
| 305 | 
            -
            a worker from the new generation processed an additional 100 requests and finally after  | 
| 325 | 
            +
            a worker from the new generation processed an additional 100 requests and finally after *every*
         | 
| 306 326 | 
             
            1000 requests.
         | 
| 307 327 |  | 
| 328 | 
            +
            If you don't want unlimited reforking, you can set `false` as the last element of the array:
         | 
| 329 | 
            +
             | 
| 330 | 
            +
            ```ruby
         | 
| 331 | 
            +
            refork_after [50, 100, 1000, false]
         | 
| 332 | 
            +
            ```
         | 
| 333 | 
            +
             | 
| 308 334 | 
             
            Generally speaking Copy-on-Write efficiency tend to degrade fast during the early requests,
         | 
| 309 335 | 
             
            and then less and less frequently.
         | 
| 310 336 |  | 
| @@ -314,36 +340,8 @@ By default automatic reforking isn't enabled. | |
| 314 340 |  | 
| 315 341 | 
             
            Make sure to read the [fork safety guide](FORK_SAFETY.md) before enabling reforking.
         | 
| 316 342 |  | 
| 317 | 
            -
            ### `mold_selector`
         | 
| 318 | 
            -
             | 
| 319 | 
            -
            Sets the mold selector implementation.
         | 
| 320 | 
            -
             | 
| 321 | 
            -
            ```ruby
         | 
| 322 | 
            -
            mold_selector do |server|
         | 
| 323 | 
            -
              candidate = server.children.workers.sample # return an random worker
         | 
| 324 | 
            -
              server.logger.info("worker=#{worker.nr} pid=#{worker.pid} selected as new mold")
         | 
| 325 | 
            -
              candidate
         | 
| 326 | 
            -
            end
         | 
| 327 | 
            -
            ```
         | 
| 328 | 
            -
             | 
| 329 | 
            -
            The has access to `server.children` a `Pitchfork::Children` instance.
         | 
| 330 | 
            -
            This object can be used to introspect the state of the cluster and select the most
         | 
| 331 | 
            -
            appropriate worker to be used as the new mold from which workers will be reforked.
         | 
| 332 | 
            -
             | 
| 333 | 
            -
            The default implementation selects the worker with the least
         | 
| 334 | 
            -
            amount of shared memory. This heuristic aim to select the most
         | 
| 335 | 
            -
            warmed up worker.
         | 
| 336 | 
            -
             | 
| 337 | 
            -
            This should be considered a very advanced API and it is discouraged
         | 
| 338 | 
            -
            to use it unless you are confident you have a clear understanding
         | 
| 339 | 
            -
            of pitchfork's architecture.
         | 
| 340 | 
            -
             | 
| 341 343 | 
             
            ## Rack Features
         | 
| 342 344 |  | 
| 343 | 
            -
            ### `default_middleware`
         | 
| 344 | 
            -
             | 
| 345 | 
            -
            Sets whether to add Pitchfork's default middlewares. Defaults to `true`.
         | 
| 346 | 
            -
             | 
| 347 345 | 
             
            ### `early_hints`
         | 
| 348 346 |  | 
| 349 347 | 
             
            Sets whether to enable the proposed early hints Rack API. Defaults to `false`.
         | 
    
        data/examples/pitchfork.conf.rb
    CHANGED
    
    | @@ -23,9 +23,9 @@ check_client_connection false | |
| 23 23 | 
             
            # local variable to guard against running a hook multiple times
         | 
| 24 24 | 
             
            run_once = true
         | 
| 25 25 |  | 
| 26 | 
            -
             | 
| 26 | 
            +
            after_promotion do |server, worker|
         | 
| 27 27 | 
             
              # the following is highly recommended for Rails
         | 
| 28 | 
            -
              # as there's no need for the  | 
| 28 | 
            +
              # as there's no need for the mold process to hold a connection
         | 
| 29 29 | 
             
              defined?(ActiveRecord::Base) and
         | 
| 30 30 | 
             
                ActiveRecord::Base.connection.disconnect!
         | 
| 31 31 |  | 
    
        data/exe/pitchfork
    CHANGED
    
    | @@ -6,7 +6,6 @@ require 'optparse' | |
| 6 6 | 
             
            ENV["RACK_ENV"] ||= "development"
         | 
| 7 7 | 
             
            rackup_opts = Pitchfork::Configurator::RACKUP
         | 
| 8 8 | 
             
            options = rackup_opts[:options]
         | 
| 9 | 
            -
            set_no_default_middleware = true
         | 
| 10 9 |  | 
| 11 10 | 
             
            op = OptionParser.new("", 24, '  ') do |opts|
         | 
| 12 11 | 
             
              cmd = File.basename($0)
         | 
| @@ -59,11 +58,6 @@ op = OptionParser.new("", 24, '  ') do |opts| | |
| 59 58 | 
             
                ENV["RACK_ENV"] = e
         | 
| 60 59 | 
             
              end
         | 
| 61 60 |  | 
| 62 | 
            -
              opts.on("-N", "--no-default-middleware",
         | 
| 63 | 
            -
                      "do not load middleware implied by RACK_ENV") do |e|
         | 
| 64 | 
            -
                rackup_opts[:no_default_middleware] = true if set_no_default_middleware
         | 
| 65 | 
            -
              end
         | 
| 66 | 
            -
             | 
| 67 61 | 
             
              opts.on("-s", "--server SERVER",
         | 
| 68 62 | 
             
                      "this flag only exists for compatibility") do |s|
         | 
| 69 63 | 
             
                warn "-s/--server only exists for compatibility with rackup"
         | 
| @@ -101,7 +95,6 @@ op = OptionParser.new("", 24, '  ') do |opts| | |
| 101 95 | 
             
              opts.parse! ARGV
         | 
| 102 96 | 
             
            end
         | 
| 103 97 |  | 
| 104 | 
            -
            set_no_default_middleware = false
         | 
| 105 98 | 
             
            app = Pitchfork.builder(ARGV[0] || 'config.ru', op)
         | 
| 106 99 | 
             
            op = nil
         | 
| 107 100 |  | 
| @@ -113,4 +106,4 @@ if $DEBUG | |
| 113 106 | 
             
              })
         | 
| 114 107 | 
             
            end
         | 
| 115 108 |  | 
| 116 | 
            -
            Pitchfork::HttpServer.new(app, options).start.join
         | 
| 109 | 
            +
            exit(Pitchfork::HttpServer.new(app, options).start(false).join)
         | 
| @@ -64,9 +64,8 @@ static VALUE prep_readers(VALUE cls, VALUE readers) | |
| 64 64 |  | 
| 65 65 | 
             
            #if USE_EPOLL
         | 
| 66 66 | 
             
            struct ep_wait {
         | 
| 67 | 
            -
            	struct epoll_event  | 
| 67 | 
            +
            	struct epoll_event event;
         | 
| 68 68 | 
             
            	rb_io_t *fptr;
         | 
| 69 | 
            -
            	int maxevents;
         | 
| 70 69 | 
             
            	int timeout_msec;
         | 
| 71 70 | 
             
            };
         | 
| 72 71 |  | 
| @@ -74,8 +73,14 @@ static void *do_wait(void *ptr) /* runs w/o GVL */ | |
| 74 73 | 
             
            {
         | 
| 75 74 | 
             
            	struct ep_wait *epw = ptr;
         | 
| 76 75 |  | 
| 77 | 
            -
            	 | 
| 78 | 
            -
             | 
| 76 | 
            +
            	/*
         | 
| 77 | 
            +
            	 * Linux delivers epoll events in the order received, and using
         | 
| 78 | 
            +
            	 * maxevents=1 ensures we pluck one item off ep->rdllist
         | 
| 79 | 
            +
            	 * at-a-time (c.f. fs/eventpoll.c in linux.git, it's quite
         | 
| 80 | 
            +
            	 * easy-to-understand for anybody familiar with Ruby C).
         | 
| 81 | 
            +
            	 */
         | 
| 82 | 
            +
            	return (void *)(long)epoll_wait(epw->fptr->fd, &epw->event, 1,
         | 
| 83 | 
            +
            					epw->timeout_msec);
         | 
| 79 84 | 
             
            }
         | 
| 80 85 |  | 
| 81 86 | 
             
            /* :nodoc: */
         | 
| @@ -84,14 +89,10 @@ static VALUE | |
| 84 89 | 
             
            get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec)
         | 
| 85 90 | 
             
            {
         | 
| 86 91 | 
             
            	struct ep_wait epw;
         | 
| 87 | 
            -
            	long  | 
| 88 | 
            -
            	VALUE buf;
         | 
| 92 | 
            +
            	long n;
         | 
| 89 93 |  | 
| 90 94 | 
             
            	Check_Type(ready, T_ARRAY);
         | 
| 91 95 | 
             
            	Check_Type(readers, T_ARRAY);
         | 
| 92 | 
            -
            	epw.maxevents = RARRAY_LENINT(readers);
         | 
| 93 | 
            -
            	buf = rb_str_buf_new(sizeof(struct epoll_event) * epw.maxevents);
         | 
| 94 | 
            -
            	epw.events = (struct epoll_event *)RSTRING_PTR(buf);
         | 
| 95 96 | 
             
            	epio = rb_io_get_io(epio);
         | 
| 96 97 | 
             
            	GetOpenFile(epio, epw.fptr);
         | 
| 97 98 |  | 
| @@ -99,17 +100,12 @@ get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec) | |
| 99 100 | 
             
            	n = (long)rb_thread_call_without_gvl(do_wait, &epw, RUBY_UBF_IO, NULL);
         | 
| 100 101 | 
             
            	if (n < 0) {
         | 
| 101 102 | 
             
            		if (errno != EINTR) rb_sys_fail("epoll_wait");
         | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
            	/* Linux delivers events in order received */
         | 
| 105 | 
            -
            	for (i = 0; i < n; i++) {
         | 
| 106 | 
            -
            		struct epoll_event *ev = &epw.events[i];
         | 
| 107 | 
            -
            		VALUE obj = rb_ary_entry(readers, ev->data.u64);
         | 
| 103 | 
            +
            	} else if (n > 0) { /* maxevents is hardcoded to 1 */
         | 
| 104 | 
            +
            		VALUE obj = rb_ary_entry(readers, epw.event.data.u64);
         | 
| 108 105 |  | 
| 109 106 | 
             
            		if (RTEST(obj))
         | 
| 110 107 | 
             
            			rb_ary_push(ready, obj);
         | 
| 111 | 
            -
            	}
         | 
| 112 | 
            -
            	rb_str_resize(buf, 0);
         | 
| 108 | 
            +
            	} /* n == 0 : timeout */
         | 
| 113 109 | 
             
            	return Qfalse;
         | 
| 114 110 | 
             
            }
         | 
| 115 111 | 
             
            #endif /* USE_EPOLL */
         |