module Gitlab module Git module Storage module ForkedStorageCheck extend self def storage_available?(path, timeout_seconds = 5, retries = 1) partial_timeout = timeout_seconds / retries status = timeout_check(path, partial_timeout) # If the status check did not succeed the first time, we retry a few # more times to avoid one-off failures current_attempts = 1 while current_attempts < retries && !status.success? status = timeout_check(path, partial_timeout) current_attempts += 1 end status.success? end def timeout_check(path, timeout_seconds) filesystem_check_pid = check_filesystem_in_process(path) deadline = timeout_seconds.seconds.from_now.utc wait_time = 0.01 status = nil while status.nil? if deadline > Time.now.utc sleep(wait_time) _pid, status = Process.wait2(filesystem_check_pid, Process::WNOHANG) else Process.kill('KILL', filesystem_check_pid) # Blocking wait, so we are sure the process is gone before continuing _pid, status = Process.wait2(filesystem_check_pid) end end status end # This will spawn a new 2 processes to do the check: # The outer child (waiter) will spawn another child process (stater). # # The stater is the process is performing the actual filesystem check # the check might hang if the filesystem is acting up. # In this case we will send a `KILL` to the waiter, which will still # be responsive while the stater is hanging. def check_filesystem_in_process(path) spawn('ruby', '-e', ruby_check, path, [:out, :err] => '/dev/null') end def ruby_check <<~RUBY_FILESYSTEM_CHECK inner_pid = fork { File.stat(ARGV.first) } Process.waitpid(inner_pid) exit $?.exitstatus RUBY_FILESYSTEM_CHECK end end end end end