puma_cluster_spec.rb 2.21 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
# frozen_string_literal: true

require 'spec_helper'

# For easier debugging set `PUMA_DEBUG=1`

describe Gitlab::Cluster::Mixins::PumaCluster do
  PUMA_STARTUP_TIMEOUT = 30

  context 'when running Puma in Cluster-mode' do
    %i[USR1 USR2 INT HUP].each do |signal|
      it "for #{signal} does execute phased restart block" do
        with_puma(workers: 1) do |pid|
          Process.kill(signal, pid)

          child_pid, child_status = Process.wait2(pid)
          expect(child_pid).to eq(pid)
          expect(child_status).to be_exited
          expect(child_status.exitstatus).to eq(140)
        end
      end
    end
  end

  private

  def with_puma(workers:, timeout: PUMA_STARTUP_TIMEOUT)
    with_puma_config(workers: workers) do |puma_rb|
      cmdline = [
        "bundle", "exec", "puma",
        "-C", puma_rb,
        "-I", Rails.root.to_s
      ]

      IO.popen(cmdline) do |process|
        # wait for process to start:
        # [2123] * Listening on tcp://127.0.0.1:0
        wait_for_output(process, /Listening on/, timeout: timeout)
        consume_output(process)

        yield(process.pid)
      ensure
        Process.kill(:KILL, process.pid) unless process.eof?
      end
    end
  end

  def with_puma_config(workers:)
    Dir.mktmpdir do |dir|
      File.write "#{dir}/puma.rb", <<-EOF
        require './lib/gitlab/cluster/lifecycle_events'
        require './lib/gitlab/cluster/mixins/puma_cluster'

        workers #{workers}
        bind "tcp://127.0.0.1:0"
        preload_app!

        app -> (env) { [404, {}, ['']] }

        Puma::Cluster.prepend(#{described_class})

        Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do
          exit(140)
        end

        # redirect stderr to stdout
        $stderr.reopen($stdout)
      EOF

      yield("#{dir}/puma.rb")
    end
  end

  def wait_for_output(process, output, timeout:)
    Timeout.timeout(timeout) do
      loop do
        line = process.readline
        puts "PUMA_DEBUG: #{line}" if ENV['PUMA_DEBUG']
        break if line =~ output
      end
    end
  end

  def consume_output(process)
    Thread.new do
      loop do
        line = process.readline
        puts "PUMA_DEBUG: #{line}" if ENV['PUMA_DEBUG']
      end
    rescue
    end
  end
end