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

This commit is contained in:
tqcq 2024-04-14 02:42:50 +08:00
parent ebea797d67
commit 5b45b01611
8 changed files with 2347 additions and 3 deletions

View File

@ -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)

View File

@ -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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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);
}
}
}

View 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;
}