#Centipede Implement buffered writing in OSS variant of RemoteFile API PiperOrigin-RevId: 622994037
diff --git a/centipede/remote_file.cc b/centipede/remote_file.cc index 7b4e9f8..f490c0c 100644 --- a/centipede/remote_file.cc +++ b/centipede/remote_file.cc
@@ -18,6 +18,7 @@ #include "./centipede/remote_file.h" #include <glob.h> +#include <sys/stat.h> #include <cstdint> #include <cstdio> @@ -43,6 +44,84 @@ namespace centipede { +namespace { + +// A basic version of "remote file" that can only actually handle local files. +// This provides a polyfill that enables running Centipede on local filesystems. +// It can be replaced by an implementation for an actual remote filesystem by +// overriding some of the weak symbols below (the ones that either return or +// take a `RemoteFile *` param). +// TODO(ussuri): This stops just one step away from making `RemoteFile` an +// abstract virtual base. Measure the performance of doing so and maybe switch. +class LocalRemoteFile : public RemoteFile { + public: + static LocalRemoteFile *Create(std::string_view path, std::string_view mode) { + FILE *file = std::fopen(path.data(), mode.data()); + if (file == nullptr) { + LOG(ERROR) << "Failed to open file: " << VV(path); + return nullptr; + } + return new LocalRemoteFile{path, file}; + } + + ~LocalRemoteFile() { + CHECK(file_ == nullptr) << "Dtor called before Close(): " << VV(path_); + } + + // Movable but not copyable. + LocalRemoteFile(const LocalRemoteFile &) = delete; + LocalRemoteFile &operator=(const LocalRemoteFile &) = delete; + LocalRemoteFile(LocalRemoteFile &&) = default; + LocalRemoteFile &operator=(LocalRemoteFile &&) = default; + + void SetWriteBufSize(size_t size) { + CHECK(write_buf_ == nullptr) << "SetWriteBufCapacity called twice"; + write_buf_ = std::make_unique<char[]>(size); + CHECK_EQ(std::setvbuf(file_, write_buf_.get(), _IOFBF, size), 0) + << VV(path_); + } + + void Write(const ByteArray &ba) { + static constexpr auto elt_size = sizeof(ba[0]); + const auto elts_to_write = ba.size(); + const auto elts_written = + std::fwrite(ba.data(), elt_size, elts_to_write, file_); + CHECK_EQ(elts_written, elts_to_write) << VV(path_); + } + + void Flush() { std::fflush(file_); } + + void Read(ByteArray &ba) { + // Compute the file size as a difference between the end and start offsets. + CHECK_EQ(std::fseek(file_, 0, SEEK_END), 0) << VV(path_); + const auto file_size = std::ftell(file_); + CHECK_EQ(std::fseek(file_, 0, SEEK_SET), 0) << VV(path_); + static constexpr auto elt_size = sizeof(ba[0]); + CHECK_EQ(file_size % elt_size, 0) + << VV(file_size) << VV(elt_size) << VV(path_); + const auto elts_to_read = file_size / elt_size; + ba.resize(elts_to_read); + const auto elts_read = std::fread(ba.data(), elt_size, elts_to_read, file_); + CHECK_EQ(elts_read, elts_to_read) << VV(path_); + } + + void Close() { + CHECK_EQ(std::fclose(file_), 0) << VV(path_); + file_ = nullptr; + write_buf_ = nullptr; + } + + private: + LocalRemoteFile(std::string_view path, FILE *file) + : path_{path}, file_{file} {} + + std::string_view path_; + FILE *file_; + std::unique_ptr<char[]> write_buf_; +}; + +} // namespace + // NOTE: We use weak symbols for the main API definitions in this source so that // alternative implementations could easily override them with their own // versions at link time. @@ -56,30 +135,28 @@ CHECK(!error) << VV(path) << VV(error); } +// TODO(ussuri): For now, simulate the old behavior, where a failure to open +// a file returned nullptr. Adjust the clients to expect non-null and use a +// normal ctor with a CHECK instead of `Create()` here instead. ABSL_ATTRIBUTE_WEAK absl::Nullable<RemoteFile *> RemoteFileOpen( std::string_view path, const char *mode) { - CHECK(!path.empty()); - FILE *f = std::fopen(path.data(), mode); - return reinterpret_cast<RemoteFile *>(f); + return LocalRemoteFile::Create(path, mode); } ABSL_ATTRIBUTE_WEAK void RemoteFileClose(absl::Nonnull<RemoteFile *> f) { - std::fclose(reinterpret_cast<FILE *>(f)); + auto *file = static_cast<LocalRemoteFile *>(f); + file->Close(); + delete file; } ABSL_ATTRIBUTE_WEAK void RemoteFileSetWriteBufferSize( absl::Nonnull<RemoteFile *> f, size_t size) { - // NOTE: A no-op for now. + static_cast<LocalRemoteFile *>(f)->SetWriteBufSize(size); } ABSL_ATTRIBUTE_WEAK void RemoteFileAppend(absl::Nonnull<RemoteFile *> f, const ByteArray &ba) { - auto *file = reinterpret_cast<FILE *>(f); - constexpr auto elt_size = sizeof(ba[0]); - const auto elts_to_write = ba.size(); - const auto elts_written = - std::fwrite(ba.data(), elt_size, elts_to_write, file); - CHECK_EQ(elts_written, elts_to_write); + static_cast<LocalRemoteFile *>(f)->Write(ba); } // Does not need weak attribute as the implementation depends on @@ -91,22 +168,12 @@ } ABSL_ATTRIBUTE_WEAK void RemoteFileFlush(absl::Nonnull<RemoteFile *> f) { - auto *file = reinterpret_cast<FILE *>(f); - std::fflush(file); + static_cast<LocalRemoteFile *>(f)->Flush(); } ABSL_ATTRIBUTE_WEAK void RemoteFileRead(absl::Nonnull<RemoteFile *> f, ByteArray &ba) { - auto *file = reinterpret_cast<FILE *>(f); - std::fseek(file, 0, SEEK_END); // seek to end - const auto file_size = std::ftell(file); - std::fseek(file, 0, SEEK_SET); // seek back to start - constexpr auto elt_size = sizeof(ba[0]); - CHECK_EQ(file_size % elt_size, 0) << VV(file_size) << VV(elt_size); - const auto elts_to_read = file_size / elt_size; - ba.resize(elts_to_read); - const auto elts_read = std::fread(ba.data(), elt_size, elts_to_read, file); - CHECK_EQ(elts_read, elts_to_read); + static_cast<LocalRemoteFile *>(f)->Read(ba); } // Does not need weak attribute as the implementation depends on