aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/commands/process/reaper.rb
blob: 0064642d6a5b9fb38e6b90ac872321ebc6a3c273 (plain) (blame)
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
require 'optparse'
require 'net/http'
require 'uri'

if RUBY_PLATFORM =~ /mswin32/ then abort("Reaper is only for Unix") end

# Instances of this class represent a single running process. Processes may
# be queried by "keyword" to find those that meet a specific set of criteria.
class ProgramProcess
  class << self
    
    # Searches for all processes matching the given keywords, and then invokes
    # a specific action on each of them. This is useful for (e.g.) reloading a
    # set of processes:
    #
    #   ProgramProcess.process_keywords(:reload, "basecamp")
    def process_keywords(action, *keywords)
      processes = keywords.collect { |keyword| find_by_keyword(keyword) }.flatten

      if processes.empty?
        puts "Couldn't find any process matching: #{keywords.join(" or ")}"
      else
        processes.each do |process|
          puts "#{action.capitalize}ing #{process}"
          process.send(action)
        end
      end      
    end

    # Searches for all processes matching the given keyword:
    #
    #   ProgramProcess.find_by_keyword("basecamp")
    def find_by_keyword(keyword)
      process_lines_with_keyword(keyword).split("\n").collect { |line|
        next if line.include?("inq") || line.include?("ps -axww") || line.include?("grep")
        pid, *command = line.split
        new(pid, command.join(" "))
      }.compact
    end

    private
      def process_lines_with_keyword(keyword)
        `ps -axww -o 'pid command' | grep #{keyword}`
      end
  end

  # Create a new ProgramProcess instance that represents the process with the
  # given pid, running the given command.
  def initialize(pid, command)
    @pid, @command = pid, command
  end

  # Forces the (rails) application to reload by sending a +HUP+ signal to the
  # process.
  def reload
    `kill -s HUP #{@pid}`
  end

  # Forces the (rails) application to gracefully terminate by sending a
  # +TERM+ signal to the process.
  def graceful
    `kill -s TERM #{@pid}`
  end

  # Forces the (rails) application to terminate immediately by sending a -9
  # signal to the process.
  def kill
    `kill -9 #{@pid}`
  end

  # Send a +USR1+ signal to the process.
  def usr1
    `kill -s USR1 #{@pid}`
  end

  # Force the (rails) application to restart by sending a +USR2+ signal to the
  # process.
  def restart
    `kill -s USR2 #{@pid}`
  end

  def to_s #:nodoc:
    "[#{@pid}] #{@command}"
  end
end

OPTIONS = {
  :action      => "restart",
  :dispatcher  => File.expand_path(RAILS_ROOT + '/public/dispatch.fcgi')
}

ARGV.options do |opts|
  opts.banner = "Usage: reaper [options]"

  opts.separator ""

  opts.on <<-EOF
  Description:
    The reaper is used to restart, reload, gracefully exit, and forcefully exit FCGI processes
    running a Rails Dispatcher. This is commonly done when a new version of the application
    is available, so the existing processes can be updated to use the latest code.

    The reaper actions are:

    * restart : Restarts the application by reloading both application and framework code
    * reload  : Only reloads the application, but not the framework (like the development environment)
    * graceful: Marks all of the processes for exit after the next request
    * kill    : Forcefully exists all processes regardless of whether they're currently serving a request

    Restart is the most common and default action.

  Examples:
    reaper # restarts the default dispatcher
    reaper -a reload
    reaper -a exit -d /my/special/dispatcher.fcgi
  EOF

  opts.on("  Options:")

  opts.on("-a", "--action=name", "reload|graceful|kill (default: #{OPTIONS[:action]})", String)  { |OPTIONS[:action]| }
  opts.on("-d", "--dispatcher=path", "default: #{OPTIONS[:dispatcher]}", String)                 { |OPTIONS[:dispatcher]| }

  opts.separator ""

  opts.on("-h", "--help", "Show this help message.") { puts opts; exit }

  opts.parse!
end

ProgramProcess.process_keywords(OPTIONS[:action], OPTIONS[:dispatcher])