feat(Deps/libsidecar): Libsidecar (clustering) ABI versions check and portable shared-library loading (#26890)

This commit is contained in:
Anton Popovichenko
2026-08-22 16:52:46 +02:00
committed by GitHub
parent c1793976c8
commit 23baad639b
12 changed files with 243 additions and 10 deletions
+3 -3
View File
@@ -86,6 +86,6 @@ fkYAML (A C++ header-only YAML library)
https://github.com/fktn-k/fkYAML https://github.com/fktn-k/fkYAML
Version: 721edb3e1a817e527fd9e1e18a3bea300822522e Version: 721edb3e1a817e527fd9e1e18a3bea300822522e
libsidecar (enables interaction with other components when the worldserver is running in cluster mode. It requires building a shared library first and putting it in the corresponding folder.) libsidecar (enables interaction with other components when the worldserver is running in cluster mode. Place the shared library from the release assets in deps/libsidecar for builds. UNIX: BUILD_RPATH finds deps/libsidecar; install copies to ${prefix}/lib; runtime also searches next to worldserver and ../lib. Windows: no rpath - build copies the DLL next to worldserver; install places it next to worldserver. Headers under deps/libsidecar/include - including tc9_version.h - must come from the same libsidecar build/release as the linked binary. worldserver checks ABI early when Cluster.Enabled.)
https://github.com/walkline/ToCloud9/tree/master/game-server/libsidecar https://github.com/walkline/ToCloud9/releases/tag/libsidecar-v1.0.0
Version: master Version: 1.0.0 (TC9_VERSION in deps/libsidecar/include/tc9_version.h)
+31 -7
View File
@@ -37,30 +37,54 @@ else()
set(LIBSIDECAR_EXTENSION "so") set(LIBSIDECAR_EXTENSION "so")
endif() endif()
# Visible to worldserver CMake (rpath on UNIX; POST_BUILD copy on Windows).
set(LIBSIDECAR_SHARED_FILE
"${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION}"
CACHE INTERNAL "Path to the real libsidecar shared library file")
if (NOT EXISTS "${LIBSIDECAR_SHARED_FILE}")
message(WARNING
"USE_REAL_LIBSIDECAR is ON but ${LIBSIDECAR_SHARED_FILE} was not found. "
"Place the matching shared library (and headers) from the same libsidecar "
"build/release before linking or installing.")
endif()
if(WIN32) if(WIN32)
set_target_properties(libsidecar set_target_properties(libsidecar
PROPERTIES PROPERTIES
IMPORTED_LOCATION IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION} ${LIBSIDECAR_SHARED_FILE}
IMPORTED_IMPLIB IMPORTED_IMPLIB
${CMAKE_CURRENT_SOURCE_DIR}/${LIBSIDECAR_IMPORT_LIB} ${CMAKE_CURRENT_SOURCE_DIR}/${LIBSIDECAR_IMPORT_LIB}
INTERFACE_INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR}/include) ${CMAKE_CURRENT_SOURCE_DIR}/include)
# worldserver installs to CMAKE_INSTALL_PREFIX root; DLL must sit next to it.
# Build-tree runs: apps/CMakeLists.txt copies this DLL next to worldserver
# (Windows has no rpath; the loader only searches the exe directory / PATH).
install(FILES "${LIBSIDECAR_SHARED_FILE}"
DESTINATION "${CMAKE_INSTALL_PREFIX}")
else() else()
set_target_properties(libsidecar set_target_properties(libsidecar
PROPERTIES PROPERTIES
IMPORTED_LOCATION IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION} ${LIBSIDECAR_SHARED_FILE}
INTERFACE_INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
IMPORTED_NO_SONAME IMPORTED_NO_SONAME
true) true)
set_target_properties(libsidecar PROPERTIES # Source-tree dir for BUILD_RPATH only (dev runs from the build tree).
IMPORTED_LOCATION_NOCONFIG # Do not put this path on INSTALL_RPATH — that would bake the build
"${CMAKE_INSTALL_RPATH}/libsidecar.${LIBSIDECAR_EXTENSION}" # machine's source tree into installed binaries.
IMPORTED_SONAME_NOCONFIG # Do not use INTERFACE_LINK_OPTIONS -rpath: game, scripts, and
"libsidecar.${LIBSIDECAR_EXTENSION}") # worldserver all link libsidecar and would get duplicate -rpath warnings.
set(LIBSIDECAR_RUNTIME_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL
"Directory containing the real libsidecar shared library (build tree)")
# Install next to the rest of the prefix libraries (worldserver → bin/,
# so runtime rpath uses $ORIGIN/../lib and CMAKE_INSTALL_RPATH = prefix/lib).
install(FILES "${LIBSIDECAR_SHARED_FILE}" DESTINATION lib)
endif() endif()
endif() endif()
+10
View File
@@ -19,6 +19,9 @@
#endif #endif
#endif #endif
/* Compile-time version macros (must match the libsidecar you link) */
#include "tc9_version.h"
/* Include all API headers */ /* Include all API headers */
#include "battleground-api.h" #include "battleground-api.h"
#include "events-group.h" #include "events-group.h"
@@ -33,6 +36,13 @@
extern "C" { extern "C" {
#endif #endif
/* ABI / package version of the *linked* library (not the caller's headers).
* Pass TC9_VERSION_MAJOR / TC9_VERSION_MINOR from the headers you compiled
* against into TC9CheckAbiCompatible. Returns 0 if compatible. */
TC9_API void TC9GetVersion(int* major, int* minor, int* patch);
TC9_API const char* TC9GetVersionString(void);
TC9_API int TC9CheckAbiCompatible(int required_major, int required_minor);
/* Main library functions */ /* Main library functions */
TC9_API void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize); TC9_API void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize);
TC9_API void TC9GracefulShutdown(); TC9_API void TC9GracefulShutdown();
+20
View File
@@ -0,0 +1,20 @@
#ifndef TC9_VERSION_H
#define TC9_VERSION_H
/* Vendored snapshot of libsidecar-cpp generated version header.
* Keep in sync with the libsidecar binary in this deps folder.
* Upstream source of truth: project(libsidecar VERSION ...) in
* game-server/libsidecar-cpp/CMakeLists.txt (generates this file).
*/
#define TC9_VERSION_MAJOR 1
#define TC9_VERSION_MINOR 0
#define TC9_VERSION_PATCH 0
#define TC9_VERSION_STRING "1.0.0"
/* major*10000 + minor*100 + patch */
#define TC9_VERSION_NUMBER \
(TC9_VERSION_MAJOR * 10000 + TC9_VERSION_MINOR * 100 + TC9_VERSION_PATCH)
#endif /* TC9_VERSION_H */
+26
View File
@@ -7,6 +7,32 @@ void panicWithTC9Unavailable(const char* message) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// Version APIs work without a real libsidecar so callers can always query
// the header version this tree was built against.
void TC9GetVersion(int* major, int* minor, int* patch)
{
if (major)
*major = TC9_VERSION_MAJOR;
if (minor)
*minor = TC9_VERSION_MINOR;
if (patch)
*patch = TC9_VERSION_PATCH;
}
const char* TC9GetVersionString(void)
{
return TC9_VERSION_STRING;
}
int TC9CheckAbiCompatible(int required_major, int required_minor)
{
if (TC9_VERSION_MAJOR != required_major)
return 1;
if (TC9_VERSION_MINOR < required_minor)
return 2;
return 0;
}
// TC9SetBattlegroundStartHandler sets handler for starting battleground. // TC9SetBattlegroundStartHandler sets handler for starting battleground.
// //
extern void TC9SetBattlegroundStartHandler(BattlegroundStartHandler h) { panicWithTC9Unavailable("TC9SetBattlegroundStartHandler"); } extern void TC9SetBattlegroundStartHandler(BattlegroundStartHandler h) { panicWithTC9Unavailable("TC9SetBattlegroundStartHandler"); }
+8
View File
@@ -4,6 +4,9 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
/* Compile-time version macros (vendored with real headers under include/) */
#include "tc9_version.h"
/* Include all API headers */ /* Include all API headers */
#include "battleground-api.h" #include "battleground-api.h"
#include "events-group.h" #include "events-group.h"
@@ -18,6 +21,11 @@
extern "C" { extern "C" {
#endif #endif
/* Version APIs (implemented by stub without panic; real lib reports its build) */
void TC9GetVersion(int* major, int* minor, int* patch);
const char* TC9GetVersionString(void);
int TC9CheckAbiCompatible(int required_major, int required_minor);
/* Main library functions */ /* Main library functions */
void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize); void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize);
void TC9GracefulShutdown(); void TC9GracefulShutdown();
+21
View File
@@ -0,0 +1,21 @@
#ifndef TC9_VERSION_H
#define TC9_VERSION_H
/* Vendored snapshot of libsidecar-cpp generated version header.
* Keep in sync with include/tc9_version.h and the linked libsidecar binary.
* Built only when USE_REAL_LIBSIDECAR is OFF (static stub, not the real .so/.dll).
*/
#define TC9_LIBSIDECAR_IS_STUB 1
#define TC9_VERSION_MAJOR 1
#define TC9_VERSION_MINOR 0
#define TC9_VERSION_PATCH 0
#define TC9_VERSION_STRING "1.0.0"
/* major*10000 + minor*100 + patch */
#define TC9_VERSION_NUMBER \
(TC9_VERSION_MAJOR * 10000 + TC9_VERSION_MINOR * 100 + TC9_VERSION_PATCH)
#endif /* TC9_VERSION_H */
+10
View File
@@ -123,6 +123,16 @@ endif()
if ( USE_REAL_LIBSIDECAR ) if ( USE_REAL_LIBSIDECAR )
message("* Use stub for libsidecar : No") message("* Use stub for libsidecar : No")
# Required ABI only applies when linking the real shared library (include/).
if (EXISTS "${CMAKE_SOURCE_DIR}/deps/libsidecar/include/tc9_version.h")
file(STRINGS "${CMAKE_SOURCE_DIR}/deps/libsidecar/include/tc9_version.h" _tc9_ver_line
REGEX "^#define TC9_VERSION_STRING ")
if (_tc9_ver_line)
string(REGEX REPLACE "^#define TC9_VERSION_STRING \"([^\"]+)\".*" "\\1" TC9_VENDORED_VERSION "${_tc9_ver_line}")
message("* libsidecar required ABI : ${TC9_VENDORED_VERSION}")
endif()
unset(_tc9_ver_line)
endif()
else() else()
message("* Use stub for libsidecar : Yes") message("* Use stub for libsidecar : Yes")
endif() endif()
+39
View File
@@ -148,6 +148,45 @@ foreach(APPLICATION_NAME ${APPLICATIONS_BUILD_LIST})
set_target_properties(${APP_PROJECT_NAME} PROPERTIES LINK_FLAGS "${${APP_PROJECT_NAME}_LINK_FLAGS}") set_target_properties(${APP_PROJECT_NAME} PROPERTIES LINK_FLAGS "${${APP_PROJECT_NAME}_LINK_FLAGS}")
# Real libsidecar runtime location (not via imported-target INTERFACE -rpath:
# game, scripts, and worldserver all link libsidecar and would get duplicates).
#
# UNIX/macOS — rpath search order:
# BUILD: deps/libsidecar → next to binary → ../lib
# INSTALL: target inherits CMAKE_INSTALL_RPATH (${prefix}/lib) first, then
# APPEND next to binary and ../lib (no source-tree path)
#
# Windows — no rpath; loader only searches the exe directory (then PATH).
# BUILD: POST_BUILD copy DLL next to worldserver
# INSTALL: install(FILES) places DLL next to worldserver (see deps/libsidecar)
if (USE_REAL_LIBSIDECAR)
if (WIN32 AND LIBSIDECAR_SHARED_FILE)
add_custom_command(TARGET ${APP_PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${LIBSIDECAR_SHARED_FILE}"
"$<TARGET_FILE_DIR:${APP_PROJECT_NAME}>"
COMMENT "Copy libsidecar.dll next to worldserver (Windows build tree)")
elseif (LIBSIDECAR_RUNTIME_DIR)
if (APPLE)
set_property(TARGET ${APP_PROJECT_NAME} APPEND PROPERTY BUILD_RPATH
"${LIBSIDECAR_RUNTIME_DIR}"
"@loader_path"
"@loader_path/../lib")
set_property(TARGET ${APP_PROJECT_NAME} APPEND PROPERTY INSTALL_RPATH
"@loader_path"
"@loader_path/../lib")
elseif (UNIX)
set_property(TARGET ${APP_PROJECT_NAME} APPEND PROPERTY BUILD_RPATH
"${LIBSIDECAR_RUNTIME_DIR}"
"$ORIGIN"
"$ORIGIN/../lib")
set_property(TARGET ${APP_PROJECT_NAME} APPEND PROPERTY INSTALL_RPATH
"$ORIGIN"
"$ORIGIN/../lib")
endif()
endif()
endif()
# Add all dynamic projects as dependency to the worldserver # Add all dynamic projects as dependency to the worldserver
if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES) if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES)
add_dependencies(${APP_PROJECT_NAME} ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES}) add_dependencies(${APP_PROJECT_NAME} ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES})
+5
View File
@@ -205,6 +205,11 @@ int main(int argc, char** argv)
LOG_INFO("server.worldserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100); LOG_INFO("server.worldserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
}); });
// Cluster.Enabled is known from config here. Fail before DB/network if the
// loaded libsidecar is the stub or does not match the headers we built with.
if (!sToCloud9Sidecar->CheckLibsidecarAbi())
return 1;
OpenSSLCrypto::threadsSetup(); OpenSSLCrypto::threadsSetup();
std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); }); std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); });
+65
View File
@@ -44,6 +44,71 @@ ToCloud9Sidecar::ToCloud9Sidecar() : _clusterModeEnabled(false), _isCrossrealm(f
{ {
} }
bool ToCloud9Sidecar::CheckLibsidecarAbi()
{
if (!sConfigMgr->GetOption<bool>("Cluster.Enabled", false))
return true;
int libMajor = 0;
int libMinor = 0;
int libPatch = 0;
TC9GetVersion(&libMajor, &libMinor, &libPatch);
char const* libVersionStr = TC9GetVersionString();
#if defined(TC9_LIBSIDECAR_IS_STUB)
char const* libKind = "stub";
#else
char const* libKind = "shared library";
#endif
#if defined(TC9_LIBSIDECAR_IS_STUB)
// Stub always "matches" its own macros; fail here instead of after the realm
// is online when TC9InitLib panics.
// SetSynchronous first: this runs before the io_context pool starts, so async
// LOG_* would never print on early return. Success path leaves async alone.
sLog->SetSynchronous();
LOG_INFO("server", "libsidecar ({}) runtime {}.{}.{} ({}) - headers {}.{}.{} ({})",
libKind,
libMajor, libMinor, libPatch,
libVersionStr ? libVersionStr : "?",
TC9_VERSION_MAJOR, TC9_VERSION_MINOR, TC9_VERSION_PATCH,
TC9_VERSION_STRING);
LOG_ERROR("server",
"Cluster.Enabled requires the real libsidecar shared library. "
"This worldserver was built with the stub (default). "
"Rebuild with -DUSE_REAL_LIBSIDECAR=ON and place matching headers + library "
"from the same libsidecar release under deps/libsidecar.");
return false;
#else
if (TC9CheckAbiCompatible(TC9_VERSION_MAJOR, TC9_VERSION_MINOR) != 0)
{
sLog->SetSynchronous();
LOG_INFO("server", "libsidecar ({}) runtime {}.{}.{} ({}) - headers {}.{}.{} ({})",
libKind,
libMajor, libMinor, libPatch,
libVersionStr ? libVersionStr : "?",
TC9_VERSION_MAJOR, TC9_VERSION_MINOR, TC9_VERSION_PATCH,
TC9_VERSION_STRING);
LOG_ERROR("server",
"libsidecar ABI mismatch: worldserver was built for {}.{}.{} (headers {}), "
"but loaded library is {}.{}.{} ({}). "
"Copy matching headers + library from the same libsidecar build/release.",
TC9_VERSION_MAJOR, TC9_VERSION_MINOR, TC9_VERSION_PATCH, TC9_VERSION_STRING,
libMajor, libMinor, libPatch,
libVersionStr ? libVersionStr : "?");
return false;
}
LOG_INFO("server", "libsidecar ({}) runtime {}.{}.{} ({}) - headers {}.{}.{} ({})",
libKind,
libMajor, libMinor, libPatch,
libVersionStr ? libVersionStr : "?",
TC9_VERSION_MAJOR, TC9_VERSION_MINOR, TC9_VERSION_PATCH,
TC9_VERSION_STRING);
return true;
#endif
}
void ToCloud9Sidecar::Init(uint16 port, int realmId) void ToCloud9Sidecar::Init(uint16 port, int realmId)
{ {
_clusterModeEnabled = sConfigMgr->GetOption<bool>("Cluster.Enabled", false); _clusterModeEnabled = sConfigMgr->GetOption<bool>("Cluster.Enabled", false);
+5
View File
@@ -34,6 +34,11 @@ private:
public: public:
static ToCloud9Sidecar* instance(); static ToCloud9Sidecar* instance();
/// When Cluster.Enabled, validate libsidecar (real library required; ABI match).
/// Call after config and logging are ready, before DB/network startup.
/// @return false if worldserver should exit with code 1.
bool CheckLibsidecarAbi();
void Init(uint16 port, int realmId); void Init(uint16 port, int realmId);
void Deinit(); void Deinit();