Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let Store#store support large amounts of data #1636

Merged
merged 3 commits into from Dec 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 25 additions & 2 deletions nanoc-core/lib/nanoc/core/store.rb
Expand Up @@ -128,8 +128,9 @@ def unsafe_load_uninstrumented
self.data = read_obj_from_file(data_filename)
end

def write_obj_to_file(fn, obj)
File.binwrite(fn, Marshal.dump(obj))
def write_obj_to_file(filename, obj)
data = Marshal.dump(obj)
write_data_to_file(filename, data)
end

def read_obj_from_file(fn)
Expand All @@ -143,6 +144,28 @@ def version_filename
def data_filename
"#{filename}.data.db"
end

def write_data_to_file(filename, data)
basename = File.basename(filename)
dirname = File.dirname(filename)

# Write to a temporary file first, and then (atomically) move it into
# place.
Tempfile.open(".#{basename}", dirname) do |temp_file|
temp_file.binmode

# Write the data as a stream, because File.binwrite can’t
# necessarily deal with writing that much data all at once.
#
# See https://github.com/nanoc/nanoc/issues/1635.
reader = StringIO.new(data)
IO.copy_stream(reader, temp_file)
temp_file.close

# Rename (atomic)
File.rename(temp_file.path, filename)
end
end
end
end
end
21 changes: 21 additions & 0 deletions nanoc-core/spec/nanoc/core/store_spec.rb
Expand Up @@ -109,4 +109,25 @@ def gen_hash(path)
store.load
expect(store.data).to be_nil
end

it 'can write humongous amounts of data' do
# Skip running on GitHub actions etc because this thing just uses far too many resources
skip if ENV['CI']

store = test_store_klass.new('test', 1)

# Create huge string
array = []
100.times do |i|
raw = 'x' * 1_000_037
raw << i.to_s
io = StringIO.new
100.times { io << raw }
array << io.string
end

# Write
store.data = { data: array }
expect { store.store }.not_to raise_exception
end
end