feat add hot_reloader
Some checks failed
linux-x64-gcc / linux-gcc (Debug) (push) Failing after 1m21s
linux-aarch64-cpu-gcc / linux-gcc-aarch64 (push) Failing after 1m35s
linux-arm-gcc / linux-gcc-armhf (push) Failing after 1m44s
linux-mips64-gcc / linux-gcc-mips64el (Debug) (push) Failing after 1m59s
linux-x64-gcc / linux-gcc (Release) (push) Failing after 2m1s
linux-mips64-gcc / linux-gcc-mips64el (Release) (push) Failing after 2m16s
Some checks failed
linux-x64-gcc / linux-gcc (Debug) (push) Failing after 1m21s
linux-aarch64-cpu-gcc / linux-gcc-aarch64 (push) Failing after 1m35s
linux-arm-gcc / linux-gcc-armhf (push) Failing after 1m44s
linux-mips64-gcc / linux-gcc-mips64el (Debug) (push) Failing after 1m59s
linux-x64-gcc / linux-gcc (Release) (push) Failing after 2m1s
linux-mips64-gcc / linux-gcc-mips64el (Release) (push) Failing after 2m16s
This commit is contained in:
parent
ebea797d67
commit
5b45b01611
@ -80,6 +80,7 @@ target_sources(
|
||||
src/sled/synchronization/sequence_checker.cc
|
||||
src/sled/synchronization/thread_local.cc
|
||||
src/sled/system/location.cc
|
||||
src/sled/system/hot_reloader.cc
|
||||
src/sled/system/pid.cc
|
||||
src/sled/system/thread.cc
|
||||
src/sled/system/thread_pool.cc
|
||||
@ -211,6 +212,21 @@ if(SLED_BUILD_TESTS)
|
||||
sled_add_test(
|
||||
NAME sled_cache_test SRCS src/sled/cache/lru_cache_test.cc
|
||||
src/sled/cache/fifo_cache_test.cc src/sled/cache/expire_cache_test.cc)
|
||||
|
||||
add_library(hot_reloader_test_dynamic SHARED
|
||||
src/sled/system/hot_reloader_test_dynamic.cc)
|
||||
target_link_libraries(hot_reloader_test_dynamic PRIVATE sled)
|
||||
message(
|
||||
STATUS "HOT_RELOADER_TEST_DYNAMIC_LOCATION ${hot_reloader_test_dynamic}")
|
||||
get_target_property(HOT_RELOADER_TEST_DYNAMIC_LOCATION
|
||||
hot_reloader_test_dynamic LINK_LIBRARIES)
|
||||
|
||||
sled_add_test(
|
||||
NAME sled_hot_reloader_test SRCS src/sled/system/hot_reloader_test.cc LIBS
|
||||
hot_reloader_test_dynamic)
|
||||
target_compile_definitions(
|
||||
sled_hot_reloader_test
|
||||
PRIVATE "TEST_BIN_PATH=\"${HOT_RELOADER_TEST_DYNAMIC_LOCATION}\"")
|
||||
endif(SLED_BUILD_TESTS)
|
||||
|
||||
if(SLED_BUILD_FUZZ)
|
||||
|
@ -7,6 +7,36 @@
|
||||
#else
|
||||
#define SLED_THREAD_ANNOTATION_ATTRIBUTE__(x)// no-op
|
||||
#endif
|
||||
//
|
||||
// Global compiler specific defines/customizations
|
||||
//
|
||||
#if defined(_MSC_VER)
|
||||
#if defined(__cplusplus)
|
||||
#define SLED_EXPORT extern "C" __declspec(dllexport)
|
||||
#define SLED_IMPORT extern "C" __declspec(dllimport)
|
||||
#else
|
||||
#define SLED_EXPORT __declspec(dllexport)
|
||||
#define SLED_IMPORT __declspec(dllimport)
|
||||
#endif
|
||||
#endif// defined(_MSC_VER)
|
||||
|
||||
#if defined(__GNUC__)// clang & gcc
|
||||
#if defined(__cplusplus)
|
||||
#define SLED_EXPORT extern "C" __attribute__((visibility("default")))
|
||||
#else
|
||||
#define SLED_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
#define SLED_IMPORT
|
||||
#endif// defined(__GNUC__)
|
||||
|
||||
#if defined(__MINGW32__)
|
||||
#undef SLED_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define SLED_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define SLED_EXPORT __declspec(dllexport)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define SLED_NODISCARD SLED_THREAD_ANNOTATION_ATTRIBUTE__(__warn_unused_result__)
|
||||
#define SLED_DEPRECATED SLED_THREAD_ANNOTATION_ATTRIBUTE__(deprecated)
|
||||
|
1954
src/sled/system/detail/cr.h
Normal file
1954
src/sled/system/detail/cr.h
Normal file
File diff suppressed because it is too large
Load Diff
136
src/sled/system/hot_reloader.cc
Normal file
136
src/sled/system/hot_reloader.cc
Normal file
@ -0,0 +1,136 @@
|
||||
#include "sled/system/hot_reloader.h"
|
||||
#include "sled/log/log.h"
|
||||
|
||||
#define CR_HOST CR_SAFEST
|
||||
#define CR_MAIN_FUNC "sled_hot_loader_main"
|
||||
#include "sled/system/detail/cr.h"
|
||||
|
||||
namespace sled {
|
||||
namespace detail {
|
||||
HotReloader::Failure
|
||||
TranslateFailure(cr_failure failure)
|
||||
{
|
||||
switch (failure) {
|
||||
case CR_NONE:
|
||||
return HotReloader::Failure::kOk;
|
||||
case CR_SEGFAULT:
|
||||
return HotReloader::Failure::kSegmentationFault;
|
||||
case CR_ILLEGAL:
|
||||
return HotReloader::Failure::kIllegal;
|
||||
case CR_ABORT:
|
||||
return HotReloader::Failure::kAbort;
|
||||
case CR_MISALIGN:
|
||||
return HotReloader::Failure::kMisalign;
|
||||
case CR_BOUNDS:
|
||||
return HotReloader::Failure::kBounds;
|
||||
case CR_STACKOVERFLOW:
|
||||
return HotReloader::Failure::kStackOverflow;
|
||||
case CR_STATE_INVALIDATED:
|
||||
return HotReloader::Failure::kStateInvalidated;
|
||||
case CR_BAD_IMAGE:
|
||||
return HotReloader::Failure::kBadImage;
|
||||
case CR_INITIAL_FAILURE:
|
||||
return HotReloader::Failure::kInitialFailure;
|
||||
case CR_OTHER:
|
||||
return HotReloader::Failure::kOther;
|
||||
default:
|
||||
return HotReloader::Failure::kOther;
|
||||
}
|
||||
}
|
||||
}// namespace detail
|
||||
|
||||
std::string
|
||||
DynamicLibraryName(sled::string_view base_name)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return sled::to_string(base_name) + ".dll";
|
||||
#elif defined(__APPLE__)
|
||||
return "lib" + sled::to_string(base_name) + ".dylib";
|
||||
#elif defined(__linux__)
|
||||
return "lib" + sled::to_string(base_name) + ".so";
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
class HotReloader::Impl {
|
||||
public:
|
||||
Impl(sled::string_view bin_path) : bin_path_(sled::to_string(bin_path)) {}
|
||||
|
||||
~Impl() { cr_plugin_close(ctx_); }
|
||||
|
||||
bool Initialize()
|
||||
{
|
||||
if (cr_plugin_open(ctx_, bin_path_.c_str())) { return true; }
|
||||
LOGV("HotReloader", "Failed to load plugin: {}", bin_path_);
|
||||
return false;
|
||||
}
|
||||
|
||||
sled::optional<Failure> Update(bool force_reload)
|
||||
{
|
||||
if (cr_plugin_update(ctx_, force_reload) == 0) { return sled::nullopt; }
|
||||
return detail::TranslateFailure(ctx_.failure);
|
||||
}
|
||||
|
||||
void set_userdata(void *data) { ctx_.userdata = data; }
|
||||
|
||||
sled::string_view name() const { return bin_path_; }
|
||||
|
||||
VersionType version() const { return ctx_.version; }
|
||||
|
||||
VersionType last_working_version() const { return ctx_.last_working_version; }
|
||||
|
||||
VersionType next_version() const { return ctx_.next_version; }
|
||||
|
||||
private:
|
||||
std::string bin_path_;
|
||||
cr_plugin ctx_;
|
||||
};
|
||||
|
||||
HotReloader::HotReloader(sled::string_view bin_path) : impl_(new Impl(bin_path)) {}
|
||||
|
||||
HotReloader::~HotReloader() {}
|
||||
|
||||
bool
|
||||
HotReloader::Initialize()
|
||||
{
|
||||
return impl_->Initialize();
|
||||
}
|
||||
|
||||
sled::optional<HotReloader::Failure>
|
||||
HotReloader::UpdateOrError(bool force_reload)
|
||||
{
|
||||
return impl_->Update(force_reload);
|
||||
}
|
||||
|
||||
void
|
||||
HotReloader::set_userdata(void *data)
|
||||
{
|
||||
impl_->set_userdata(data);
|
||||
}
|
||||
|
||||
sled::string_view
|
||||
HotReloader::name() const
|
||||
{
|
||||
return impl_->name();
|
||||
}
|
||||
|
||||
HotReloader::VersionType
|
||||
HotReloader::version() const
|
||||
{
|
||||
return impl_->version();
|
||||
}
|
||||
|
||||
HotReloader::VersionType
|
||||
HotReloader::last_working_version() const
|
||||
{
|
||||
return impl_->last_working_version();
|
||||
}
|
||||
|
||||
HotReloader::VersionType
|
||||
HotReloader::next_version() const
|
||||
{
|
||||
return impl_->next_version();
|
||||
}
|
||||
|
||||
}// namespace sled
|
94
src/sled/system/hot_reloader.h
Normal file
94
src/sled/system/hot_reloader.h
Normal file
@ -0,0 +1,94 @@
|
||||
#ifndef SLED_SYSTEM_HOT_LOADER_H
|
||||
#define SLED_SYSTEM_HOT_LOADER_H
|
||||
|
||||
#pragma once
|
||||
#include "sled/nonstd/string_view.h"
|
||||
#include "sled/optional.h"
|
||||
#include <memory>
|
||||
|
||||
// hot reload library should implement
|
||||
// sled_hot_loader_main function
|
||||
// if success return 0,
|
||||
// otherwise return negative value (reversed -1, -2)
|
||||
// int sled_hot_loader_main(void* ctx, int op) {
|
||||
// switch(op) {
|
||||
// case 0: on_load(...);
|
||||
// case 2: on_unload(...);
|
||||
// case 3: on_close(...);
|
||||
// }
|
||||
// if (failure) {
|
||||
// return -3;
|
||||
// }
|
||||
// return 0;
|
||||
// }
|
||||
namespace sled {
|
||||
// Windows return <base_name>.dll
|
||||
// linux return lib<base_name>.so
|
||||
// macos return lib<base_name>.dylib
|
||||
std::string DynamicLibraryName(sled::string_view base_name);
|
||||
|
||||
class HotReloader {
|
||||
public:
|
||||
using VersionType = unsigned int;
|
||||
|
||||
// cr_mode defines how much we validate global state transfer between
|
||||
// instances. The default is CR_UNSAFE, you can choose another mode by
|
||||
// defining CR_HOST, ie.: #define CR_HOST CR_SAFEST
|
||||
|
||||
enum class LoadMode {
|
||||
kSafest = 0, // validate address and size of the state section,
|
||||
// if anything changes the load will rollback
|
||||
kSafe = 1, // validate only the size of the state section,
|
||||
// if it changes the load will rollback
|
||||
kUnsafe = 2, // don't validate anything but that the size of section fits
|
||||
// may not be identical though
|
||||
kDisable = 3,// disable the auto state transfer
|
||||
};
|
||||
|
||||
enum class Operation {
|
||||
kLoad = 0,
|
||||
kStep = 1,
|
||||
kUnload = 2,
|
||||
kClose = 3,
|
||||
};
|
||||
|
||||
enum class Failure {
|
||||
kOk = 0,
|
||||
kSegmentationFault = 1,// SIGSEGV
|
||||
kIllegal = 2,// SIGILL
|
||||
kAbort = 3,// SIGBRT
|
||||
kMisalign = 4,// SIGBUS
|
||||
kBounds = 5,// ARRAY BOUNDS EXEEDED
|
||||
kStackOverflow = 6,
|
||||
kStateInvalidated = 7,
|
||||
kBadImage = 8,// invalid binary
|
||||
kInitialFailure = 9,
|
||||
kOther = 10,
|
||||
kUser = 0x100,
|
||||
};
|
||||
|
||||
HotReloader(sled::string_view bin_path);
|
||||
~HotReloader();
|
||||
|
||||
bool Initialize();
|
||||
/**
|
||||
* @brief 如果 Update失败,再次Update将会回滚
|
||||
* @force_reload: 如果当前库已经加载,是否reload当前库
|
||||
* @ status: 如果不为nullptr,返回当前加载状态
|
||||
**/
|
||||
sled::optional<Failure> UpdateOrError(bool force_reload = true);
|
||||
// bool Rollback();
|
||||
|
||||
void set_userdata(void *data);
|
||||
sled::string_view name() const;
|
||||
VersionType version() const;
|
||||
VersionType last_working_version() const;
|
||||
VersionType next_version() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
}// namespace sled
|
||||
|
||||
#endif// SLED_SYSTEM_HOT_LOADER_H
|
64
src/sled/system/hot_reloader_test.cc
Normal file
64
src/sled/system/hot_reloader_test.cc
Normal file
@ -0,0 +1,64 @@
|
||||
#include <sled/sled.h>
|
||||
#include <sled/system/hot_reloader.h>
|
||||
#ifndef TEST_BIN_PATH
|
||||
#error "must define TEST_BIN_PATH"
|
||||
#endif
|
||||
|
||||
TEST_SUITE("Hot Loader")
|
||||
{
|
||||
TEST_CASE("DynamicLibraryName")
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
CHECK_EQ(sled::DynamicLibraryName("test"), "test.dll");
|
||||
#elif defined(__APPLE__)
|
||||
CHECK_EQ(sled::DynamicLibraryName("test"), "libtest.dylib");
|
||||
#elif defined(__linux__)
|
||||
CHECK_EQ(sled::DynamicLibraryName("test"), "libtest.so");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("crash_test")
|
||||
{
|
||||
sled::HotReloader reloader(sled::DynamicLibraryName("hot_reloader_test_dynamic"));
|
||||
CHECK_MESSAGE(reloader.Initialize(), "Initialize failed, ", reloader.name());
|
||||
CHECK_EQ(reloader.version(), 0);
|
||||
CHECK_EQ(reloader.next_version(), 1);
|
||||
CHECK_EQ(reloader.last_working_version(), 0);
|
||||
|
||||
int op;
|
||||
reloader.set_userdata(&op);
|
||||
|
||||
SUBCASE("OnLoad Crash")
|
||||
{
|
||||
// OnLoad crash test
|
||||
op = 0;
|
||||
sled::optional<sled::HotReloader::Failure> error = reloader.UpdateOrError();
|
||||
REQUIRE(error);
|
||||
CHECK_EQ(*error, sled::HotReloader::Failure::kSegmentationFault);
|
||||
CHECK_EQ(op, -1);
|
||||
|
||||
CHECK_EQ(reloader.version(), 0);
|
||||
CHECK_EQ(reloader.next_version(), 2);
|
||||
CHECK_EQ(reloader.last_working_version(), 0);
|
||||
}
|
||||
SUBCASE("OnClose crash")
|
||||
{
|
||||
// OnClose crash test
|
||||
op = 3;
|
||||
sled::optional<sled::HotReloader::Failure> error = reloader.UpdateOrError();
|
||||
REQUIRE_FALSE(error);
|
||||
CHECK_EQ(op, -1);
|
||||
CHECK_EQ(reloader.version(), 1);
|
||||
CHECK_EQ(reloader.next_version(), 2);
|
||||
CHECK_EQ(reloader.last_working_version(), 0);
|
||||
}
|
||||
SUBCASE("OnClose crash")
|
||||
{
|
||||
// OnStep crash test
|
||||
op = 1;
|
||||
sled::optional<sled::HotReloader::Failure> error = reloader.UpdateOrError();
|
||||
REQUIRE_FALSE(error);
|
||||
CHECK_EQ(op, -1);
|
||||
}
|
||||
}
|
||||
}
|
50
src/sled/system/hot_reloader_test_dynamic.cc
Normal file
50
src/sled/system/hot_reloader_test_dynamic.cc
Normal file
@ -0,0 +1,50 @@
|
||||
#include <sled/sled.h>
|
||||
|
||||
void
|
||||
crash()
|
||||
{
|
||||
int *addr = nullptr;
|
||||
(void) ++*addr;
|
||||
}
|
||||
|
||||
SLED_EXPORT int
|
||||
sled_hot_loader_main(void *ctx, int op)
|
||||
{
|
||||
struct ctx_impl {
|
||||
void *p;
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
void *userdata = reinterpret_cast<ctx_impl *>(ctx)->userdata;
|
||||
int *value_ptr = reinterpret_cast<int *>(userdata);
|
||||
int crash_type = *value_ptr;
|
||||
*value_ptr = -1;
|
||||
LOGD("plugin ", "sled_hot_loader_main: op={}, crash={}", op, crash_type);
|
||||
|
||||
switch (op) {
|
||||
// On Load
|
||||
case 0: {
|
||||
if (crash_type == 0) { crash(); }
|
||||
break;
|
||||
}
|
||||
// On Step
|
||||
case 1: {
|
||||
if (crash_type == 1) { crash(); }
|
||||
break;
|
||||
}
|
||||
// OnUnload
|
||||
case 2: {
|
||||
if (crash_type == 2) { crash(); }
|
||||
|
||||
break;
|
||||
}
|
||||
// OnClose
|
||||
case 3: {
|
||||
if (crash_type == 3) { crash(); }
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user