diff --git a/include/seastar/util/file.hh b/include/seastar/util/file.hh index 1b9fa025695..97b7c732a2a 100644 --- a/include/seastar/util/file.hh +++ b/include/seastar/util/file.hh @@ -102,6 +102,18 @@ future>> read_entire_file(std::filesystem::pa /// \param path path of the file to be read. future read_entire_file_contiguous(std::filesystem::path path); +/// Removes a regular file via blocks discarding. +/// +/// \param name name of the file to remove. +/// +/// \note +/// The removal is processed by the I/O queue according to its configuration. +/// The removal may be delayed when the bandwidth is not available. The user +/// must ensure, that the file is not used until the removal finishes. +/// +/// Effectively: opens a file, unlinks it and punches holes until nothing is left. +future<> remove_file_via_blocks_discarding(std::string_view name) noexcept; + SEASTAR_MODULE_EXPORT_END /// @} diff --git a/src/core/file.cc b/src/core/file.cc index 3e7508d14e8..bd151f51d42 100644 --- a/src/core/file.cc +++ b/src/core/file.cc @@ -268,7 +268,11 @@ posix_file_impl::fcntl_short(int op, uintptr_t arg) noexcept { future<> posix_file_impl::discard(uint64_t offset, uint64_t length) noexcept { - return engine().punch_hole(_fd, offset, length); + internal::maybe_priority_class_ref io_priority_class; + auto req = internal::io_request::make_discard(_fd, offset, length); + return _io_queue.submit_io_discard(internal::priority_class(io_priority_class), length, std::move(req), nullptr).then([] (auto len) { + return make_ready_future<>(); + }); } future<> diff --git a/src/util/file.cc b/src/util/file.cc index 99dacda6e84..517d94747a8 100644 --- a/src/util/file.cc +++ b/src/util/file.cc @@ -37,6 +37,7 @@ module seastar; #else #include #include +#include #include #endif @@ -227,6 +228,70 @@ future read_entire_file_contiguous(std::filesystem::path path) { }); } +future<> remove_file_via_blocks_discarding(std::string_view name) noexcept { + return open_file_dma(name, open_flags::rw, file_open_options{}).then([name = sstring(name)](file f) mutable { + return f.size().then_wrapped([f, name = std::move(name)](future fut) mutable { + // If getting size failed then close file and report error gracefully. + if (fut.failed()) { + return f.close().then([f, ex = fut.get_exception()] () mutable { + return make_exception_future(ex); + }); + } + + return remove_file(name).then_wrapped([f, fsize = fut.get()] (future<> fut) mutable { + // If unlink failed, then close file and report error gracefully. + if (fut.failed()) { + return f.close().then([f, ex = fut.get_exception()] () mutable { + return make_exception_future(ex); + }); + } + + // If file was empty, then unlink is sufficient. + if (fsize == 0u) { + return f.close().finally([f](){}); + } + + // Else, issue discards for all blocks. + const uint64_t block_size{32u << 20u}; // 32MiB + const uint64_t additional_iteration = (fsize % block_size == 0) ? 0 : 1; + const uint64_t blocks_count{static_cast(fsize / block_size) + additional_iteration}; + + lw_shared_ptr>> futs; + try { + futs = make_lw_shared>>(); + futs->reserve(blocks_count); + } catch (...) { + return f.close().then([f, ex = std::current_exception()] { + return make_exception_future(ex); + }); + } + + for (uint64_t i = 0u; i < blocks_count; ++i) { + auto offset = i * block_size; + auto discard_end = std::min(offset + block_size, fsize); + auto length = discard_end - offset; + futs->push_back(f.discard(offset, length)); + } + + // Wait until all finish and close file. + return when_all(futs->begin(), futs->end()).then([futs, f] (auto results) mutable { + for (auto&& res : results) { + if (res.failed()) { + return f.close().then([f, ex = res.get_exception()] () mutable { + return make_exception_future<>(ex); + }); + } + + res.ignore_ready_future(); + } + + return f.close().finally([f](){}); + }); + }); + }); + }); +} + } // namespace util } //namespace seastar