Class: Makit::Process
- Inherits:
-
Object
- Object
- Makit::Process
- Defined in:
- lib/makit/process.rb
Overview
Cross-platform process management utilities
This class provides methods for checking, listing, and terminating processes across different operating systems (Windows, Linux, macOS). It abstracts platform-specific commands into a unified interface.
Class Method Summary collapse
-
.background_running?(pid_file) ⇒ Boolean
Check if a background process is still running using its PID file.
-
.cleanup_stale_pids(pid_files) ⇒ Array<String>
Clean up stale PID files (processes that are no longer running).
-
.get_background_pid(pid_file) ⇒ Integer?
Get the PID of a background process from its PID file.
-
.health_check_port(port, host: "localhost", timeout: 5) ⇒ Boolean
Health check for a service running on a specific port.
-
.health_check_ports(ports, host: "localhost", timeout: 5) ⇒ Hash
Health check for multiple ports.
-
.is_running?(name) ⇒ Boolean
Check if a process with the given name is currently running.
-
.kill(name) ⇒ Array<String>
Terminate all processes that match a given name.
-
.list ⇒ String
List all running processes on the system.
-
.list_current_user ⇒ String
List all running processes for the current user.
-
.list_current_user_processes(name) ⇒ Array<String>
List processes for the current user filtered by name.
-
.run_background(command, pid_file, working_dir: nil, environment: {}) ⇒ Integer
Run a command in the background and track it with a PID file.
-
.stop_background(pid_file, signal: "TERM") ⇒ Boolean
Stop a background process using its PID file.
Class Method Details
.background_running?(pid_file) ⇒ Boolean
Check if a background process is still running using its PID file
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/makit/process.rb', line 285 def self.background_running?(pid_file) return false unless File.exist?(pid_file) begin pid = File.read(pid_file).strip.to_i return false if pid <= 0 # Check if process is still running Process.getpgid(pid) true rescue Errno::ESRCH, Errno::EPERM false rescue => e # Log error but return false puts "Error checking background process: #{e.message}" if defined?(puts) false end end |
.cleanup_stale_pids(pid_files) ⇒ Array<String>
Clean up stale PID files (processes that are no longer running)
325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/makit/process.rb', line 325 def self.cleanup_stale_pids(pid_files) cleaned = [] pid_files.each do |pid_file| next unless File.exist?(pid_file) unless background_running?(pid_file) File.delete(pid_file) cleaned << pid_file end end cleaned end |
.get_background_pid(pid_file) ⇒ Integer?
Get the PID of a background process from its PID file
308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/makit/process.rb', line 308 def self.get_background_pid(pid_file) return nil unless File.exist?(pid_file) begin pid = File.read(pid_file).strip.to_i return nil if pid <= 0 pid rescue => e puts "Error reading PID file: #{e.message}" if defined?(puts) nil end end |
.health_check_port(port, host: "localhost", timeout: 5) ⇒ Boolean
Health check for a service running on a specific port
346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/makit/process.rb', line 346 def self.health_check_port(port, host: "localhost", timeout: 5) require 'net/http' require 'timeout' begin uri = URI("http://#{host}:#{port}") Timeout.timeout(timeout) do response = Net::HTTP.get_response(uri) response.code.to_i < 500 # Consider 4xx as "responding" but 5xx as "unhealthy" end rescue => e false end end |
.health_check_ports(ports, host: "localhost", timeout: 5) ⇒ Hash
Health check for multiple ports
367 368 369 370 371 372 373 374 375 |
# File 'lib/makit/process.rb', line 367 def self.health_check_ports(ports, host: "localhost", timeout: 5) results = {} ports.each do |port| results[port] = health_check_port(port, host: host, timeout: timeout) end results end |
.is_running?(name) ⇒ Boolean
Check if a process with the given name is currently running
Uses platform-specific commands:
-
Windows: tasklist command
-
Unix: ps command with grep filtering
19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/makit/process.rb', line 19 def self.is_running?(name) return false if name.nil? || name.strip.empty? # provide a cross-platform way to check if a process is running results = if Makit::Environment.is_windows? # on windows, use the tasklist command `tasklist /FI "imagename eq #{name}.exe" 2>nul` else # on linux/mac, use the ps command `ps aux | grep "#{name}" | grep -v grep 2>/dev/null` end results.include?(name) end |
.kill(name) ⇒ Array<String>
Terminate all processes that match a given name
Uses platform-specific termination commands:
-
Windows: taskkill command
-
Unix: kill command with SIGKILL (-9)
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 |
# File 'lib/makit/process.rb', line 41 def self.kill(name) return [] if name.nil? || name.strip.empty? killed_pids = [] if Makit::Environment.is_windows? # Windows: Use tasklist to find PIDs first, then kill them begin # Find processes by name tasklist_output = `tasklist /FI "imagename eq #{name}.exe" /FO CSV 2>nul` # Parse CSV output to extract PIDs lines = tasklist_output.split("\n") if lines.length > 1 # Skip header line lines[1..].each do |line| parts = line.split(",") next unless parts.length >= 2 pid = parts[1]&.gsub('"', "")&.strip next unless pid && !pid.empty? && pid.match?(/^\d+$/) begin `taskkill /PID #{pid} /F 2>nul` killed_pids << pid rescue StandardError => e # Log error but continue with other processes puts "Failed to kill process #{pid}: #{e.message}" end end end rescue StandardError => e # If tasklist fails, try direct taskkill by name begin `taskkill /IM "#{name}.exe" /F 2>nul` # Since we can't get PIDs from direct kill, return empty array # but don't raise error rescue StandardError => e2 # Both methods failed, return empty array puts "Failed to kill processes by name #{name}: #{e2.message}" end end else # Unix (Linux/Mac): Use pgrep to find PIDs, then kill them begin # Use pgrep to find PIDs, handle both Linux and Mac pids = if Makit::Environment.is_mac? # Mac: pgrep -f might not work the same way `pgrep -x "#{name}" 2>/dev/null`.split("\n").reject(&:empty?) else # Linux: pgrep -f works well `pgrep -f "#{name}" 2>/dev/null`.split("\n").reject(&:empty?) end pids.each do |pid| next unless pid.match?(/^\d+$/) begin `kill -9 #{pid} 2>/dev/null` killed_pids << pid rescue StandardError => e puts "Failed to kill process #{pid}: #{e.message}" end end rescue StandardError => e # If pgrep fails, return empty array but don't raise error puts "Failed to find processes by name #{name}: #{e.message}" end end killed_pids end |
.list ⇒ String
List all running processes on the system
Uses platform-specific commands to get a complete process listing:
-
Windows: tasklist command
-
Unix: ps aux command
120 121 122 123 124 125 126 |
# File 'lib/makit/process.rb', line 120 def self.list if Makit::Environment.is_windows? `tasklist 2>nul` else `ps aux 2>/dev/null` end end |
.list_current_user ⇒ String
List all running processes for the current user
Filters the process list to show only processes owned by the current user. Uses platform-specific filtering:
-
Windows: tasklist with USERNAME filter
-
Unix: ps aux with grep filtering
136 137 138 139 140 141 142 |
# File 'lib/makit/process.rb', line 136 def self.list_current_user if Makit::Environment.is_windows? `tasklist /FI "STATUS eq running and USERNAME eq #{Makit::Environment.current_user}" 2>nul` else `ps aux | grep "#{Makit::Environment.current_user}" | grep -v grep 2>/dev/null` end end |
.list_current_user_processes(name) ⇒ Array<String>
List processes for the current user filtered by name
Returns an array of process lines for consistency across platforms. Filters by both current user and process name.
151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/makit/process.rb', line 151 def self.list_current_user_processes(name) if Makit::Environment.is_windows? # filter the results to only include the current user results = `tasklist /FI "STATUS eq running and USERNAME eq #{Makit::Environment.current_user}" 2>nul` results.split("\n").select { |line| line.include?(Makit::Environment.current_user) } else # Unix (Linux/Mac): return as array for consistency results = `ps aux | grep "#{name}" | grep -v grep 2>/dev/null` results.split("\n").reject(&:empty?) end end |
.run_background(command, pid_file, working_dir: nil, environment: {}) ⇒ Integer
Run a command in the background and track it with a PID file
This method starts a process in the background and stores its PID in a file for later management. It handles cross-platform differences in process spawning.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/makit/process.rb', line 174 def self.run_background(command, pid_file, working_dir: nil, environment: {}) # Ensure the command is not nil or empty raise ArgumentError, "Command cannot be nil or empty" if command.nil? || command.strip.empty? raise ArgumentError, "PID file path cannot be nil or empty" if pid_file.nil? || pid_file.strip.empty? # Convert command to array for Process.spawn cmd_array = if Makit::Environment.is_windows? # Windows: Use cmd /c for complex commands ["cmd", "/c", command] else # Unix-like: Use shell for command parsing ["sh", "-c", command] end # Prepare spawn options = {} [:chdir] = working_dir if working_dir [:pgroup] = true unless Makit::Environment.is_windows? # Set environment variables env = ENV.to_h.merge(environment) [:unsetenv_others] = false begin # Start the process pid = Process.spawn(env, *cmd_array, ) # Write PID to file File.write(pid_file, pid.to_s) # Give the process a moment to start sleep(0.5) # Verify the process is still running begin Process.getpgid(pid) if Makit::Environment.is_windows? Process.getpgid(pid) unless Makit::Environment.is_windows? rescue Errno::ESRCH raise StandardError, "Process failed to start or exited immediately" end pid rescue => e # Clean up PID file if process failed to start File.delete(pid_file) if File.exist?(pid_file) raise StandardError, "Failed to start background process: #{e.message}" end end |
.stop_background(pid_file, signal: "TERM") ⇒ Boolean
Stop a background process using its PID file
This method reads the PID from a file and terminates the process gracefully. It handles cross-platform differences in process termination.
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/makit/process.rb', line 231 def self.stop_background(pid_file, signal: "TERM") return false unless File.exist?(pid_file) begin pid = File.read(pid_file).strip.to_i return false if pid <= 0 # Check if process is still running begin if Makit::Environment.is_windows? # Windows: Use taskkill if signal == "TERM" system("taskkill /PID #{pid} /F >nul 2>&1") else system("taskkill /PID #{pid} /F >nul 2>&1") end else # Unix-like: Use kill with specified signal Process.kill(signal, pid) end # Wait a moment for the process to terminate sleep(0.5) # Verify the process is gone begin Process.getpgid(pid) # Process still running, force kill if TERM didn't work if signal == "TERM" return stop_background(pid_file, signal: "KILL") end return false rescue Errno::ESRCH # Process is gone, success true end rescue Errno::ESRCH, Errno::EPERM # Process was not running or permission denied false end rescue => e # Log error but don't raise puts "Error stopping background process: #{e.message}" if defined?(puts) false ensure # Clean up PID file File.delete(pid_file) if File.exist?(pid_file) end end |