From 2809f11839ff0fd4eec45b2e116811b0698dd05b Mon Sep 17 00:00:00 2001 From: Naios Date: Sun, 17 Apr 2016 21:31:04 +0200 Subject: Core/Scripting: Use the systems temporary path for caching shared libs * Disables the shared library caching on platforms completely which never block files on usage (like linux). (cherry picked from commit 081720b5dd5a80aa77181712d85d309d64d3ec03) --- src/server/game/Scripting/ScriptReloadMgr.cpp | 170 ++++++++++++++++++-------- 1 file changed, 119 insertions(+), 51 deletions(-) (limited to 'src/server/game/Scripting/ScriptReloadMgr.cpp') diff --git a/src/server/game/Scripting/ScriptReloadMgr.cpp b/src/server/game/Scripting/ScriptReloadMgr.cpp index 5b492306cd8..6862125369b 100644 --- a/src/server/game/Scripting/ScriptReloadMgr.cpp +++ b/src/server/game/Scripting/ScriptReloadMgr.cpp @@ -56,6 +56,7 @@ ScriptReloadMgr* ScriptReloadMgr::instance() #include "Config.h" #include "BuiltInConfig.h" #include "ScriptMgr.h" +#include "SHA1.h" #include "StartProcess.h" #include "MPSCQueue.h" #include "GitRevision.h" @@ -64,8 +65,10 @@ namespace fs = boost::filesystem; #ifdef _WIN32 #include + #define HOTSWAP_PLATFORM_REQUIRES_CACHING #else // Posix #include + // #define HOTSWAP_PLATFORM_REQUIRES_CACHING #endif // Promote the sScriptReloadMgr to a HotSwapScriptReloadMgr @@ -102,10 +105,10 @@ typedef void* HandleType; class SharedLibraryUnloader { public: - SharedLibraryUnloader() - : _path() { } - explicit SharedLibraryUnloader(fs::path const& path) - : _path(path) { } + explicit SharedLibraryUnloader(fs::path path) + : path_(std::move(path)) { } + SharedLibraryUnloader(fs::path path, Optional cache_path) + : path_(std::move(path)), cache_path_(std::move(cache_path)) { } void operator() (HandleType handle) const { @@ -119,26 +122,37 @@ public: if (!success) { TC_LOG_ERROR("scripts.hotswap", "Failed to unload (syscall) the shared library \"%s\".", - _path.generic_string().c_str()); + path_.generic_string().c_str()); return; } - boost::system::error_code error; - if (fs::remove(_path, error)) + /// When the shared library was cached delete it's shared version + if (cache_path_) { - TC_LOG_TRACE("scripts.hotswap", "Lazy unloaded and deleted the shared library \"%s\".", - _path.generic_string().c_str()); + boost::system::error_code error; + if (!fs::remove(*cache_path_, error)) + { + TC_LOG_ERROR("scripts.hotswap", "Failed to delete the cached shared library \"%s\" (%s).", + cache_path_->generic_string().c_str(), error.message().c_str()); + + return; + } + + TC_LOG_TRACE("scripts.hotswap", "Lazy unloaded the shared library \"%s\" " + "and deleted it's cached version at \"%s\"", + path_.generic_string().c_str(), cache_path_->generic_string().c_str()); } else { - TC_LOG_ERROR("scripts.hotswap", "Failed to delete the shared library \"%s\" (%s).", - _path.generic_string().c_str(), error.message().c_str()); + TC_LOG_TRACE("scripts.hotswap", "Lazy unloaded the shared library \"%s\".", + path_.generic_string().c_str()); } } private: - fs::path _path; + fs::path const path_; + Optional const cache_path_; }; typedef std::unique_ptr::type, SharedLibraryUnloader> HandleHolder; @@ -165,7 +179,8 @@ public: ScriptModule& operator= (ScriptModule const&) = delete; ScriptModule& operator= (ScriptModule&& right) = delete; - static Optional> CreateFromPath(fs::path const& path); + static Optional> + CreateFromPath(fs::path const& path, Optional cache_path); char const* GetScriptModuleRevisionHash() const override { @@ -215,23 +230,41 @@ static bool GetFunctionFromSharedLibrary(HandleType handle, std::string const& n } // Load a shared library from the given path. -Optional> ScriptModule::CreateFromPath(fs::path const& path) +Optional> + ScriptModule::CreateFromPath(fs::path const& path, Optional cache_path) { + auto const load_path = [&] () -> fs::path { + if (cache_path) + return *cache_path; + else + return path; + }(); + #ifdef _WIN32 - HandleType handle = LoadLibrary(path.generic_string().c_str()); + HandleType handle = LoadLibrary(load_path.generic_string().c_str()); #else // Posix - HandleType handle = dlopen(path.c_str(), RTLD_LAZY); + HandleType handle = dlopen(load_path.generic_string().c_str(), RTLD_LAZY); #endif if (!handle) { - TC_LOG_ERROR("scripts.hotswap", "Could not load the shared library \"%s\" for reading.", - path.generic_string().c_str()); + if (cache_path) + { + TC_LOG_ERROR("scripts.hotswap", "Could not dynamic load the shared library \"%s\" " + "(the library is cached at %s)", + path.generic_string().c_str(), cache_path->generic_string().c_str()); + } + else + { + TC_LOG_ERROR("scripts.hotswap", "Could not dynamic load the shared library \"%s\".", + path.generic_string().c_str()); + } + return boost::none; } // Use RAII to release the library on failure. - HandleHolder holder(handle, SharedLibraryUnloader(path)); + HandleHolder holder(handle, SharedLibraryUnloader(path, std::move(cache_path))); GetScriptModuleRevisionHashType getScriptModuleRevisionHash; AddScriptsType addScripts; @@ -357,7 +390,7 @@ static bool IsDebuggerBlockingRebuild() /// /// This class manages shared library loading/unloading through watching /// the script module directory. Loaded shared libraries are mirrored -/// into a .cache subdirectory to allow lazy unloading as long as +/// into a cache subdirectory to allow lazy unloading as long as /// the shared library is still used which is useful for scripts /// which can't be instantly replaced like spells or instances. /// Several modules which reference different versions can be kept loaded @@ -545,28 +578,28 @@ public: } } - // Get the cache directory path - fs::path const cache_path = [] - { - auto path = fs::absolute(sScriptReloadMgr->GetLibraryDirectory()); - path /= ".cache"; - return path; - }(); + #ifdef HOTSWAP_PLATFORM_REQUIRES_CACHING + + temporary_cache_path_ = CalculateTemporaryCachePath(); // We use the boost filesystem function versions which accept // an error code to prevent it from throwing exceptions. boost::system::error_code code; - if ((!fs::exists(cache_path, code) || (fs::remove_all(cache_path, code) > 0)) && - !fs::create_directory(cache_path, code)) + if ((!fs::exists(temporary_cache_path_, code) + || (fs::remove_all(temporary_cache_path_, code) > 0)) && + !fs::create_directory(temporary_cache_path_, code)) { - TC_LOG_ERROR("scripts.hotswap", "Couldn't create the cache directory \"%s\".", - cache_path.generic_string().c_str()); + TC_LOG_ERROR("scripts.hotswap", "Couldn't create the cache directory at \"%s\".", + temporary_cache_path_.generic_string().c_str()); + return; } // Used to silent compiler warnings (void)code; + #endif // #ifdef HOTSWAP_PLATFORM_REQUIRES_CACHING + // Correct the CMake prefix when needed if (sWorld->getBoolConfig(CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED)) DoCMakePrefixCorrectionIfNeeded(); @@ -698,6 +731,32 @@ private: _fileWatcher.watch(); } + static fs::path CalculateTemporaryCachePath() + { + auto path = fs::temp_directory_path(); + + path /= Trinity::StringFormat("tc_script_cache_%s_%s", + GitRevision::GetBranch(), + CalculateSHA1Hash(sConfigMgr->GetFilename()).c_str()); + + return path; + } + + fs::path GenerateUniquePathForLibraryInCache(fs::path path) + { + ASSERT(!temporary_cache_path_.empty(), + "The temporary cache path wasn't set!"); + + // Create the cache path and increment the library counter to use an unique name for each library + auto cache_path = temporary_cache_path_; + cache_path /= Trinity::StringFormat("%s.%u%s", + path.stem().generic_string().c_str(), + _unique_library_name_counter++, + path.extension().generic_string().c_str()); + + return cache_path; + } + /// Updates the current state of the given source path void UpdateSourceChangeRequest(std::string const& module_name, fs::path const& path, @@ -778,31 +837,37 @@ private: ASSERT(_running_script_module_names.find(path) == _running_script_module_names.end(), "Can't load a module which is running already!"); - // Create the cache path and increment the library counter to use an unique name for each library - fs::path cache_path = fs::absolute(sScriptReloadMgr->GetLibraryDirectory()); - cache_path /= ".cache"; - cache_path /= Trinity::StringFormat("%s.%u%s", - path.stem().generic_string().c_str(), - _unique_library_name_counter++, - path.extension().generic_string().c_str()); + Optional cache_path; + + #ifdef HOTSWAP_PLATFORM_REQUIRES_CACHING + + // Copy the shared library into a cache on platforms which lock files on use (windows). + cache_path = GenerateUniquePathForLibraryInCache(path); - boost::system::error_code code; - fs::copy_file(path, cache_path, fs::copy_option::fail_if_exists, code); - if (code) { - TC_LOG_FATAL("scripts.hotswap", ">> Failed to create cache entry for module " - "\"%s\" at \"%s\" with reason (\"%s\")!", - path.filename().generic_string().c_str(), cache_path.generic_string().c_str(), - code.message().c_str()); + boost::system::error_code code; + fs::copy_file(path, *cache_path, fs::copy_option::fail_if_exists, code); + if (code) + { + TC_LOG_FATAL("scripts.hotswap", ">> Failed to create cache entry for module " + "\"%s\" at \"%s\" with reason (\"%s\")!", + path.filename().generic_string().c_str(), cache_path->generic_string().c_str(), + code.message().c_str()); + + // Find a better solution for this but it's much better + // to start the core without scripts + std::this_thread::sleep_for(std::chrono::seconds(5)); + ABORT(); + return; + } - // Find a better solution for this but it's much better - // to start the core without scripts - std::this_thread::sleep_for(std::chrono::seconds(5)); - ABORT(); - return; + TC_LOG_TRACE("scripts.hotswap", ">> Copied the shared library \"%s\" to \"%s\" for caching.", + path.filename().generic_string().c_str(), cache_path->generic_string().c_str()); } - auto module = ScriptModule::CreateFromPath(cache_path); + #endif // #ifdef HOTSWAP_PLATFORM_REQUIRES_CACHING + + auto module = ScriptModule::CreateFromPath(path, cache_path); if (!module) { TC_LOG_FATAL("scripts.hotswap", ">> Failed to load script module \"%s\"!", @@ -1348,6 +1413,9 @@ private: // Is true when the build job dispatcher should stop after // the current job has finished bool terminate_early; + + // The path to the tc_scripts temporary cache + fs::path temporary_cache_path_; }; /// Maps efsw actions to strings -- cgit v1.2.3