fix(gameplay): prevent use-after-free in pending delete queues during destruction

This commit is contained in:
2026-07-10 20:35:15 +08:00
parent f8aac019bd
commit 176df4ee04
2 changed files with 25 additions and 4 deletions

View File

@@ -105,6 +105,12 @@ public:
} }
private: private:
std::atomic<bool> m_is_pending_delete_queue_free{false};
std::mutex m_delete_vbo_mutex;
std::mutex m_delete_vao_mutex;
std::vector<std::unique_ptr<VertexBuffer>> m_pending_delete_vbo;
std::vector<std::unique_ptr<VertexArray>> m_pending_delete_vao;
enum class ChunkLoadStyle { RANDOM, CENTER }; enum class ChunkLoadStyle { RANDOM, CENTER };
using ChunkHashMap = using ChunkHashMap =
tbb::concurrent_hash_map<ChunkPos, std::shared_ptr<ClientChunk>, tbb::concurrent_hash_map<ChunkPos, std::shared_ptr<ClientChunk>,
@@ -130,15 +136,11 @@ private:
std::vector<glm::vec4> m_planes; std::vector<glm::vec4> m_planes;
std::jthread m_client_thread; std::jthread m_client_thread;
std::mutex m_delete_vbo_mutex;
std::mutex m_delete_vao_mutex;
mutable std::shared_mutex m_player_info_mutex; mutable std::shared_mutex m_player_info_mutex;
tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue; tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue;
tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue; tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue;
tbb::concurrent_queue<PendingSound> m_pending_sound; tbb::concurrent_queue<PendingSound> m_pending_sound;
std::vector<std::unique_ptr<VertexBuffer>> m_pending_delete_vbo;
std::vector<std::unique_ptr<VertexArray>> m_pending_delete_vao;
std::deque<ChunkPos> m_dirty_queue; std::deque<ChunkPos> m_dirty_queue;
std::vector<const ChunkRenderSnapshot*> m_render_snapshots; std::vector<const ChunkRenderSnapshot*> m_render_snapshots;

View File

@@ -25,11 +25,22 @@ ClientWorld::ClientWorld(AudioEngine& auido, Config& config)
: m_player(*this), m_audio(auido), m_config(config) {} : m_player(*this), m_audio(auido), m_config(config) {}
ClientWorld::~ClientWorld() { ClientWorld::~ClientWorld() {
m_client->close();
stop_client_thread(); stop_client_thread();
stop_thread_pool(); stop_thread_pool();
// Must first clean up and push the generated chunk data into
// m_pending_delete_vbo and m_pending_delete_vao; cannot delete them in the
// destructor, otherwise it will cause leaks and use-after-free.
m_dirty_chunk_queue.clear();
m_pending_upload_queue.clear();
m_chunks.clear(); m_chunks.clear();
if (m_is_pending_delete_queue_free.exchange(true)) {
return;
}
{ {
std::lock_guard lk(m_delete_vbo_mutex); std::lock_guard lk(m_delete_vbo_mutex);
m_pending_delete_vbo.clear(); m_pending_delete_vbo.clear();
@@ -262,10 +273,18 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
} }
} }
void ClientWorld::push_delete_vbo(std::unique_ptr<VertexBuffer>& vbo) { void ClientWorld::push_delete_vbo(std::unique_ptr<VertexBuffer>& vbo) {
if (m_is_pending_delete_queue_free) {
Logger::error("Push delete vbo Use After Free");
return;
}
std::lock_guard lk(m_delete_vbo_mutex); std::lock_guard lk(m_delete_vbo_mutex);
m_pending_delete_vbo.push_back(std::move(vbo)); m_pending_delete_vbo.push_back(std::move(vbo));
} }
void ClientWorld::push_delete_vao(std::unique_ptr<VertexArray>& vao) { void ClientWorld::push_delete_vao(std::unique_ptr<VertexArray>& vao) {
if (m_is_pending_delete_queue_free) {
Logger::error("Push delete vao Use After Free");
return;
}
std::lock_guard lk(m_delete_vao_mutex); std::lock_guard lk(m_delete_vao_mutex);
m_pending_delete_vao.push_back(std::move(vao)); m_pending_delete_vao.push_back(std::move(vao));
} }