tests: port some bad config tests to Perl 5
[unicorn.git] / lib / unicorn / worker.rb
blob5ddf379d8194cda39b0f8d70ad2799d79ce0cd37
1 # -*- encoding: binary -*-
2 require "raindrops"
4 # This class and its members can be considered a stable interface
5 # and will not change in a backwards-incompatible fashion between
6 # releases of unicorn.  Knowledge of this class is generally not
7 # not needed for most users of unicorn.
9 # Some users may want to access it in the before_fork/after_fork hooks.
10 # See the Unicorn::Configurator RDoc for examples.
11 class Unicorn::Worker
12   # :stopdoc:
13   attr_accessor :nr, :switched
14   attr_reader :to_io # IO.select-compatible
15   attr_reader :master
17   PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
18   DROPS = []
20   def initialize(nr, pipe=nil)
21     drop_index = nr / PER_DROP
22     @raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
23     @offset = nr % PER_DROP
24     @raindrop[@offset] = 0
25     @nr = nr
26     @switched = false
27     @to_io, @master = pipe || Unicorn.pipe
28   end
30   def atfork_child # :nodoc:
31     # we _must_ close in child, parent just holds this open to signal
32     @master = @master.close
33   end
35   # master fakes SIGQUIT using this
36   def quit # :nodoc:
37     @master = @master.close if @master
38   end
40   # parent does not read
41   def atfork_parent # :nodoc:
42     @to_io = @to_io.close
43   end
45   # call a signal handler immediately without triggering EINTR
46   # We do not use the more obvious Process.kill(sig, $$) here since
47   # that signal delivery may be deferred.  We want to avoid signal delivery
48   # while the Rack app.call is running because some database drivers
49   # (e.g. ruby-pg) may cancel pending requests.
50   def fake_sig(sig) # :nodoc:
51     old_cb = trap(sig, "IGNORE")
52     old_cb.call
53   ensure
54     trap(sig, old_cb)
55   end
57   # master sends fake signals to children
58   def soft_kill(sig) # :nodoc:
59     case sig
60     when Integer
61       signum = sig
62     else
63       signum = Signal.list[sig.to_s] or
64           raise ArgumentError, "BUG: bad signal: #{sig.inspect}"
65     end
66     # writing and reading 4 bytes on a pipe is atomic on all POSIX platforms
67     # Do not care in the odd case the buffer is full, here.
68     @master.kgio_trywrite([signum].pack('l'))
69   rescue Errno::EPIPE
70     # worker will be reaped soon
71   end
73   # this only runs when the Rack app.call is not running
74   # act like a listener
75   def kgio_tryaccept # :nodoc:
76     case buf = @to_io.kgio_tryread(4)
77     when String
78       # unpack the buffer and trigger the signal handler
79       signum = buf.unpack('l')
80       fake_sig(signum[0])
81       # keep looping, more signals may be queued
82     when nil # EOF: master died, but we are at a safe place to exit
83       fake_sig(:QUIT)
84     when :wait_readable # keep waiting
85       return false
86     end while true # loop, as multiple signals may be sent
87   end
89   # worker objects may be compared to just plain Integers
90   def ==(other_nr) # :nodoc:
91     @nr == other_nr
92   end
94   # called in the worker process
95   def tick=(value) # :nodoc:
96     @raindrop[@offset] = value
97   end
99   # called in the master process
100   def tick # :nodoc:
101     @raindrop[@offset]
102   end
104   # called in both the master (reaping worker) and worker (SIGQUIT handler)
105   def close # :nodoc:
106     @master.close if @master
107     @to_io.close if @to_io
108   end
110   # :startdoc:
112   # In most cases, you should be using the Unicorn::Configurator#user
113   # directive instead.  This method should only be used if you need
114   # fine-grained control of exactly when you want to change permissions
115   # in your after_fork or after_worker_ready hooks, or if you want to
116   # use the chroot support.
117   #
118   # Changes the worker process to the specified +user+ and +group+,
119   # and chroots to the current working directory if +chroot+ is set.
120   # This is only intended to be called from within the worker
121   # process from the +after_fork+ hook.  This should be called in
122   # the +after_fork+ hook after any privileged functions need to be
123   # run (e.g. to set per-worker CPU affinity, niceness, etc)
124   #
125   # +group+ can be specified as a string, or as an array of two
126   # strings.  If an array of two strings is given, the first string
127   # is used as the primary group of the process, and the second is
128   # used as the group of the log files.
129   #
130   # Any and all errors raised within this method will be propagated
131   # directly back to the caller (usually the +after_fork+ hook.
132   # These errors commonly include ArgumentError for specifying an
133   # invalid user/group and Errno::EPERM for insufficient privileges.
134   #
135   # chroot support is only available in unicorn 5.3.0+
136   # user and group switching appeared in unicorn 0.94.0 (2009-11-05)
137   def user(user, group = nil, chroot = false)
138     # we do not protect the caller, checking Process.euid == 0 is
139     # insufficient because modern systems have fine-grained
140     # capabilities.  Let the caller handle any and all errors.
141     uid = Etc.getpwnam(user).uid
143     if group
144       if group.is_a?(Array)
145         group, log_group = group
146         log_gid = Etc.getgrnam(log_group).gid
147       end
148       gid = Etc.getgrnam(group).gid
149       log_gid ||= gid
150     end
152     Unicorn::Util.chown_logs(uid, log_gid)
153     if gid && Process.egid != gid
154       Process.initgroups(user, gid)
155       Process::GID.change_privilege(gid)
156     end
157     if chroot
158       chroot = Dir.pwd if chroot == true
159       Dir.chroot(chroot)
160       Dir.chdir('/')
161     end
162     Process.euid != uid and Process::UID.change_privilege(uid)
163     @switched = true
164   end