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
2016-04-17 21:31:04 +02:00
parent 3271f328de
commit 081720b5dd

View File

@@ -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 <windows.h>
#define HOTSWAP_PLATFORM_REQUIRES_CACHING
#else // Posix
#include <dlfcn.h>
// #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<fs::path> 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<fs::path> const cache_path_;
};
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&& 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
{
@@ -215,23 +230,41 @@ static bool GetFunctionFromSharedLibrary(HandleType handle, std::string const& n
}
// 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
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<fs::path> 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