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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 243 additions and 10 deletions

View file

@ -86,6 +86,6 @@ fkYAML (A C++ header-only YAML library)
https://github.com/fktn-k/fkYAML
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.)
https://github.com/walkline/ToCloud9/tree/master/game-server/libsidecar
Version: master
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/releases/tag/libsidecar-v1.0.0
Version: 1.0.0 (TC9_VERSION in deps/libsidecar/include/tc9_version.h)

View file

@ -37,30 +37,54 @@ else()
set(LIBSIDECAR_EXTENSION "so")
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)
set_target_properties(libsidecar
PROPERTIES
IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION}
${LIBSIDECAR_SHARED_FILE}
IMPORTED_IMPLIB
${CMAKE_CURRENT_SOURCE_DIR}/${LIBSIDECAR_IMPORT_LIB}
INTERFACE_INCLUDE_DIRECTORIES
${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()
set_target_properties(libsidecar
PROPERTIES
IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION}
${LIBSIDECAR_SHARED_FILE}
INTERFACE_INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR}/include
IMPORTED_NO_SONAME
true)
set_target_properties(libsidecar PROPERTIES
IMPORTED_LOCATION_NOCONFIG
"${CMAKE_INSTALL_RPATH}/libsidecar.${LIBSIDECAR_EXTENSION}"
IMPORTED_SONAME_NOCONFIG
"libsidecar.${LIBSIDECAR_EXTENSION}")
# Source-tree dir for BUILD_RPATH only (dev runs from the build tree).
# Do not put this path on INSTALL_RPATH — that would bake the build
# machine's source tree into installed binaries.
# Do not use INTERFACE_LINK_OPTIONS -rpath: game, scripts, and
# 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()

View file

@ -19,6 +19,9 @@
#endif
#endif
/* Compile-time version macros (must match the libsidecar you link) */
#include "tc9_version.h"
/* Include all API headers */
#include "battleground-api.h"
#include "events-group.h"
@ -33,6 +36,13 @@
extern "C" {
#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 */
TC9_API void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize);
TC9_API void TC9GracefulShutdown();

20
deps/libsidecar/include/tc9_version.h vendored Normal file
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 */

View file

@ -7,6 +7,32 @@ void panicWithTC9Unavailable(const char* message) {
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.
//
extern void TC9SetBattlegroundStartHandler(BattlegroundStartHandler h) { panicWithTC9Unavailable("TC9SetBattlegroundStartHandler"); }

View file

@ -4,6 +4,9 @@
#include <stdint.h>
#include <stdbool.h>
/* Compile-time version macros (vendored with real headers under include/) */
#include "tc9_version.h"
/* Include all API headers */
#include "battleground-api.h"
#include "events-group.h"
@ -18,6 +21,11 @@
extern "C" {
#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 */
void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize);
void TC9GracefulShutdown();

21
deps/libsidecar/stub/tc9_version.h vendored Normal file
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 */

View file

@ -123,6 +123,16 @@ endif()
if ( USE_REAL_LIBSIDECAR )
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()
message("* Use stub for libsidecar : Yes")
endif()

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}")
# 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
if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES)
add_dependencies(${APP_PROJECT_NAME} ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES})

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);
});
// 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();
std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); });

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)
{
_clusterModeEnabled = sConfigMgr->GetOption<bool>("Cluster.Enabled", false);

View file

@ -34,6 +34,11 @@ private:
public:
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 Deinit();