From 68f26c8e2adf88905a365528f6e72323c5ede080 Mon Sep 17 00:00:00 2001
From: Ruslan Tushov <turuslan@users.noreply.github.com>
Date: Tue, 14 Jan 2025 14:39:35 +0500
Subject: [PATCH] wasmedge compile tmp (#2336)

Signed-off-by: turuslan <turuslan.devbox@gmail.com>
---
 .../runtime/wasm_edge/module_factory_impl.cpp |  6 +-
 core/utils/write_file.hpp                     | 67 +++++++++++++++----
 2 files changed, 60 insertions(+), 13 deletions(-)

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 <boost/filesystem/operations.hpp>
+#include <filesystem>
 #include <fstream>
+#include <optional>
 #include <qtils/bytestr.hpp>
+#include <qtils/option_take.hpp>
 #include <qtils/outcome.hpp>
 
 namespace kagome {
@@ -26,20 +29,60 @@ namespace kagome {
     return writeFile(path, qtils::byte2str(data));
   }
 
-  outcome::result<void> 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<TmpFile> 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<void> 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<std::filesystem::path> tmp;
+  };
+
+  outcome::result<void> 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