Merge pull request #4949 from MikeMcQuaid/dbm_crash

cache_store: handle corrupt DBM database.
This commit is contained in:
Mike McQuaid 2018-09-20 11:16:02 +01:00 committed by GitHub
commit 97dca89e7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 9 deletions

View File

@ -1,5 +1,6 @@
require "dbm"
require "json"
require "timeout"
#
# `CacheStoreDatabase` acts as an interface to a persistent storage mechanism
@ -46,7 +47,7 @@ class CacheStoreDatabase
#
# @return [Boolean]
def created?
File.exist?(cache_path)
cache_path.exist?
end
private
@ -57,6 +58,10 @@ class CacheStoreDatabase
# https://docs.oracle.com/cd/E17276_01/html/api_reference/C/envopen.html
DATABASE_MODE = 0664
# Spend 5 seconds trying to read the DBM file. If it takes longer than this it
# has likely hung or segfaulted.
DBM_TEST_READ_TIMEOUT = 5
# Lazily loaded database in read/write mode. If this method is called, a
# database file with be created in the `HOMEBREW_CACHE` with name
# corresponding to the `@type` instance variable
@ -66,6 +71,29 @@ class CacheStoreDatabase
# DBM::WRCREAT: Creates the database if it does not already exist
@db ||= begin
HOMEBREW_CACHE.mkpath
if created?
dbm_test_read_cmd = SystemCommand.new(
ENV["HOMEBREW_RUBY_PATH"],
args: [
"-rdbm",
"-e",
"DBM.open('#{dbm_file_path}', #{DATABASE_MODE}, DBM::READER).size",
],
print_stderr: false,
must_succeed: true,
)
dbm_test_read_success = begin
Timeout.timeout(DBM_TEST_READ_TIMEOUT) do
dbm_test_read_cmd.run!
true
end
rescue ErrorDuringExecution, Timeout::Error
odebug "Failed to read #{dbm_file_path}!"
Process.kill(:KILL, dbm_test_read_cmd.pid)
false
end
cache_path.delete unless dbm_test_read_success
end
DBM.open(dbm_file_path, DATABASE_MODE, DBM::WRCREAT)
end
end
@ -83,7 +111,7 @@ class CacheStoreDatabase
#
# @return [String]
def dbm_file_path
File.join(HOMEBREW_CACHE, @type.to_s)
"#{HOMEBREW_CACHE}/#{@type}"
end
# The path where the database resides in the `HOMEBREW_CACHE` for the given
@ -91,7 +119,7 @@ class CacheStoreDatabase
#
# @return [String]
def cache_path
"#{dbm_file_path}.db"
Pathname("#{dbm_file_path}.db")
end
end

View File

@ -19,6 +19,8 @@ end
class SystemCommand
extend Predicable
attr_reader :pid
def self.run(executable, **options)
new(executable, **options).run!
end
@ -122,6 +124,7 @@ class SystemCommand
raw_stdin, raw_stdout, raw_stderr, raw_wait_thr =
Open3.popen3(env, [executable, executable], *args, **options)
@pid = raw_wait_thr.pid
write_input_to(raw_stdin)
raw_stdin.close_write
@ -191,6 +194,7 @@ class SystemCommand
end
def success?
return false if @exit_status.nil?
@exit_status.zero?
end

View File

@ -118,15 +118,15 @@ describe CacheStoreDatabase do
end
describe "#created?" do
let(:cache_path) { "path/to/homebrew/cache/sample.db" }
let(:cache_path) { Pathname("path/to/homebrew/cache/sample.db") }
before(:each) do
allow(subject).to receive(:cache_path).and_return(cache_path)
end
context "`File.exist?(cache_path)` returns `true`" do
context "`cache_path.exist?` returns `true`" do
before(:each) do
allow(File).to receive(:exist?).with(cache_path).and_return(true)
allow(cache_path).to receive(:exist?).and_return(true)
end
it "returns `true`" do
@ -134,9 +134,9 @@ describe CacheStoreDatabase do
end
end
context "`File.exist?(cache_path)` returns `false`" do
context "`cache_path.exist?` returns `false`" do
before(:each) do
allow(File).to receive(:exist?).with(cache_path).and_return(false)
allow(cache_path).to receive(:exist?).and_return(false)
end
it "returns `false`" do

View File

@ -47,7 +47,7 @@ setup-ruby-path() {
then
odie "Failed to install vendor Ruby."
fi
rm -rf "$vendor_dir/bundle/ruby" "$HOMEBREW_CACHE/linkage.db"
rm -rf "$vendor_dir/bundle/ruby"
HOMEBREW_RUBY_PATH="$vendor_ruby_path"
fi
fi