zbatery.git  about / heads / tags
Zbatery HTTP server for Rack
blob 56edf91e8ab5ff1bb333bd3377df140a3bc89b31 4453 bytes (raw)
$ git show v0.3.0:lib/zbatery.rb	# shows this blob on the CLI

  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
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
 
# -*- encoding: binary -*-
# :enddoc:
require 'rainbows'

module Zbatery

  # current version of Zbatery
  VERSION = "0.3.0"

  class << self

    # runs the Zbatery HttpServer with +app+ and +options+ and does
    # not return until the server has exited.
    def run(app, options = {})
      Rainbows::HttpServer.new(app, options).start.join
    end
  end

  Rainbows::Const::RACK_DEFAULTS["SERVER_SOFTWARE"] = "Zbatery #{VERSION}"

  # true if our Ruby implementation supports unlinked files
  UnlinkedIO = begin
    tmp = Unicorn::Util.tmpio
    tmp.chmod(0)
    tmp.close
    true
  rescue
    false
  end

  # we don't actually fork workers, but allow using the
  # {before,after}_fork hooks found in Unicorn/Rainbows!
  # config files...
  FORK_HOOK = lambda { |_,_| }

end

# :stopdoc:
# override stuff we don't need or can't use portably
module Rainbows

  module Base
    # master == worker in our case
    def init_worker_process(worker)
      after_fork.call(self, worker)
      worker.user(*user) if user.kind_of?(Array) && ! worker.switched
      build_app! unless preload_app
      Rainbows::Response.setup(self.class)
      Rainbows::MaxBody.setup

      # avoid spurious wakeups and blocking-accept() with 1.8 green threads
      if RUBY_VERSION.to_f < 1.9
        require "io/nonblock"
        HttpServer::LISTENERS.each { |l| l.nonblock = true }
      end

      logger.info "Zbatery #@use worker_connections=#@worker_connections"
    end
  end

  # we can't/don't need to do the fchmod heartbeat Unicorn/Rainbows! does
  def G.tick
    alive
  end

  class HttpServer

    # this class is only used to avoid breaking Unicorn user switching
    class DeadIO
      def chown(*args); end
    end

    # only used if no concurrency model is specified
    def worker_loop(worker)
      init_worker_process(worker)
      begin
        ret = IO.select(LISTENERS, nil, nil, nil) and
        ret.first.each do |sock|
          begin
            process_client(sock.accept_nonblock)
          rescue Errno::EAGAIN, Errno::ECONNABORTED
          end
        end
      rescue Errno::EINTR
      rescue Errno::EBADF, TypeError
        break
      rescue => e
        Rainbows::Error.listen_loop(e)
      end while G.alive
    end

    # no-op
    def maintain_worker_count; end
    def init_self_pipe!; end

    # can't just do a graceful exit if reopening logs fails, so we just
    # continue on...
    def reopen_logs
      logger.info "reopening logs"
      Unicorn::Util.reopen_logs
      logger.info "done reopening logs"
      rescue => e
        logger.error "failed reopening logs #{e.message}"
    end

    def trap_deferred(sig)
      # nothing
    end

    def join
      begin
        trap(:INT) { stop(false) } # Mongrel trapped INT for Win32...

        # try these anyways regardless of platform...
        trap(:TERM) { stop(false) }
        trap(:QUIT) { stop }
        trap(:USR1) { reopen_logs }
        trap(:USR2) { reexec }

        # no other way to reliably switch concurrency models...
        trap(:HUP) { reexec; stop }

        # technically feasible in some cases, just not sanely supportable:
        %w(TTIN TTOU WINCH).each do |sig|
          trap(sig) { logger.info "SIG#{sig} is not handled by Zbatery" }
        end
      rescue => e # hopefully ignores errors on Win32...
        logger.error "failed to setup signal handler: #{e.message}"
      end

      if ready_pipe
        ready_pipe.syswrite($$.to_s)
        ready_pipe.close rescue nil
        self.ready_pipe = nil
      end

      worker = Worker.new(0, DeadIO.new)
      before_fork.call(self, worker)
      worker_loop(worker) # runs forever
    end

    def stop(graceful = true)
      Rainbows::G.quit!
      exit!(0) unless graceful
    end

    def before_fork
      hook = super
      hook == Zbatery::FORK_HOOK or
        logger.warn "calling before_fork without forking"
      hook
    end

    def after_fork
      hook = super
      hook == Zbatery::FORK_HOOK or
        logger.warn "calling after_fork without having forked"
      hook
    end
  end
end

module Unicorn

  class Configurator
    DEFAULTS[:before_fork] = DEFAULTS[:after_fork] = Zbatery::FORK_HOOK
  end

  unless Zbatery::UnlinkedIO
    require 'tempfile'
    class Util

      # Tempfiles should get automatically unlinked by GC
      def self.tmpio
        fp = Tempfile.new("zbatery")
        fp.binmode
        fp.sync = true
        fp
      end
    end
  end

end

git clone https://yhbt.net/zbatery.git