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
This commit is contained in:
brainiac 2025-12-28 02:21:53 -08:00
parent 82f9f7a02a
commit b8deacca01
18 changed files with 795 additions and 624 deletions

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<uv_loop_t>())
{
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

View File

@ -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 <memory>
#include <cstring>
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<uv_loop_t> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "task_scheduler.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
namespace EQ::Event {
static constexpr int DefaultThreadCount = 4;
struct TaskScheduler::SchedulerData
{
std::atomic_bool running{ false };
std::vector<std::thread> threads;
std::mutex lock;
std::condition_variable cv;
std::queue<std::function<void()>> tasks;
};
TaskScheduler::TaskScheduler()
: m_data(std::make_unique<SchedulerData>())
{
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<void()> 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<void()>&& 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

View File

@ -17,116 +17,45 @@
*/
#pragma once
#include <condition_variable>
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
#include <memory>
namespace EQ
namespace EQ::Event {
class TaskScheduler
{
namespace Event
public:
TaskScheduler();
TaskScheduler(size_t threads);
~TaskScheduler();
void Start(size_t threads);
void Stop();
template <typename Fn, typename... Args>
auto Enqueue(Fn&& fn, Args&&... args) -> std::future<typename std::invoke_result<Fn, Args...>::type>
{
class TaskScheduler
{
public:
static const int DefaultThreadCount = 4;
TaskScheduler() : _running(false)
using return_type = typename std::invoke_result<Fn, Args...>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
[fn = std::forward<Fn>(fn), ...args = std::forward<Args>(args)]() mutable
{
Start(DefaultThreadCount);
}
TaskScheduler(size_t threads) : _running(false)
{
Start(threads);
return fn(std::forward<Args>(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<std::mutex> lock(_lock);
_running = false;
}
_cv.notify_all();
for (auto &t : _threads) {
t.join();
}
}
template<typename Fn, typename... Args>
auto Enqueue(Fn&& fn, Args&&... args) -> std::future<typename std::invoke_result<Fn, Args...>::type> {
using return_type = typename std::invoke_result<Fn, Args...>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> 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<void()> work;
{
std::unique_lock<std::mutex> 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<std::thread> _threads;
std::mutex _lock;
std::condition_variable _cv;
std::queue<std::function<void()>> _tasks;
};
AddTask([task] { (*task)(); });
return task->get_future();
}
}
private:
void AddTask(std::function<void()>&& task);
void ProcessWork();
struct SchedulerData;
std::unique_ptr<SchedulerData> m_data;
};
} // namespace EQ::Event

90
common/event/timer.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<uv_timer_t>();
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<Timer*>(handle->data);
t->Execute();
}, duration_ms, duration_ms);
}
else
{
uv_timer_start(m_timer.get(),
[](uv_timer_t* handle)
{
Timer* t = static_cast<Timer*>(handle->data);
t->Stop();
t->Execute();
}, duration_ms, 0);
}
}
}
void Timer::Stop()
{
if (m_timer)
{
uv_close(reinterpret_cast<uv_handle_t*>(m_timer.release()),
[](uv_handle_t* handle)
{
delete reinterpret_cast<uv_timer_t*>(handle);
});
}
}
void Timer::Execute()
{
m_cb(this);
}
} // namespace EQ

View File

@ -19,69 +19,31 @@
#include "event_loop.h"
#include <cstring>
#include <functional>
#include <memory>
typedef struct uv_timer_s uv_timer_t;
namespace EQ {
class Timer
{
public:
Timer(std::function<void(Timer *)> cb)
{
m_timer = nullptr;
m_cb = cb;
}
Timer(uint64_t duration_ms, bool repeats, std::function<void(Timer *)> cb)
{
m_timer = nullptr;
m_cb = cb;
Start(duration_ms, repeats);
}
class Timer
{
public:
using callback_t = std::function<void(Timer*)>;
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<void(Timer*)> m_cb;
};
}
std::unique_ptr<uv_timer_t> m_timer;
callback_t m_cb;
};
} // namespace EQ

View File

@ -25,14 +25,24 @@
#include "common/net/dns.h"
#include "fmt/format.h"
#include <cstring>
#include <csignal>
#include <iostream>
#include <string>
#include <vector>
/**
* @param ip
* @return
*/
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#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 <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#endif
#include <iostream>
#include <string>
#include <cstring>
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<std::chrono::milliseconds>(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 <iostream>
#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") // Link against Winsock library
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
#include <iostream>
#include <string>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h> // For inet_pton
#pragma comment(lib, "ws2_32.lib") // Link against Winsock library
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // For inet_pton
#include <unistd.h>
#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

View File

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

157
common/memory/ksm.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "ksm.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h> // For madvise
#include <unistd.h> // 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<size_t>(sysconf(_SC_PAGESIZE));
}
void CheckPageAlignment(void* ptr)
{
size_t page_size = sysconf(_SC_PAGESIZE);
if (reinterpret_cast<uintptr_t>(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<uintptr_t>(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<char*>(current_break) - static_cast<char*>(start);
}
#endif

107
common/memory/ksm.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <typename T>
class PageAlignedAllocator : public PageAlignedAllocatorBase
{
public:
using value_type = T;
PageAlignedAllocator() noexcept = default;
template <typename U>
PageAlignedAllocator(const PageAlignedAllocator<U>&) noexcept {}
T* allocate(std::size_t n)
{
size_t size = n * sizeof(T);
return static_cast<T*>(allocateInternal(size, getPageSize()));
}
void deallocate(T* p, std::size_t) noexcept
{
free(p);
}
};
template <typename T, typename U>
bool operator==(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) noexcept {
return true;
}
template <typename T, typename U>
bool operator!=(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) 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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/eqemu_logsys.h"
#include <iostream>
#include <vector>
#include <cstring>
#ifdef _WIN32
#include <malloc.h> // For _aligned_malloc, _aligned_free
#include <windows.h>
#else
#include <sys/mman.h> // For madvise
#include <unistd.h> // For sysconf, sbrk
#endif
// Page-aligned allocator for std::vector
template <typename T>
class PageAlignedAllocator {
public:
using value_type = T;
PageAlignedAllocator() noexcept = default;
template <typename U>
PageAlignedAllocator(const PageAlignedAllocator<U>&) 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<T*>(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<size_t>(sysconf(_SC_PAGESIZE));
#endif
};
};
template <typename T, typename U>
bool operator==(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) noexcept {
return true;
}
template <typename T, typename U>
bool operator!=(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) 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<uintptr_t>(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<uintptr_t>(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<char*>(current_break) - static_cast<char*>(start);
}
#endif
inline size_t getPageSize()
{
#ifdef _WIN32
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwPageSize; // Page size in bytes
#else
return static_cast<size_t>(sysconf(_SC_PAGESIZE)); // POSIX page size
#endif
};
template <typename T>
inline void PageAlignVectorAligned(std::vector<T, PageAlignedAllocator<T>>& 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<std::uintptr_t>(start) % page_size != 0) {
// Allocate a new aligned vector
std::vector<T, PageAlignedAllocator<T>> 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<std::uintptr_t>(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
}
}

131
common/net/dns.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<DNSBaton*>(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<sockaddr_in6*>(result->ai_addr), addr, 40);
}
else
{
uv_ip4_name(reinterpret_cast<sockaddr_in*>(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<std::chrono::milliseconds>(end - begin).count() > 1500) {
LogInfo(
"Deadline exceeded [{}]",
1500
);
running = false;
}
loop.Process();
}
return ret;
});
return res.get();
}
} // namespace EQ::Net

View File

@ -17,63 +17,15 @@
*/
#pragma once
#include "common/event/event_loop.h"
#include <functional>
#include <string>
namespace EQ
{
namespace Net
{
static void DNSLookup(const std::string &addr, int port, bool ipv6, std::function<void(const std::string&)> cb) {
struct DNSBaton
{
std::function<void(const std::string&)> 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<void(const std::string&)>;
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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@
#include "raycast_mesh.h"
#include "common/eqemu_logsys.h"
#include "common/memory/ksm.hpp"
#include "common/memory/ksm.h"
#include <cassert>
#include <cmath>