class Bootsnap::LoadPathCache::Store

Constants

CURRENT_VERSION
NestedTransactionError
SetOutsideTransactionNotAllowed
VERSION_KEY

Public Class Methods

new(store_path, readonly: false) click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 16
def initialize(store_path, readonly: false)
  @store_path = store_path
  @txn_mutex = Mutex.new
  @dirty = false
  @readonly = readonly
  load_data
end

Public Instance Methods

fetch(key) { || ... } click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 28
def fetch(key)
  raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?

  v = get(key)
  unless v
    v = yield
    mark_for_mutation!
    @data[key] = v
  end
  v
end
get(key) click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 24
def get(key)
  @data[key]
end
set(key, value) click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 40
def set(key, value)
  raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?

  if value != @data[key]
    mark_for_mutation!
    @data[key] = value
  end
end
transaction() { || ... } click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 49
def transaction
  raise(NestedTransactionError) if @txn_mutex.owned?

  @txn_mutex.synchronize do
    yield
  ensure
    commit_transaction
  end
end

Private Instance Methods

commit_transaction() click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 66
def commit_transaction
  if @dirty && !@readonly
    dump_data
    @dirty = false
  end
end
default_data() click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 112
def default_data
  {VERSION_KEY => CURRENT_VERSION}
end
dump_data() click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 95
def dump_data
  # Change contents atomically so other processes can't get invalid
  # caches if they read at an inopportune time.
  tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100_000).to_i}.tmp"
  mkdir_p(File.dirname(tmp))
  exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
  # `encoding:` looks redundant wrt `binwrite`, but necessary on windows
  # because binary is part of mode.
  File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
    MessagePack.dump(@data, io)
  end
  File.rename(tmp, @store_path)
rescue Errno::EEXIST
  retry
rescue SystemCallError
end
load_data() click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 73
def load_data
  @data = begin
    data = File.open(@store_path, encoding: Encoding::BINARY) do |io|
      MessagePack.load(io, freeze: true)
    end
    if data.is_a?(Hash) && data[VERSION_KEY] == CURRENT_VERSION
      data
    else
      default_data
    end
  # handle malformed data due to upgrade incompatibility
  rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
    default_data
  rescue ArgumentError => error
    if error.message =~ /negative array size/
      default_data
    else
      raise
    end
  end
end
mark_for_mutation!() click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 61
def mark_for_mutation!
  @dirty = true
  @data = @data.dup if @data.frozen?
end
mkdir_p(path) click to toggle source
# File lib/bootsnap/load_path_cache/store.rb, line 116
def mkdir_p(path)
  stack = []
  until File.directory?(path)
    stack.push path
    path = File.dirname(path)
  end
  stack.reverse_each do |dir|
    Dir.mkdir(dir)
  rescue SystemCallError
    raise unless File.directory?(dir)
  end
end