public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
commit  434ca28bfbadff572b66d57c5d699513036edc5e
tree    046940e1aa0643eebd25e5713d052dd63fe4b42b
parent  0e1eaf123a113403f3517f0bf7d35928dd40d48e
thin / lib / thin / daemonizing.rb
100644 147 lines (125 sloc) 4.402 kb
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
require 'etc'
require 'daemons' unless Thin.win?
 
module Process
  # Returns +true+ the process identied by +pid+ is running.
  def running?(pid)
    Process.getpgid(pid) != -1
  rescue Errno::ESRCH
    false
  end
  module_function :running?
end
 
module Thin
  # Raised when the pid file already exist starting as a daemon.
  class PidFileExist < RuntimeError; end
  
  # Module included in classes that can be turned into a daemon.
  # Handle stuff like:
  # * storing the PID in a file
  # * redirecting output to the log file
  # * changing processs privileges
  # * killing the process gracefully
  module Daemonizable
    attr_accessor :pid_file, :log_file
    
    def self.included(base)
      base.extend ClassMethods
    end
    
    def pid
      File.exist?(pid_file) ? open(pid_file).read.to_i : nil
    end
    
    # Turns the current script into a daemon process that detaches from the console.
    def daemonize
      raise PlatformNotSupported, 'Daemonizing not supported on Windows' if Thin.win?
      raise ArgumentError, 'You must specify a pid_file to daemonize' unless @pid_file
      raise PidFileExist, "#{@pid_file} already exist, seems like it's already running. " +
                                  "Stop the process or delete #{@pid_file}." if File.exist?(@pid_file)
      
      pwd = Dir.pwd # Current directory is changed during daemonization, so store it
      
      Daemonize.daemonize(File.expand_path(@log_file), name)
      
      Dir.chdir(pwd)
      
      write_pid_file
      
      trap('HUP') { restart }
      at_exit do
        log ">> Exiting!"
        remove_pid_file
      end
    end
    
    # Change privileges of the process
    # to the specified user and group.
    def change_privilege(user, group=user)
      log ">> Changing process privilege to #{user}:#{group}"
      
      uid, gid = Process.euid, Process.egid
      target_uid = Etc.getpwnam(user).uid
      target_gid = Etc.getgrnam(group).gid
 
      if uid != target_uid || gid != target_gid
        # Change process ownership
        Process.initgroups(user, target_gid)
        Process::GID.change_privilege(target_gid)
        Process::UID.change_privilege(target_uid)
      end
    rescue Errno::EPERM => e
      log "Couldn't change user and group to #{user}:#{group}: #{e}"
    end
    
    # Registerer a proc to be called to restart the server.
    def on_restart(&block)
      @on_restart = block
    end
    
    # Restart the server
    def restart
      raise ArgumentError, "Can't restart, no 'on_restart' proc specified" unless @on_restart
      log '>> Restarting ...'
      stop
      remove_pid_file
      @on_restart.call
      exit!
    end
    
    module ClassMethods
      # Send a QUIT signal the process which PID is stored in +pid_file+.
      # If the process is still running after +timeout+, KILL signal is
      # sent.
      def kill(pid_file, timeout=60)
        if pid = send_signal('QUIT', pid_file)
          begin
            Timeout.timeout(timeout) do
              sleep 0.1 while Process.running?(pid)
            end
          rescue Timeout::Error
            print "Timeout! "
            send_signal('KILL', pid_file)
          rescue Interrupt
            send_signal('KILL', pid_file)
          end
        end
        File.delete(pid_file) if File.exist?(pid_file)
      end
      
      # Restart the server by sending HUP signal
      def restart(pid_file)
        send_signal('HUP', pid_file)
      end
      
      # Send a +signal+ to the process which PID is stored in +pid_file+.
      def send_signal(signal, pid_file)
        if File.exist?(pid_file) && pid = open(pid_file).read
          pid = pid.to_i
          print "Sending #{signal} signal to process #{pid} ... "
          Process.kill(signal, pid)
          puts
          pid
        else
          puts "Can't stop process, no PID found in #{pid_file}"
          nil
        end
      rescue Errno::ESRCH # No such process
        puts "process not found!"
        nil
      end
    end
    
    protected
      def remove_pid_file
        File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
      end
    
      def write_pid_file
        log ">> Writing PID to #{@pid_file}"
        FileUtils.mkdir_p File.dirname(@pid_file)
        open(@pid_file,"w") { |f| f.write(Process.pid) }
        File.chmod(0644, @pid_file)
      end
  end
end