public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
macournoyer (author)
Thu Jan 17 19:55:04 -0800 2008
commit  bc601ce1a4f4e42e19b77d9574f7fdebae4b15e7
tree    41963326771eba4ab6aece11a238325d3c3810aa
parent  ca716970ee03dc0ed94bda0e4dc83b8a0bf0cb9d
thin / lib / thin / daemonizing.rb
100644 113 lines (96 sloc) 3.233 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
require 'etc'
require 'daemons'
 
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
  # 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 : nil
    end
    
    # Turns the current script into a daemon process that detaches from the console.
    def daemonize
      check_plateform_support
      raise ArgumentError, 'You must specify a pid_file to deamonize' unless @pid_file
      
      pwd = Dir.pwd # Current directory is changed during daemonization, so store it
      
      Daemonize.daemonize(File.expand_path(@log_file))
      
      Dir.chdir(pwd)
      
      write_pid_file
      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)
      check_plateform_support
      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
    
    module ClassMethods
      # Kill the process which PID is stored in +pid_file+.
      def kill(pid_file, timeout=60)
        if pid = open(pid_file).read
          pid = pid.to_i
          print "Sending INT signal to process #{pid} ... "
          begin
            Process.kill('INT', pid)
            Timeout.timeout(timeout) do
              sleep 0.1 while Process.running?(pid)
            end
          rescue Timeout::Error
            print "timeout, Sending KILL signal ... "
            Process.kill('KILL', pid)
          end
          puts "stopped!"
        else
          puts "Can't stop process, no PID found in #{@pid_file}"
        end
      rescue Errno::ESRCH # No such process
        puts "process not found!"
      ensure
        File.delete(pid_file) rescue nil
      end
    end
    
    private
      def check_plateform_support
        raise RuntimeError, 'Daemonizing not supported on Windows' if RUBY_PLATFORM =~ /mswin/
      end
      
      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