diff --git a/core/runtime/wasm_edge/module_factory_impl.cpp b/core/runtime/wasm_edge/module_factory_impl.cpp index 5176e092bf..36f6e99f5e 100644 --- a/core/runtime/wasm_edge/module_factory_impl.cpp +++ b/core/runtime/wasm_edge/module_factory_impl.cpp @@ -414,8 +414,12 @@ namespace kagome::runtime::wasm_edge { CompilerContext compiler = WasmEdge_CompilerCreate(configure_ctx_raw); SL_INFO(log_, "Start compiling wasm module {}", path_compiled); + // Multiple processes can write to same cache file concurrently, + // write to tmp file first to avoid conflict. + OUTCOME_TRY(tmp, TmpFile::make(path_compiled)); WasmEdge_UNWRAP_COMPILE_ERR(WasmEdge_CompilerCompileFromBuffer( - compiler.raw(), code.data(), code.size(), path_compiled.c_str())); + compiler.raw(), code.data(), code.size(), tmp.path().c_str())); + OUTCOME_TRY(tmp.rename()); SL_INFO(log_, "Compilation finished, saved at {}", path_compiled); return outcome::success(); } diff --git a/core/utils/write_file.hpp b/core/utils/write_file.hpp index 220172e5c3..30c4be4883 100644 --- a/core/utils/write_file.hpp +++ b/core/utils/write_file.hpp @@ -7,8 +7,11 @@ #pragma once #include +#include #include +#include #include +#include #include namespace kagome { @@ -26,20 +29,60 @@ namespace kagome { return writeFile(path, qtils::byte2str(data)); } - outcome::result writeFileTmp(const std::filesystem::path &path, - auto &&data) { - boost::system::error_code ec1; - auto tmp = - boost::filesystem::unique_path(path.native() + ".%%%%", ec1).native(); - if (ec1) { - return ec1; + /** + * Wrapper to generate tmp file name and rename it later. + * Concurrent writers would corrupt file, so they write to tmp file first, and + * atomically rename it after file is completely written. + */ + struct TmpFile { + /** + * Generate tmp file name for path. + * Tmp file is created in same directory as target path, + * to avoid `EXDEV` error from `rename`. + */ + static outcome::result make(std::filesystem::path path) { + boost::system::error_code ec; + auto tmp = + boost::filesystem::unique_path(path.native() + ".%%%%", ec).native(); + if (ec) { + return ec; + } + return TmpFile{std::move(path), std::move(tmp)}; } - OUTCOME_TRY(writeFile(tmp, data)); - std::error_code ec2; - std::filesystem::rename(tmp, path, ec2); - if (ec2) { - return ec2; + + TmpFile(std::filesystem::path target, std::filesystem::path tmp) + : target{std::move(target)}, tmp{std::move(tmp)} {} + + /** + * Get current file path. + */ + std::filesystem::path path() const { + return tmp.value_or(target); } + + /** + * Rename file to target name. + */ + outcome::result rename() { + if (auto tmp = qtils::optionTake(this->tmp)) { + std::error_code ec; + std::filesystem::rename(*tmp, target, ec); + if (ec) { + return ec; + } + } + return outcome::success(); + } + + std::filesystem::path target; + std::optional tmp; + }; + + outcome::result writeFileTmp(const std::filesystem::path &path, + auto &&data) { + OUTCOME_TRY(tmp, TmpFile::make(path)); + OUTCOME_TRY(writeFile(tmp.path(), data)); + OUTCOME_TRY(tmp.rename()); return outcome::success(); } } // namespace kagome