diff options
Diffstat (limited to 'libbuild2/file-cache.cxx')
-rw-r--r-- | libbuild2/file-cache.cxx | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/libbuild2/file-cache.cxx b/libbuild2/file-cache.cxx new file mode 100644 index 0000000..caaf40c --- /dev/null +++ b/libbuild2/file-cache.cxx @@ -0,0 +1,190 @@ +// file : libbuild2/file-cache.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/file-cache.hxx> + +#include <libbutl/lz4.hxx> + +#include <libbuild2/filesystem.hxx> // exists(), try_rmfile() +#include <libbuild2/diagnostics.hxx> + +using namespace butl; + +namespace build2 +{ + // file_cache::entry + // + file_cache::write file_cache::entry:: + init_new () + { + assert (state_ == uninit); + + // Remove stale compressed file if it exists. While not strictly necessary + // (since the presence of the new uncompressed file will render the + // compressed one invalid), this makes things cleaner in case we don't get + // to compressing the new file (for example, if we fail and leave the + // uncompressed file behind for troubleshooting). + // + if (!comp_path_.empty ()) + try_rmfile_ignore_error (comp_path_); + + // Note: state remains uninit until write::close(). + + pin (); + return write (*this); + } + + void file_cache::entry:: + init_existing () + { + assert (state_ == uninit); + + // Determine the cache state from the filesystem state. + // + // First check for the uncompressed file. Its presence means that the + // compressed file, if exists, is invalid and we clean it up, similar to + // init_new(). + // + // Note that if compression is disabled, we omit the check assuming the + // the uncompressed file exists. + // + if (!comp_path_.empty ()) + { + if (exists (path_)) + { + try_rmfile_ignore_error (comp_path_); + state_ = uncomp; + } + else if (exists (comp_path_)) + { + state_ = comp; + } + else + fail << comp_path_ << " (or its uncompressed variant) does not exist" << + info << "consider cleaning the build state"; + } + else + state_ = uncomp; + } + + void file_cache::entry:: + preempt () + { + // Note that this function is called from destructors so it's best if it + // doesn't throw. + // + switch (state_) + { + case uncomp: + { + if (!compress ()) + break; + + state_ = decomp; // We now have both. + } + // Fall through. + case decomp: + { + if (try_rmfile_ignore_error (path_)) + state_ = comp; + + break; + } + default: + assert (false); + } + } + + bool file_cache::entry:: + compress () + { + tracer trace ("file_cache::entry::compress"); + + try + { + ifdstream ifs (path_, fdopen_mode::binary, ifdstream::badbit); + ofdstream ofs (comp_path_, fdopen_mode::binary); + + uint64_t n (fdstat (ifs.fd ()).size); + + // Experience shows that for the type of content we typically cache + // using 1MB blocks results in almost the same comression as for 4MB. + // + uint64_t cn (lz4::compress (ofs, ifs, + 1 /* compression_level (fastest) */, + 6 /* block_size_id (1MB) */, + n)); + + ofs.close (); + + l6 ([&]{trace << "compressed " << path_ << " to " + << (cn * 100 / n) << '%';}); + } + catch (const std::exception& e) + { + l5 ([&]{trace << "unable to compress " << path_ << ": " << e;}); + try_rmfile_ignore_error (comp_path_); + return false; + } + + return true; + } + + void file_cache::entry:: + decompress () + { + try + { + ifdstream ifs (comp_path_, fdopen_mode::binary, ifdstream::badbit); + ofdstream ofs (path_, fdopen_mode::binary); + + lz4::decompress (ofs, ifs); + + ofs.close (); + } + catch (const std::exception& e) + { + fail << "unable to decompress " << comp_path_ << ": " << e << + info << "consider cleaning the build state"; + } + } + + void file_cache::entry:: + remove () + { + switch (state_) + { + case uninit: + { + // In this case we are cleaning the filesystem without having any idea + // about its state. As a result, if we couldn't remove the compressed + // file, then we don't attempt to remove the uncompressed file either + // since it could be an indicator that the compressed file is invalid. + // + if (comp_path_.empty () || try_rmfile_ignore_error (comp_path_)) + try_rmfile_ignore_error (path_); + break; + } + case uncomp: + { + try_rmfile_ignore_error (path_); + break; + } + case comp: + { + try_rmfile_ignore_error (comp_path_); + break; + } + case decomp: + { + // Both are valid so we are ok with failing to remove either. + // + try_rmfile_ignore_error (comp_path_); + try_rmfile_ignore_error (path_); + break; + } + case null: + assert (false); + } + } +} |