Skip to content

Commit

Permalink
update logging and ensure no_timeout works as well
Browse files Browse the repository at this point in the history
  • Loading branch information
kbukum1 committed Dec 17, 2024
1 parent 81a3e19 commit b6aef5f
Showing 1 changed file with 28 additions and 10 deletions.
38 changes: 28 additions & 10 deletions common/lib/dependabot/command_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,15 @@ def to_s
end
end

# Default timeout for commands
DEFAULT_TIMEOUT = 120

DEFAULT_TIMEOUTS = T.let({
no_time_out: -1, # No timeout
local: 30, # Local commands
network: 120, # Network-dependent commands
network: DEFAULT_TIMEOUT, # Network-dependent commands
long_running: 300 # Long-running tasks (e.g., builds)
}.freeze, T::Hash[T.untyped, T.untyped])
}.freeze, T::Hash[Symbol, Integer])

# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
Expand All @@ -82,6 +85,7 @@ def self.capture3_with_timeout(
)
# Assign default timeout based on command type if timeout < 0
timeout = DEFAULT_TIMEOUTS[command_type] if timeout.negative?
timeout = DEFAULT_TIMEOUT if command_type != :no_time_out && (!timeout || timeout.negative?)

stdout = T.let("", String)
stderr = T.let("", String)
Expand All @@ -92,6 +96,9 @@ def self.capture3_with_timeout(
begin
T.unsafe(Open3).popen3(*env_cmd) do |stdin, stdout_io, stderr_io, wait_thr|
pid = wait_thr.pid
Dependabot.logger.info("Started process PID: #{pid} with command: #{env_cmd.join(' ')}")

# Write to stdin if input data is provided
stdin&.write(stdin_data) if stdin_data
stdin&.close

Expand All @@ -105,10 +112,11 @@ def self.capture3_with_timeout(

until ios.empty?
# Calculate remaining timeout dynamically
remaining_timeout = timeout - (Time.now - last_output_time)
remaining_timeout = T.must(timeout) - (Time.now - last_output_time) if command_type != :no_time_out

# Raise an error if timeout is exceeded
if remaining_timeout <= 0
if command_type != :no_time_out && T.must(remaining_timeout) <= 0
Dependabot.logger.warn("Process PID: #{pid} timed out after #{timeout}s. Terminating...")
terminate_process(pid)
status = ProcessStatus.new(wait_thr.value, 124)
raise Timeout::Error, "Timed out due to inactivity after #{timeout} seconds"
Expand All @@ -119,42 +127,50 @@ def self.capture3_with_timeout(

# Process ready IO streams
ready_ios&.first&.each do |io|
# Ensure UTF-8 encoding for input data
io.set_encoding("UTF-8", "UTF-8")

data = io.read_nonblock(1024)

last_output_time = Time.now
# Reset the timeout if data is received
last_output_time = Time.now if data
if io == stdout_io
stdout += data
else
stderr += data unless stderr_to_stdout
stdout += data if stderr_to_stdout
end
rescue EOFError
# Remove the stream when EOF is reached
ios.delete(io)
rescue IO::WaitReadable
# Continue when IO is not ready yet
next
end
end

status = ProcessStatus.new(wait_thr.value)
Dependabot.logger.info("Process PID: #{pid} completed with status: #{status}")
end
rescue Timeout::Error => e
Dependabot.logger.error("Process PID: #{pid} failed due to timeout: #{e.message}")
stderr += e.message unless stderr_to_stdout
stdout += e.message if stderr_to_stdout
rescue Errno::ENOENT => e
Dependabot.logger.error("Command failed: #{e.message}")
stderr += e.message unless stderr_to_stdout
stdout += e.message if stderr_to_stdout
end

elapsed_time = Time.now - start_time
Dependabot.logger.info("Total execution time: #{elapsed_time.round(2)} seconds")
[stdout, stderr, status, elapsed_time]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity

# Terminate a process by PID
sig { params(pid: T.nilable(Integer)).void }
def self.terminate_process(pid)
return unless pid
Expand All @@ -178,21 +194,23 @@ def self.terminate_process(pid)
end
end

# Check if the process is still alive
sig { params(pid: T.nilable(Integer)).returns(T::Boolean) }
def self.process_alive?(pid)
return false if pid.nil? # No PID, consider process not alive
return false if pid.nil?

begin
Process.kill(0, pid) # Check if the process exists
true # Process is still alive
true
rescue Errno::ESRCH
false # Process does not exist (terminated successfully)
false
rescue Errno::EPERM
Dependabot.logger.error("Insufficient permissions to check process: #{pid}")
false # Assume process not alive due to lack of permissions
false
end
end

# Escape shell commands to ensure safe execution
sig { params(command: String).returns(String) }
def self.escape_command(command)
command_parts = command.split.map(&:strip).reject(&:empty?)
Expand Down

0 comments on commit b6aef5f

Please sign in to comment.