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).
This commit is contained in:
Naios authored and Aokromes committed 2016-08-10 08:57:12 +02:00
1 parent 7a827fe435
commit 2ec212fdd1
1 file changed
+119 -51
+119 -51
View File
@@ -56,6 +56,7 @@ ScriptReloadMgr* ScriptReloadMgr::instance()
#include "Config.h" #include "Config.h"
#include "BuiltInConfig.h" #include "BuiltInConfig.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "SHA1.h"
#include "StartProcess.h" #include "StartProcess.h"
#include "MPSCQueue.h" #include "MPSCQueue.h"
#include "GitRevision.h" #include "GitRevision.h"
@@ -64,8 +65,10 @@ namespace fs = boost::filesystem;
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#define HOTSWAP_PLATFORM_REQUIRES_CACHING
#else // Posix #else // Posix
#include <dlfcn.h> #include <dlfcn.h>
// #define HOTSWAP_PLATFORM_REQUIRES_CACHING
#endif #endif
// Promote the sScriptReloadMgr to a HotSwapScriptReloadMgr // Promote the sScriptReloadMgr to a HotSwapScriptReloadMgr
@@ -102,10 +105,10 @@ typedef void* HandleType;
class SharedLibraryUnloader class SharedLibraryUnloader
{ {
public: public:
SharedLibraryUnloader() explicit SharedLibraryUnloader(fs::path path)
: _path() { } : path_(std::move(path)) { }
explicit SharedLibraryUnloader(fs::path const& path) SharedLibraryUnloader(fs::path path, Optional<fs::path> cache_path)
: _path(path) { } : path_(std::move(path)), cache_path_(std::move(cache_path)) { }
void operator() (HandleType handle) const void operator() (HandleType handle) const
{ {
@@ -119,26 +122,37 @@ public:
if (!success) if (!success)
{ {
TC_LOG_ERROR("scripts.hotswap", "Failed to unload (syscall) the shared library \"%s\".", TC_LOG_ERROR("scripts.hotswap", "Failed to unload (syscall) the shared library \"%s\".",
_path.generic_string().c_str()); path_.generic_string().c_str());
return; return;
} }
boost::system::error_code error; /// When the shared library was cached delete it's shared version
if (fs::remove(_path, error)) if (cache_path_)
{ {
TC_LOG_TRACE("scripts.hotswap", "Lazy unloaded and deleted the shared library \"%s\".", boost::system::error_code error;
_path.generic_string().c_str()); 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 else
{ {
TC_LOG_ERROR("scripts.hotswap", "Failed to delete the shared library \"%s\" (%s).", TC_LOG_TRACE("scripts.hotswap", "Lazy unloaded the shared library \"%s\".",
_path.generic_string().c_str(), error.message().c_str()); path_.generic_string().c_str());
} }
} }
private: private:
fs::path _path; fs::path const path_;
Optional<fs::path> const cache_path_;
}; };
typedef std::unique_ptr<typename std::remove_pointer<HandleType>::type, SharedLibraryUnloader> HandleHolder; typedef std::unique_ptr<typename std::remove_pointer<HandleType>::type, SharedLibraryUnloader> HandleHolder;
@@ -165,7 +179,8 @@ public:
ScriptModule& operator= (ScriptModule const&) = delete; ScriptModule& operator= (ScriptModule const&) = delete;
ScriptModule& operator= (ScriptModule&& right) = delete; ScriptModule& operator= (ScriptModule&& right) = delete;
static Optional<std::shared_ptr<ScriptModule>> CreateFromPath(fs::path const& path); static Optional<std::shared_ptr<ScriptModule>>
CreateFromPath(fs::path const& path, Optional<fs::path> cache_path);
char const* GetScriptModuleRevisionHash() const override 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. // Load a shared library from the given path.
Optional<std::shared_ptr<ScriptModule>> ScriptModule::CreateFromPath(fs::path const& path) Optional<std::shared_ptr<ScriptModule>>
ScriptModule::CreateFromPath(fs::path const& path, Optional<fs::path> cache_path)
{ {
auto const load_path = [&] () -> fs::path {
if (cache_path)
return *cache_path;
else
return path;
}();
#ifdef _WIN32 #ifdef _WIN32
HandleType handle = LoadLibrary(path.generic_string().c_str()); HandleType handle = LoadLibrary(load_path.generic_string().c_str());
#else // Posix #else // Posix
HandleType handle = dlopen(path.c_str(), RTLD_LAZY); HandleType handle = dlopen(load_path.generic_string().c_str(), RTLD_LAZY);
#endif #endif
if (!handle) if (!handle)
{ {
TC_LOG_ERROR("scripts.hotswap", "Could not load the shared library \"%s\" for reading.", if (cache_path)
path.generic_string().c_str()); {
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; return boost::none;
} }
// Use RAII to release the library on failure. // Use RAII to release the library on failure.
HandleHolder holder(handle, SharedLibraryUnloader(path)); HandleHolder holder(handle, SharedLibraryUnloader(path, std::move(cache_path)));
GetScriptModuleRevisionHashType getScriptModuleRevisionHash; GetScriptModuleRevisionHashType getScriptModuleRevisionHash;
AddScriptsType addScripts; AddScriptsType addScripts;
@@ -366,7 +399,7 @@ static bool IsDebuggerBlockingRebuild()
/// ///
/// This class manages shared library loading/unloading through watching /// This class manages shared library loading/unloading through watching
/// the script module directory. Loaded shared libraries are mirrored /// 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 /// the shared library is still used which is useful for scripts
/// which can't be instantly replaced like spells or instances. /// which can't be instantly replaced like spells or instances.
/// Several modules which reference different versions can be kept loaded /// Several modules which reference different versions can be kept loaded
@@ -543,28 +576,28 @@ public:
} }
} }
// Get the cache directory path #ifdef HOTSWAP_PLATFORM_REQUIRES_CACHING
fs::path const cache_path = []
{ temporary_cache_path_ = CalculateTemporaryCachePath();
auto path = fs::absolute(sScriptReloadMgr->GetLibraryDirectory());
path /= ".cache";
return path;
}();
// We use the boost filesystem function versions which accept // We use the boost filesystem function versions which accept
// an error code to prevent it from throwing exceptions. // an error code to prevent it from throwing exceptions.
boost::system::error_code code; boost::system::error_code code;
if ((!fs::exists(cache_path, code) || (fs::remove_all(cache_path, code) > 0)) && if ((!fs::exists(temporary_cache_path_, code)
!fs::create_directory(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\".", TC_LOG_ERROR("scripts.hotswap", "Couldn't create the cache directory at \"%s\".",
cache_path.generic_string().c_str()); temporary_cache_path_.generic_string().c_str());
return; return;
} }
// Used to silent compiler warnings // Used to silent compiler warnings
(void)code; (void)code;
#endif // #ifdef HOTSWAP_PLATFORM_REQUIRES_CACHING
// Correct the CMake prefix when needed // Correct the CMake prefix when needed
if (sWorld->getBoolConfig(CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED)) if (sWorld->getBoolConfig(CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED))
DoCMakePrefixCorrectionIfNeeded(); DoCMakePrefixCorrectionIfNeeded();
@@ -696,6 +729,32 @@ private:
_fileWatcher.watch(); _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 /// Updates the current state of the given source path
void UpdateSourceChangeRequest(std::string const& module_name, void UpdateSourceChangeRequest(std::string const& module_name,
fs::path const& path, fs::path const& path,
@@ -776,31 +835,37 @@ private:
ASSERT(_running_script_module_names.find(path) == _running_script_module_names.end(), ASSERT(_running_script_module_names.find(path) == _running_script_module_names.end(),
"Can't load a module which is running already!"); "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 Optional<fs::path> cache_path;
fs::path cache_path = fs::absolute(sScriptReloadMgr->GetLibraryDirectory());
cache_path /= ".cache"; #ifdef HOTSWAP_PLATFORM_REQUIRES_CACHING
cache_path /= Trinity::StringFormat("%s.%u%s",
path.stem().generic_string().c_str(), // Copy the shared library into a cache on platforms which lock files on use (windows).
_unique_library_name_counter++, cache_path = GenerateUniquePathForLibraryInCache(path);
path.extension().generic_string().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 " boost::system::error_code code;
"\"%s\" at \"%s\" with reason (\"%s\")!", fs::copy_file(path, *cache_path, fs::copy_option::fail_if_exists, code);
path.filename().generic_string().c_str(), cache_path.generic_string().c_str(), if (code)
code.message().c_str()); {
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 // Find a better solution for this but it's much better
// to start the core without scripts // to start the core without scripts
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
ABORT(); ABORT();
return; 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) if (!module)
{ {
TC_LOG_FATAL("scripts.hotswap", ">> Failed to load script module \"%s\"!", TC_LOG_FATAL("scripts.hotswap", ">> Failed to load script module \"%s\"!",
@@ -1344,6 +1409,9 @@ private:
// Is true when the build job dispatcher should stop after // Is true when the build job dispatcher should stop after
// the current job has finished // the current job has finished
bool terminate_early; bool terminate_early;
// The path to the tc_scripts temporary cache
fs::path temporary_cache_path_;
}; };
/// Maps efsw actions to strings /// Maps efsw actions to strings