Module: Einhorn::Command::Interface
- Defined in:
- lib/einhorn/command/interface.rb
Constant Summary collapse
- @@commands =
{}
- @@command_server =
nil
Class Method Summary collapse
-
.command(name, description = nil, &code) ⇒ Object
Commands.
- .command_descriptions ⇒ Object
- .command_server ⇒ Object
- .command_server=(server) ⇒ Object
- .default_lockfile_path(cmd_name = nil) ⇒ Object
- .default_pidfile(cmd_name = nil) ⇒ Object
- .default_socket_path(cmd_name = nil) ⇒ Object
- .destroy_old_command_socket(path) ⇒ Object
- .generate_message(conn, request) ⇒ Object
- .init ⇒ Object
-
.install_handlers ⇒ Object
Signals.
- .lockfile ⇒ Object
- .normalize_signals(args) ⇒ Object
- .open_command_socket ⇒ Object
- .persistent_init ⇒ Object
- .pidfile ⇒ Object
- .process_command(conn, command) ⇒ Object
- .remove_handlers ⇒ Object
- .send_message(conn, message, request_id = nil, last = false) ⇒ Object
- .send_tagged_message(tag, message, last = false) ⇒ Object
- .socket_path ⇒ Object
- .trap_async(signal, &blk) ⇒ Object
- .uninit ⇒ Object
- .unrecognized_command(conn, request) ⇒ Object
- .validate_args(args) ⇒ Object
- .validate_signals(args) ⇒ Object
-
.with_file_lock(&blk) ⇒ Object
Lock against other Einhorn workers.
- .write_pidfile ⇒ Object
Class Method Details
.command(name, description = nil, &code) ⇒ Object
Commands
200 201 202 |
# File 'lib/einhorn/command/interface.rb', line 200 def self.command(name, description=nil, &code) @@commands[name] = {:description => description, :code => code} end |
.command_descriptions ⇒ Object
262 263 264 265 266 267 268 269 270 |
# File 'lib/einhorn/command/interface.rb', line 262 def self.command_descriptions command_specs = @@commands.select do |_, spec| spec[:description] end.sort_by {|name, _| name} command_specs.map do |name, spec| "#{name}: #{spec[:description]}" end.join("\n") end |
.command_server ⇒ Object
14 15 16 |
# File 'lib/einhorn/command/interface.rb', line 14 def self.command_server @@command_server end |
.command_server=(server) ⇒ Object
9 10 11 12 |
# File 'lib/einhorn/command/interface.rb', line 9 def self.command_server=(server) raise "Command server already set" if @@command_server && server @@command_server = server end |
.default_lockfile_path(cmd_name = nil) ⇒ Object
124 125 126 127 128 129 130 131 132 |
# File 'lib/einhorn/command/interface.rb', line 124 def self.default_lockfile_path(cmd_name=nil) cmd_name ||= Einhorn::State.cmd_name if cmd_name filename = "einhorn-#{cmd_name}.lock" else filename = "einhorn.lock" end File.join(Dir.tmpdir, filename) end |
.default_pidfile(cmd_name = nil) ⇒ Object
138 139 140 141 142 143 144 145 146 |
# File 'lib/einhorn/command/interface.rb', line 138 def self.default_pidfile(cmd_name=nil) cmd_name ||= Einhorn::State.cmd_name if cmd_name filename = "einhorn-#{cmd_name}.pid" else filename = "einhorn.pid" end File.join(Dir.tmpdir, filename) end |
.default_socket_path(cmd_name = nil) ⇒ Object
110 111 112 113 114 115 116 117 118 |
# File 'lib/einhorn/command/interface.rb', line 110 def self.default_socket_path(cmd_name=nil) cmd_name ||= Einhorn::State.cmd_name if cmd_name filename = "einhorn-#{cmd_name}.sock" else filename = "einhorn.sock" end File.join(Dir.tmpdir, filename) end |
.destroy_old_command_socket(path) ⇒ Object
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 |
# File 'lib/einhorn/command/interface.rb', line 69 def self.destroy_old_command_socket(path) # Socket isn't actually owned by anyone begin sock = UNIXSocket.new(path) rescue Errno::ECONNREFUSED # This happens with non-socket files and when the listening # end of a socket has exited. rescue Errno::ENOENT # Socket doesn't exist return else # Rats, it's still active sock.close raise Errno::EADDRINUSE.new("Another process (probably another Einhorn) is listening on the Einhorn command socket at #{path}. If you'd like to run this Einhorn as well, pass a `-d PATH_TO_SOCKET` to change the command socket location.") end # Socket should still exist, so don't need to handle error. stat = File.stat(path) unless stat.socket? raise Errno::EADDRINUSE.new("Non-socket file present at Einhorn command socket path #{path}. Either remove that file and restart Einhorn, or pass a `-d PATH_TO_SOCKET` to change the command socket location.") end Einhorn.log_info("Blowing away old Einhorn command socket at #{path}. This likely indicates a previous Einhorn master which exited uncleanly.") # Whee, blow it away. File.unlink(path) end |
.generate_message(conn, request) ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/einhorn/command/interface.rb', line 242 def self.(conn, request) unless command_name = request['command'] return 'No "command" parameter provided; not sure what you want me to do.' end if command_spec = @@commands[command_name] conn.log_debug("Received command: #{request.inspect}") begin return command_spec[:code].call(conn, request) rescue StandardError => e msg = "Error while processing command #{command_name.inspect}: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}" conn.log_error(msg) return msg end else conn.log_debug("Received unrecognized command: #{request.inspect}") return unrecognized_command(conn, request) end end |
.init ⇒ Object
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/einhorn/command/interface.rb', line 18 def self.init install_handlers at_exit do if Einhorn::TransientState.whatami == :master to_remove = [pidfile] # Don't nuke socket_path if we never successfully acquired it to_remove << socket_path if @@command_server to_remove.each do |file| begin File.unlink(file) rescue Errno::ENOENT end end end end end |
.install_handlers ⇒ Object
Signals
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 |
# File 'lib/einhorn/command/interface.rb', line 149 def self.install_handlers trap_async("INT") do Einhorn::Command.signal_all("USR2", Einhorn::WorkerPool.workers) Einhorn::Command.stop_respawning end trap_async("TERM") do Einhorn::Command.signal_all("TERM", Einhorn::WorkerPool.workers) Einhorn::Command.stop_respawning end # Note that quit is a bit different, in that it will actually # make Einhorn quit without waiting for children to exit. trap_async("QUIT") do Einhorn::Command.signal_all("QUIT", Einhorn::WorkerPool.workers) Einhorn::Command.stop_respawning exit(1) end trap_async("HUP") {Einhorn::Command.full_upgrade_smooth} trap_async("ALRM") do Einhorn.log_error("Upgrading using SIGALRM is deprecated. Please switch to SIGHUP") Einhorn::Command.full_upgrade_smooth end trap_async("CHLD") {} trap_async("USR2") do Einhorn::Command.signal_all("USR2", Einhorn::WorkerPool.workers) Einhorn::Command.stop_respawning end at_exit do if Einhorn::State.kill_children_on_exit && Einhorn::TransientState.whatami == :master Einhorn::Command.signal_all("USR2", Einhorn::WorkerPool.workers) Einhorn::Command.stop_respawning end end end |
.lockfile ⇒ Object
120 121 122 |
# File 'lib/einhorn/command/interface.rb', line 120 def self.lockfile Einhorn::State.lockfile || default_lockfile_path end |
.normalize_signals(args) ⇒ Object
463 464 465 466 467 468 469 |
# File 'lib/einhorn/command/interface.rb', line 463 def self.normalize_signals(args) args.map do |signal| signal = signal.upcase signal = $1 if signal =~ /\ASIG(.*)\Z/ signal end end |
.open_command_socket ⇒ Object
45 46 47 48 49 50 51 52 53 54 |
# File 'lib/einhorn/command/interface.rb', line 45 def self.open_command_socket path = socket_path with_file_lock do # Need to avoid time-of-check to time-of-use bugs in blowing # away and recreating the old socketfile. destroy_old_command_socket(path) Einhorn::Compat.unixserver_new(path) end end |
.persistent_init ⇒ Object
35 36 37 38 39 40 41 42 43 |
# File 'lib/einhorn/command/interface.rb', line 35 def self.persistent_init socket = open_command_socket Einhorn::Event::CommandServer.open(socket) # Could also rewrite this on reload. Might be useful in case # someone goes and accidentally clobbers/deletes. Should make # sure that the clobber is atomic if we we were do do that. write_pidfile end |
.pidfile ⇒ Object
134 135 136 |
# File 'lib/einhorn/command/interface.rb', line 134 def self.pidfile Einhorn::State.pidfile || default_pidfile end |
.process_command(conn, command) ⇒ Object
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/einhorn/command/interface.rb', line 204 def self.process_command(conn, command) begin request = Einhorn::Client::Transport.(command) rescue Einhorn::Client::Transport::ParseError end unless request.kind_of?(Hash) (conn, "Could not parse command") return end = (conn, request) if !.nil? (conn, , request['id'], true) else conn.log_debug("Got back nil response, so not responding to command.") end end |
.remove_handlers ⇒ Object
193 194 195 196 197 |
# File 'lib/einhorn/command/interface.rb', line 193 def self.remove_handlers %w{INT TERM QUIT HUP ALRM CHLD USR2}.each do |signal| Signal.trap(signal, "DEFAULT") end end |
.send_message(conn, message, request_id = nil, last = false) ⇒ Object
231 232 233 234 235 236 237 238 239 240 |
# File 'lib/einhorn/command/interface.rb', line 231 def self.(conn, , request_id=nil, last=false) if request_id response = {'message' => , 'request_id' => request_id } response['wait'] = true unless last else # support old-style protocol response = {'message' => } end Einhorn::Client::Transport.(conn, response) end |
.send_tagged_message(tag, message, last = false) ⇒ Object
222 223 224 225 226 227 228 229 |
# File 'lib/einhorn/command/interface.rb', line 222 def self.(tag, , last=false) Einhorn::Event.connections.each do |conn| if id = conn.subscription(tag) self.(conn, , id, last) conn.unsubscribe(tag) if last end end end |
.socket_path ⇒ Object
106 107 108 |
# File 'lib/einhorn/command/interface.rb', line 106 def self.socket_path Einhorn::State.socket_path || default_socket_path end |
.trap_async(signal, &blk) ⇒ Object
183 184 185 186 187 188 189 190 191 |
# File 'lib/einhorn/command/interface.rb', line 183 def self.trap_async(signal, &blk) Signal.trap(signal) do # We try to do as little work in the signal handler as # possible. This avoids potential races between e.g. iteration # and mutation. Einhorn::Event.break_loop Einhorn::Event.register_signal_action(&blk) end end |
.uninit ⇒ Object
102 103 104 |
# File 'lib/einhorn/command/interface.rb', line 102 def self.uninit remove_handlers end |
.unrecognized_command(conn, request) ⇒ Object
272 273 274 275 276 277 278 |
# File 'lib/einhorn/command/interface.rb', line 272 def self.unrecognized_command(conn, request) <<EOF Unrecognized command: #{request['command'].inspect} #{command_descriptions} EOF end |
.validate_args(args) ⇒ Object
442 443 444 445 446 447 448 449 450 451 |
# File 'lib/einhorn/command/interface.rb', line 442 def self.validate_args(args) return 'No args provided' unless args return 'Args must be an array' unless args.kind_of?(Array) args.each do |arg| return "Argument is a #{arg.class}, not a string: #{arg.inspect}" unless arg.kind_of?(String) end nil end |
.validate_signals(args) ⇒ Object
453 454 455 456 457 458 459 460 461 |
# File 'lib/einhorn/command/interface.rb', line 453 def self.validate_signals(args) args.each do |signal| unless Signal.list.include?(signal) return "Invalid signal: #{signal.inspect}" end end nil end |
.with_file_lock(&blk) ⇒ Object
Lock against other Einhorn workers. Unfortunately, have to leave this lockfile lying around forever.
58 59 60 61 62 63 64 65 66 67 |
# File 'lib/einhorn/command/interface.rb', line 58 def self.with_file_lock(&blk) path = lockfile File.open(path, 'w', 0600) do |f| unless f.flock(File::LOCK_EX|File::LOCK_NB) raise "File lock already acquired by another Einhorn process. This likely indicates you tried to run Einhorn masters with the same cmd_name at the same time. This is a pretty rare race condition." end blk.call end end |