From b8deacca014e97a4138135d2ed6d71620e316d07 Mon Sep 17 00:00:00 2001 From: brainiac Date: Sun, 28 Dec 2025 02:21:53 -0800 Subject: [PATCH] Move code in several net/event classes to cpp to hide platform details Move DNSLookupSync to dns.h/cpp Move platform-specific functionality of ksm to ksm.cpp --- common/CMakeLists.txt | 11 +- common/event/event_loop.cpp | 57 ++++++++ common/event/event_loop.h | 55 +++----- common/event/task_scheduler.cpp | 128 +++++++++++++++++ common/event/task_scheduler.h | 137 +++++-------------- common/event/timer.cpp | 90 ++++++++++++ common/event/timer.h | 78 +++-------- common/ip_util.cpp | 159 +++++----------------- common/ip_util.h | 5 +- common/memory/ksm.cpp | 157 +++++++++++++++++++++ common/memory/ksm.h | 107 +++++++++++++++ common/memory/ksm.hpp | 234 -------------------------------- common/net/dns.cpp | 131 ++++++++++++++++++ common/net/dns.h | 58 +------- world/cli/cli_test.cpp | 2 +- world/world_boot.cpp | 6 +- zone/map.cpp | 2 +- zone/raycast_mesh.cpp | 2 +- 18 files changed, 795 insertions(+), 624 deletions(-) create mode 100644 common/event/event_loop.cpp create mode 100644 common/event/task_scheduler.cpp create mode 100644 common/event/timer.cpp create mode 100644 common/memory/ksm.cpp create mode 100644 common/memory/ksm.h delete mode 100644 common/memory/ksm.hpp create mode 100644 common/net/dns.cpp diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index c7e9ea99f..3ea6ac4d2 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -37,6 +37,9 @@ set(common_sources eqemu_logsys.cpp eqtime.cpp event_sub.cpp + event/event_loop.cpp + event/task_scheduler.cpp + event/timer.cpp events/player_event_discord_formatter.cpp events/player_event_logs.cpp evolving_items.cpp @@ -55,7 +58,7 @@ set(common_sources json_config.cpp light_source.cpp md5.cpp - memory/ksm.hpp + memory/ksm.cpp memory_buffer.cpp memory_mapped_file.cpp misc.cpp @@ -66,6 +69,7 @@ set(common_sources net/console_server.cpp net/console_server_connection.cpp net/crc32.cpp + net/dns.cpp net/eqstream.cpp net/packet.cpp net/reliable_stream_connection.cpp @@ -613,8 +617,8 @@ set(common_headers item_instance.h json/json-forwards.h json/json.h - json/json_archive_single_line.h json_config.h + json/json_archive_single_line.h light_source.h linked_list.h loot.h @@ -622,8 +626,9 @@ set(common_headers md5.h memory_buffer.h memory_mapped_file.h - misc.h + memory/ksm.h misc_functions.h + misc.h mysql_request_result.h mysql_request_row.h mysql_stmt.h diff --git a/common/event/event_loop.cpp b/common/event/event_loop.cpp new file mode 100644 index 000000000..139c1657e --- /dev/null +++ b/common/event/event_loop.cpp @@ -0,0 +1,57 @@ +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "common/event/event_loop.h" +#include "uv.h" + +namespace EQ { + +EventLoop& EventLoop::Get() +{ + thread_local EventLoop inst; + return inst; +} + +EventLoop::EventLoop() + : m_loop(std::make_unique()) +{ + memset(m_loop.get(), 0, sizeof(uv_loop_t)); + uv_loop_init(m_loop.get()); +} + +EventLoop::~EventLoop() +{ + uv_loop_close(m_loop.get()); +} + +void EventLoop::Process() +{ + uv_run(m_loop.get(), UV_RUN_NOWAIT); +} + +void EventLoop::Run() +{ + uv_run(m_loop.get(), UV_RUN_DEFAULT); +} + +void EventLoop::Shutdown() +{ + uv_stop(m_loop.get()); +} + +} // namespace EQ diff --git a/common/event/event_loop.h b/common/event/event_loop.h index 3c56ab949..fed72c022 100644 --- a/common/event/event_loop.h +++ b/common/event/event_loop.h @@ -17,48 +17,31 @@ */ #pragma once -#include "common/platform/win/include_windows.h" // uv.h is going to include it so let's do it first. -#include "uv.h" // FIXME: hide this +#include -#include +typedef struct uv_loop_s uv_loop_t; -namespace EQ +namespace EQ { + +class EventLoop { - class EventLoop - { - public: - static EventLoop &Get() { - static thread_local EventLoop inst; - return inst; - } +public: + static EventLoop& Get(); - ~EventLoop() { - uv_loop_close(&m_loop); - } + ~EventLoop(); + EventLoop(const EventLoop&) = delete; + EventLoop& operator=(const EventLoop&) = delete; - void Process() { - uv_run(&m_loop, UV_RUN_NOWAIT); - } + void Process(); + void Run(); + void Shutdown(); - void Run() { - uv_run(&m_loop, UV_RUN_DEFAULT); - } + uv_loop_t* Handle() { return m_loop.get(); } - void Shutdown() { - uv_stop(&m_loop); - } +private: + EventLoop(); - uv_loop_t* Handle() { return &m_loop; } + std::unique_ptr m_loop; +}; - private: - EventLoop() { - memset(&m_loop, 0, sizeof(uv_loop_t)); - uv_loop_init(&m_loop); - } - - EventLoop(const EventLoop&); - EventLoop& operator=(const EventLoop&); - - uv_loop_t m_loop; - }; -} +} // namespace EQ diff --git a/common/event/task_scheduler.cpp b/common/event/task_scheduler.cpp new file mode 100644 index 000000000..7ee3894fd --- /dev/null +++ b/common/event/task_scheduler.cpp @@ -0,0 +1,128 @@ +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "task_scheduler.h" + +#include +#include +#include +#include +#include +#include + +namespace EQ::Event { + +static constexpr int DefaultThreadCount = 4; + +struct TaskScheduler::SchedulerData +{ + std::atomic_bool running{ false }; + std::vector threads; + std::mutex lock; + std::condition_variable cv; + std::queue> tasks; +}; + +TaskScheduler::TaskScheduler() + : m_data(std::make_unique()) +{ + Start(DefaultThreadCount); +} + +TaskScheduler::TaskScheduler(size_t threads) +{ + Start(threads); +} + +TaskScheduler::~TaskScheduler() +{ + Stop(); +} + +void TaskScheduler::Start(size_t threads) +{ + if (m_data->running.exchange(true)) + return; + + m_data->threads.reserve(threads); + + for (size_t i = 0; i < threads; ++i) + { + m_data->threads.emplace_back( + [this]{ ProcessWork(); }); + } +} + +void TaskScheduler::Stop() +{ + if (!m_data->running.exchange(false)) + return; + + m_data->cv.notify_all(); + + for (auto& t : m_data->threads) + { + t.join(); + } + + m_data->threads.clear(); +} + +void TaskScheduler::ProcessWork() +{ + for (;;) + { + std::function work; + + { + std::unique_lock lock(m_data->lock); + + m_data->cv.wait(lock, + [this] + { + return !m_data->running || m_data->tasks.empty(); + }); + + if (!m_data->running) + { + return; + } + + work = std::move(m_data->tasks.front()); + m_data->tasks.pop(); + } + + work(); + } +} + +void TaskScheduler::AddTask(std::function&& task) +{ + if (!m_data->running) + { + throw std::runtime_error("Enqueue on stopped scheduler."); + } + + { + std::scoped_lock lock(m_data->lock); + m_data->tasks.push(std::move(task)); + } + m_data->cv.notify_one(); +} + +} // namespace EQ::Event diff --git a/common/event/task_scheduler.h b/common/event/task_scheduler.h index e2e855682..c63e93bdf 100644 --- a/common/event/task_scheduler.h +++ b/common/event/task_scheduler.h @@ -17,116 +17,45 @@ */ #pragma once -#include #include #include -#include -#include -#include -#include +#include -namespace EQ +namespace EQ::Event { + +class TaskScheduler { - namespace Event +public: + TaskScheduler(); + TaskScheduler(size_t threads); + + ~TaskScheduler(); + + void Start(size_t threads); + void Stop(); + + template + auto Enqueue(Fn&& fn, Args&&... args) -> std::future::type> { - class TaskScheduler - { - public: - static const int DefaultThreadCount = 4; - - TaskScheduler() : _running(false) + using return_type = typename std::invoke_result::type; + + auto task = std::make_shared>( + [fn = std::forward(fn), ...args = std::forward(args)]() mutable { - Start(DefaultThreadCount); - } - - TaskScheduler(size_t threads) : _running(false) - { - Start(threads); + return fn(std::forward(args)...); } + ); - ~TaskScheduler() { - Stop(); - } - - void Start(size_t threads) { - if (true == _running) { - return; - } - - _running = true; - - for (size_t i = 0; i < threads; ++i) { - _threads.emplace_back(std::thread(std::bind(&TaskScheduler::ProcessWork, this))); - } - } - - void Stop() { - if (false == _running) { - return; - } - - { - std::unique_lock lock(_lock); - _running = false; - } - - _cv.notify_all(); - - for (auto &t : _threads) { - t.join(); - } - } - - template - auto Enqueue(Fn&& fn, Args&&... args) -> std::future::type> { - using return_type = typename std::invoke_result::type; - - auto task = std::make_shared>( - std::bind(std::forward(fn), std::forward(args)...) - ); - - std::future res = task->get_future(); - { - std::unique_lock lock(_lock); - - if (false == _running) { - throw std::runtime_error("Enqueue on stopped scheduler."); - } - - _tasks.emplace([task]() { (*task)(); }); - } - - _cv.notify_one(); - return res; - } - - private: - void ProcessWork() { - for (;;) { - std::function work; - - { - std::unique_lock lock(_lock); - _cv.wait(lock, [this] { return !_running || !_tasks.empty(); }); - - if (false == _running) { - return; - } - - work = std::move(_tasks.front()); - _tasks.pop(); - } - - work(); - } - - } - - bool _running = true; - std::vector _threads; - std::mutex _lock; - std::condition_variable _cv; - std::queue> _tasks; - }; + AddTask([task] { (*task)(); }); + return task->get_future(); } -} + +private: + void AddTask(std::function&& task); + void ProcessWork(); + + struct SchedulerData; + std::unique_ptr m_data; +}; + +} // namespace EQ::Event diff --git a/common/event/timer.cpp b/common/event/timer.cpp new file mode 100644 index 000000000..be541c452 --- /dev/null +++ b/common/event/timer.cpp @@ -0,0 +1,90 @@ +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "common/event/timer.h" +#include "uv.h" + +namespace EQ { + +Timer::Timer(callback_t cb) + : m_cb(std::move(cb)) +{ +} + +Timer::Timer(uint64_t duration_ms, bool repeats, callback_t cb) + : m_cb(std::move(cb)) +{ + Start(duration_ms, repeats); +} + +Timer::~Timer() +{ + Stop(); +} + +void Timer::Start(uint64_t duration_ms, bool repeats) +{ + if (!m_timer) + { + uv_loop_t* loop = EventLoop::Get().Handle(); + + m_timer = std::make_unique(); + memset(m_timer.get(), 0, sizeof(uv_timer_t)); + + uv_timer_init(loop, m_timer.get()); + m_timer->data = this; + + if (repeats) + { + uv_timer_start(m_timer.get(), [](uv_timer_t* handle) + { + Timer* t = static_cast(handle->data); + t->Execute(); + }, duration_ms, duration_ms); + } + else + { + uv_timer_start(m_timer.get(), + [](uv_timer_t* handle) + { + Timer* t = static_cast(handle->data); + t->Stop(); + t->Execute(); + }, duration_ms, 0); + } + } +} + +void Timer::Stop() +{ + if (m_timer) + { + uv_close(reinterpret_cast(m_timer.release()), + [](uv_handle_t* handle) + { + delete reinterpret_cast(handle); + }); + } +} + +void Timer::Execute() +{ + m_cb(this); +} + +} // namespace EQ diff --git a/common/event/timer.h b/common/event/timer.h index 2cea8072b..61780dfe3 100644 --- a/common/event/timer.h +++ b/common/event/timer.h @@ -19,69 +19,31 @@ #include "event_loop.h" -#include #include +#include + +typedef struct uv_timer_s uv_timer_t; namespace EQ { - class Timer - { - public: - Timer(std::function cb) - { - m_timer = nullptr; - m_cb = cb; - } - Timer(uint64_t duration_ms, bool repeats, std::function cb) - { - m_timer = nullptr; - m_cb = cb; - Start(duration_ms, repeats); - } +class Timer +{ +public: + using callback_t = std::function; + + Timer(callback_t cb); + Timer(uint64_t duration_ms, bool repeats, callback_t cb); - ~Timer() - { - Stop(); - } + ~Timer(); - void Start(uint64_t duration_ms, bool repeats) { - auto loop = EventLoop::Get().Handle(); - if (!m_timer) { - m_timer = new uv_timer_t; - memset(m_timer, 0, sizeof(uv_timer_t)); - uv_timer_init(loop, m_timer); - m_timer->data = this; + void Start(uint64_t duration_ms, bool repeats); + void Stop(); - if (repeats) { - uv_timer_start(m_timer, [](uv_timer_t *handle) { - Timer *t = (Timer*)handle->data; - t->Execute(); - }, duration_ms, duration_ms); - } - else { - uv_timer_start(m_timer, [](uv_timer_t *handle) { - Timer *t = (Timer*)handle->data; - t->Stop(); - t->Execute(); - }, duration_ms, 0); - } - } - } - - void Stop() { - if (m_timer) { - uv_close((uv_handle_t*)m_timer, [](uv_handle_t* handle) { - delete (uv_timer_t *)handle; - }); - m_timer = nullptr; - } - } - private: - void Execute() { - m_cb(this); - } +private: + void Execute(); - uv_timer_t *m_timer; - std::function m_cb; - }; -} + std::unique_ptr m_timer; + callback_t m_cb; +}; + +} // namespace EQ diff --git a/common/ip_util.cpp b/common/ip_util.cpp index c1af78263..0801fb370 100644 --- a/common/ip_util.cpp +++ b/common/ip_util.cpp @@ -25,14 +25,24 @@ #include "common/net/dns.h" #include "fmt/format.h" -#include -#include +#include +#include #include -/** - * @param ip - * @return - */ +#ifdef _WIN32 +#include +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#include +#include +#include +#include +#include +#endif + + uint32_t IpUtil::IPToUInt(const std::string &ip) { int a, b, c, d; @@ -49,12 +59,6 @@ uint32_t IpUtil::IPToUInt(const std::string &ip) return addr; } -/** - * @param ip - * @param network - * @param mask - * @return - */ bool IpUtil::IsIpInRange(const std::string &ip, const std::string &network, const std::string &mask) { uint32_t ip_addr = IpUtil::IPToUInt(ip); @@ -67,10 +71,6 @@ bool IpUtil::IsIpInRange(const std::string &ip, const std::string &network, cons return ip_addr >= net_lower && ip_addr <= net_upper; } -/** - * @param ip - * @return - */ bool IpUtil::IsIpInPrivateRfc1918(const std::string &ip) { return ( @@ -80,30 +80,13 @@ bool IpUtil::IsIpInPrivateRfc1918(const std::string &ip) ); } - -#ifdef _WIN32 -#include - #include - #pragma comment(lib, "Ws2_32.lib") -#else -#include -#include -#include -#include -#include -#endif - -#include -#include -#include - std::string IpUtil::GetLocalIPAddress() { #ifdef _WIN32 WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { - return ""; - } + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + return ""; + } #endif char my_ip_address[INET_ADDRSTRLEN]; @@ -114,10 +97,10 @@ std::string IpUtil::GetLocalIPAddress() // Create a UDP socket #ifdef _WIN32 sockfd = socket(AF_INET, SOCK_DGRAM, 0); - if (sockfd == INVALID_SOCKET) { - WSACleanup(); - return ""; - } + if (sockfd == INVALID_SOCKET) { + WSACleanup(); + return ""; + } #else sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { @@ -200,98 +183,24 @@ std::string IpUtil::GetPublicIPAddress() return {}; } -std::string IpUtil::DNSLookupSync(const std::string &addr, int port) -{ - auto task_runner = new EQ::Event::TaskScheduler(); - auto res = task_runner->Enqueue( - [&]() -> std::string { - bool running = true; - std::string ret; - - EQ::Net::DNSLookup( - addr, port, false, [&](const std::string &addr) { - ret = addr; - if (addr.empty()) { - ret = ""; - running = false; - } - - return ret; - } - ); - - std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - - auto &loop = EQ::EventLoop::Get(); - while (running) { - if (!ret.empty()) { - running = false; - } - - std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(end - begin).count() > 1500) { - LogInfo( - "Deadline exceeded [{}]", - 1500 - ); - running = false; - } - - loop.Process(); - } - - return ret; - } - ); - - std::string result = res.get(); - safe_delete(task_runner); - - return result; -} - bool IpUtil::IsIPAddress(const std::string &ip_address) { - struct sockaddr_in sa{}; - int result = inet_pton(AF_INET, ip_address.c_str(), &(sa.sin_addr)); + sockaddr_in sa{}; + int result = inet_pton(AF_INET, ip_address.c_str(), &(sa.sin_addr)); return result != 0; } -#include -#ifdef _WIN32 -#include - #pragma comment(lib, "ws2_32.lib") // Link against Winsock library -#else -#include -#include -#include -#include -#endif - -#include -#include -#ifdef _WIN32 -#include - #include // For inet_pton - #pragma comment(lib, "ws2_32.lib") // Link against Winsock library -#else -#include -#include -#include -#include // For inet_pton -#include -#endif - -bool IpUtil::IsPortInUse(const std::string& ip, int port) { +bool IpUtil::IsPortInUse(const std::string& ip, int port) +{ bool in_use = false; #ifdef _WIN32 WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { - std::cerr << "WSAStartup failed\n"; - return true; // Assume in use on failure - } + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + std::cerr << "WSAStartup failed\n"; + return true; // Assume in use on failure + } #endif int sock = socket(AF_INET, SOCK_STREAM, 0); @@ -319,20 +228,20 @@ bool IpUtil::IsPortInUse(const std::string& ip, int port) { std::cerr << "Invalid IP address format: " << ip << std::endl; #ifdef _WIN32 closesocket(sock); - WSACleanup(); + WSACleanup(); #else close(sock); #endif return true; // Assume in use on failure } - if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + if (bind(sock, (sockaddr*)&addr, sizeof(addr)) < 0) { in_use = true; // Bind failed, port is in use } #ifdef _WIN32 closesocket(sock); - WSACleanup(); + WSACleanup(); #else close(sock); #endif diff --git a/common/ip_util.h b/common/ip_util.h index 9d04263d4..f779bf875 100644 --- a/common/ip_util.h +++ b/common/ip_util.h @@ -29,10 +29,7 @@ public: static bool IsIpInPrivateRfc1918(const std::string &ip); static std::string GetLocalIPAddress(); static std::string GetPublicIPAddress(); - static std::string DNSLookupSync( - const std::string &addr, - int port - ); + static bool IsIPAddress(const std::string &ip_address); static bool IsPortInUse(const std::string& ip, int port); diff --git a/common/memory/ksm.cpp b/common/memory/ksm.cpp new file mode 100644 index 000000000..874ebedc9 --- /dev/null +++ b/common/memory/ksm.cpp @@ -0,0 +1,157 @@ +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "ksm.h" + + +#ifdef _WIN32 +#include +#else +#include // For madvise +#include // For sysconf, sbrk +#endif + + +#ifdef _WIN32 +// Windows-specific functionality + +void* PageAlignedAllocatorBase::allocateInternal(size_t size, size_t alignment) +{ + void* ptr = malloc(size); + + if (!ptr) + { + throw std::bad_alloc(); + } + + return ptr; +} + +size_t PageAlignedAllocatorBase::getPageSize() const +{ + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + return sysInfo.dwPageSize; // Page size in bytes +} + +#else + +// Linux-specific functionality + +void* PageAlignedAllocatorBase::allocateInternal(size_t size, size_t alignment) +{ + void* ptr = nullptr; + + if (posix_memalign(&ptr, alignment, size) != 0) + { + throw std::bad_alloc(); + } + + return ptr; +} + +size_t PageAlignedAllocatorBase::getPageSize() const +{ + return static_cast(sysconf(_SC_PAGESIZE)); +} + +void CheckPageAlignment(void* ptr) +{ + size_t page_size = sysconf(_SC_PAGESIZE); + + if (reinterpret_cast(ptr) % page_size == 0) + { + LogKSMDetail("Memory is page-aligned [{}]", ptr); + } + else + { + LogKSMDetail("Memory is NOT page-aligned [{}]", ptr); + } +} + +void* AllocatePageAligned(size_t size) +{ + size_t page_size = sysconf(_SC_PAGESIZE); + void* aligned_ptr = nullptr; + + if (posix_memalign(&aligned_ptr, page_size, size) != 0) + { + LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size); + } + + std::memset(aligned_ptr, 0, size); + return aligned_ptr; +} + +void MarkMemoryForKSM(void* start, size_t size) +{ + if (madvise(start, size, MADV_MERGEABLE) == 0) + { + LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size); + } + else + { + perror("madvise failed"); + } +} + +void AlignHeapToPageBoundary() +{ + size_t page_size = sysconf(_SC_PAGESIZE); + if (page_size == 0) + { + LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size); + return; + } + + void* current_break = sbrk(0); + if (current_break == (void*)-1) + { + LogKSM("Failed to retrieve the current program break"); + return; + } + + uintptr_t current_address = reinterpret_cast(current_break); + size_t misalignment = current_address % page_size; + + if (misalignment != 0) + { + size_t adjustment = page_size - misalignment; + if (sbrk(adjustment) == (void*)-1) + { + LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment); + return; + } + } + + LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0)); +} + +void* MarkHeapStart() +{ + void* current_pos = sbrk(0); + AlignHeapToPageBoundary(); + return current_pos; +} + +size_t MeasureHeapUsage(void* start) +{ + void* current_break = sbrk(0); + return static_cast(current_break) - static_cast(start); +} + +#endif diff --git a/common/memory/ksm.h b/common/memory/ksm.h new file mode 100644 index 000000000..7f03c9f87 --- /dev/null +++ b/common/memory/ksm.h @@ -0,0 +1,107 @@ +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#pragma once + +#include "common/eqemu_logsys.h" + +class PageAlignedAllocatorBase +{ +protected: + void* allocateInternal(size_t amount, size_t alignment); + size_t getPageSize() const; +}; + +// Page-aligned allocator for std::vector +template +class PageAlignedAllocator : public PageAlignedAllocatorBase +{ +public: + using value_type = T; + + PageAlignedAllocator() noexcept = default; + + template + PageAlignedAllocator(const PageAlignedAllocator&) noexcept {} + + T* allocate(std::size_t n) + { + size_t size = n * sizeof(T); + + return static_cast(allocateInternal(size, getPageSize())); + } + + void deallocate(T* p, std::size_t) noexcept + { + free(p); + } +}; + +template +bool operator==(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { + return true; +} + +template +bool operator!=(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { + return false; +} + +// Kernel Samepage Merging (KSM) functionality +namespace KSM { + +#ifdef _WIN32 + +// Windows-specific placeholder functions (no-op) +inline void CheckPageAlignment(void* ptr) +{ +} + +inline void* AllocatePageAligned(size_t size) +{ + return memset(malloc(size), 0, size); +} + +inline void MarkMemoryForKSM(void* start, size_t size) +{ +} + +inline void AlignHeapToPageBoundary() +{ +} + +inline void* MarkHeapStart() +{ + return nullptr; +} + +inline size_t MeasureHeapUsage(void* start) +{ + return 0; +} + +#else + +void CheckPageAlignment(void* ptr); +void* AllocatePageAligned(size_t size); +void MarkMemoryForKSM(void* start, size_t size); +void AlignHeapToPageBoundary(); +void* MarkHeapStart(); +size_t MeasureHeapUsage(void* start); + +#endif +} diff --git a/common/memory/ksm.hpp b/common/memory/ksm.hpp deleted file mode 100644 index 9d4b28273..000000000 --- a/common/memory/ksm.hpp +++ /dev/null @@ -1,234 +0,0 @@ -/* EQEmu: EQEmulator - - Copyright (C) 2001-2026 EQEmu Development Team - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ -#pragma once - -#include "common/eqemu_logsys.h" - -#include -#include -#include -#ifdef _WIN32 -#include // For _aligned_malloc, _aligned_free -#include -#else -#include // For madvise -#include // For sysconf, sbrk -#endif - - -// Page-aligned allocator for std::vector -template -class PageAlignedAllocator { -public: - using value_type = T; - - PageAlignedAllocator() noexcept = default; - template - PageAlignedAllocator(const PageAlignedAllocator&) noexcept {} - - T* allocate(std::size_t n) { - void* ptr = nullptr; - size_t size = n * sizeof(T); - -#ifdef _WIN32 - // Simply allocate memory without alignment - ptr = malloc(size); - if (!ptr) throw std::bad_alloc(); -#else - size_t alignment = getPageSize(); // Get the system's page size - if (posix_memalign(&ptr, alignment, size) != 0) { - throw std::bad_alloc(); - } -#endif - return static_cast(ptr); - } - - void deallocate(T* p, std::size_t) noexcept { - free(p); - } - -private: - size_t getPageSize() const - { -#ifdef _WIN32 - SYSTEM_INFO sysInfo; - GetSystemInfo(&sysInfo); - return sysInfo.dwPageSize; // Page size in bytes -#else - return static_cast(sysconf(_SC_PAGESIZE)); -#endif - }; -}; - -template -bool operator==(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { - return true; -} - -template -bool operator!=(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { - return false; -} - -// Kernel Samepage Merging (KSM) functionality -namespace KSM { - -#ifdef _WIN32 - // Windows-specific placeholder functions (no-op) - inline void CheckPageAlignment(void* ptr) { - } - - inline void* AllocatePageAligned(size_t size) { - return memset(malloc(size), 0, size); - } - - inline void MarkMemoryForKSM(void* start, size_t size) { - } - - inline void AlignHeapToPageBoundary() { - } - - inline void* MarkHeapStart() { - return nullptr; - } - - inline size_t MeasureHeapUsage(void* start) { - return 0; - } -#else - // Linux-specific functionality - inline void CheckPageAlignment(void* ptr) { - size_t page_size = sysconf(_SC_PAGESIZE); - if (reinterpret_cast(ptr) % page_size == 0) { - LogKSMDetail("Memory is page-aligned [{}]", ptr); - } else { - LogKSMDetail("Memory is NOT page-aligned [{}]", ptr); - } - } - - inline void* AllocatePageAligned(size_t size) { - size_t page_size = sysconf(_SC_PAGESIZE); - void* aligned_ptr = nullptr; - if (posix_memalign(&aligned_ptr, page_size, size) != 0) { - LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size); - } - std::memset(aligned_ptr, 0, size); - return aligned_ptr; - } - - inline void MarkMemoryForKSM(void* start, size_t size) { - if (madvise(start, size, MADV_MERGEABLE) == 0) { - LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size); - } else { - perror("madvise failed"); - } - } - - inline void AlignHeapToPageBoundary() { - size_t page_size = sysconf(_SC_PAGESIZE); - if (page_size == 0) { - LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size); - return; - } - - void* current_break = sbrk(0); - if (current_break == (void*)-1) { - LogKSM("Failed to retrieve the current program break"); - return; - } - - uintptr_t current_address = reinterpret_cast(current_break); - size_t misalignment = current_address % page_size; - - if (misalignment != 0) { - size_t adjustment = page_size - misalignment; - if (sbrk(adjustment) == (void*)-1) { - LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment); - return; - } - } - - LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0)); - } - - inline void* MarkHeapStart() { - void* current_pos = sbrk(0); - AlignHeapToPageBoundary(); - return current_pos; - } - - inline size_t MeasureHeapUsage(void* start) { - void* current_break = sbrk(0); - return static_cast(current_break) - static_cast(start); - } -#endif - - - inline size_t getPageSize() - { -#ifdef _WIN32 - SYSTEM_INFO sysInfo; - GetSystemInfo(&sysInfo); - return sysInfo.dwPageSize; // Page size in bytes -#else - return static_cast(sysconf(_SC_PAGESIZE)); // POSIX page size -#endif - }; - - template - inline void PageAlignVectorAligned(std::vector>& vec) { - if (vec.empty()) { - return; - } - - size_t page_size = getPageSize(); - void* start = vec.data(); - size_t size = vec.size() * sizeof(T); - - // Check if the memory is page-aligned - if (reinterpret_cast(start) % page_size != 0) { - // Allocate a new aligned vector - std::vector> aligned_vec(vec.get_allocator()); - aligned_vec.reserve(vec.capacity()); // Match capacity to avoid reallocation during copy - - // Copy elements from the original vector - aligned_vec.insert(aligned_vec.end(), vec.begin(), vec.end()); - - // Swap the aligned vector with the original vector - vec.swap(aligned_vec); - - // Clear the temporary aligned vector to free its memory - aligned_vec.clear(); - - // Verify the new alignment - start = vec.data(); - if (reinterpret_cast(start) % page_size != 0) { - throw std::runtime_error("Failed to align vector memory to page boundaries."); - } - - LogKSMDetail("Vector reallocated to ensure page alignment. start [{}] size [{}] bytes", start, size); - } else { - LogKSMDetail("Vector is already page-aligned. start [{}] size [{}] bytes", start, size); - } - -#ifndef _WIN32 - // Mark memory for KSM (only on non-Windows systems) - MarkMemoryForKSM(start, size); -#endif - } -} diff --git a/common/net/dns.cpp b/common/net/dns.cpp new file mode 100644 index 000000000..fa232586c --- /dev/null +++ b/common/net/dns.cpp @@ -0,0 +1,131 @@ +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "dns.h" + +#include "common/eqemu_logsys.h" +#include "common/event/event_loop.h" +#include "common/event/task_scheduler.h" + +#include "uv.h" + +namespace EQ::Net { + +struct DNSBaton +{ + dns_callback_t cb; + bool ipv6; +}; + +void DNSLookup(const std::string& addr, int port, bool ipv6, dns_callback_t cb) +{ + addrinfo hints = {}; + hints.ai_family = PF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + uv_loop_t* loop = EQ::EventLoop::Get().Handle(); + + uv_getaddrinfo_t* resolver = new uv_getaddrinfo_t(); + memset(resolver, 0, sizeof(uv_getaddrinfo_t)); + std::string port_str = std::to_string(port); + DNSBaton* baton = new DNSBaton(); + baton->cb = std::move(cb); + baton->ipv6 = ipv6; + resolver->data = baton; + + uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t* req, int status, addrinfo* result) + { + DNSBaton* baton = static_cast(req->data); + + dns_callback_t dns_callback = std::move(baton->cb); + bool ipv6 = baton->ipv6; + + delete baton; + delete req; + + if (status < 0) + { + dns_callback({}); + return; + } + + char addr[40] = {}; + + if (ipv6) + { + uv_ip6_name(reinterpret_cast(result->ai_addr), addr, 40); + } + else + { + uv_ip4_name(reinterpret_cast(result->ai_addr), addr, 40); + } + + uv_freeaddrinfo(result); + + dns_callback(addr); + }, addr.c_str(), port_str.c_str(), &hints); +} + +std::string DNSLookupSync(const std::string& addr, int port, bool ipv6 /* = false */) +{ + EQ::Event::TaskScheduler task_runner; + + auto res = task_runner.Enqueue( + [addr, port, ipv6]() -> std::string + { + bool running = true; + std::string ret; + + EQ::Net::DNSLookup( + addr, port, ipv6, [&](const std::string& addr) { + ret = addr; + running = !addr.empty(); + + return ret; + } + ); + + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + + auto& loop = EQ::EventLoop::Get(); + while (running) { + if (!ret.empty()) { + running = false; + } + + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(end - begin).count() > 1500) { + LogInfo( + "Deadline exceeded [{}]", + 1500 + ); + running = false; + } + + loop.Process(); + } + + return ret; + }); + + return res.get(); +} + + +} // namespace EQ::Net diff --git a/common/net/dns.h b/common/net/dns.h index 2b799ef4f..04f97a4e9 100644 --- a/common/net/dns.h +++ b/common/net/dns.h @@ -17,63 +17,15 @@ */ #pragma once -#include "common/event/event_loop.h" - #include #include -namespace EQ -{ - namespace Net - { - static void DNSLookup(const std::string &addr, int port, bool ipv6, std::function cb) { - struct DNSBaton - { - std::function cb; - bool ipv6; - }; +namespace EQ::Net { - addrinfo hints; - memset(&hints, 0, sizeof(addrinfo)); - hints.ai_family = PF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; +using dns_callback_t = std::function; - auto loop = EQ::EventLoop::Get().Handle(); - uv_getaddrinfo_t *resolver = new uv_getaddrinfo_t(); - memset(resolver, 0, sizeof(uv_getaddrinfo_t)); - auto port_str = std::to_string(port); - DNSBaton *baton = new DNSBaton(); - baton->cb = cb; - baton->ipv6 = ipv6; - resolver->data = baton; +void DNSLookup(const std::string& addr, int port, bool ipv6, dns_callback_t cb); - uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t* req, int status, addrinfo* res) { - DNSBaton *baton = (DNSBaton*)req->data; - if (status < 0) { - auto cb = baton->cb; - delete baton; - delete req; - cb(""); - return; - } +std::string DNSLookupSync(const std::string& addr, int port, bool ipv6 = false); - char addr[40] = { 0 }; - - if (baton->ipv6) { - uv_ip6_name((struct sockaddr_in6*)res->ai_addr, addr, 40); - } - else { - uv_ip4_name((struct sockaddr_in*)res->ai_addr, addr, 40); - } - - auto cb = baton->cb; - delete baton; - delete req; - uv_freeaddrinfo(res); - - cb(addr); - }, addr.c_str(), port_str.c_str(), &hints); - } - } -} +} // namespace EQ::Net diff --git a/world/cli/cli_test.cpp b/world/cli/cli_test.cpp index 0353e4815..4878b4f39 100644 --- a/world/cli/cli_test.cpp +++ b/world/cli/cli_test.cpp @@ -18,7 +18,7 @@ #include "world/world_server_cli.h" #include "common/events/player_events.h" -#include "common/memory/ksm.hpp" +#include "common/memory/ksm.h" #include "cereal/archives/json.hpp" #include "cereal/types/vector.hpp" diff --git a/world/world_boot.cpp b/world/world_boot.cpp index 3a07a6632..97f4a172b 100644 --- a/world/world_boot.cpp +++ b/world/world_boot.cpp @@ -24,8 +24,7 @@ #include "common/http/httplib.h" #include "common/http/uri.h" #include "common/ip_util.h" -#include "common/net/console_server.h" -#include "common/net/servertalk_server.h" +#include "common/net/dns.h" #include "common/path_manager.h" #include "common/repositories/character_expedition_lockouts_repository.h" #include "common/repositories/character_task_timers_repository.h" @@ -33,7 +32,6 @@ #include "common/rulesys.h" #include "common/strings.h" #include "common/zone_store.h" -#include "common/zone_store.h" #include "world/adventure_manager.h" #include "world/dynamic_zone_manager.h" #include "world/login_server_list.h" @@ -456,7 +454,7 @@ void WorldBoot::CheckForPossibleConfigurationIssues() std::string config_address = c->WorldAddress; if (!IpUtil::IsIPAddress(config_address)) { - config_address = IpUtil::DNSLookupSync(c->WorldAddress, 9000); + config_address = EQ::Net::DNSLookupSync(c->WorldAddress, 9000); LogInfo( "World config address using DNS [{}] resolves to [{}]", c->WorldAddress, diff --git a/zone/map.cpp b/zone/map.cpp index 65d7f30d7..0fba4d021 100644 --- a/zone/map.cpp +++ b/zone/map.cpp @@ -17,7 +17,7 @@ */ #include "common/compression.h" #include "common/file.h" -#include "common/memory/ksm.hpp" +#include "common/memory/ksm.h" #include "common/misc_functions.h" #include "zone/client.h" #include "zone/map.h" diff --git a/zone/raycast_mesh.cpp b/zone/raycast_mesh.cpp index 64e10a523..a6c92ed1b 100644 --- a/zone/raycast_mesh.cpp +++ b/zone/raycast_mesh.cpp @@ -18,7 +18,7 @@ #include "raycast_mesh.h" #include "common/eqemu_logsys.h" -#include "common/memory/ksm.hpp" +#include "common/memory/ksm.h" #include #include