Conversation
Fix a race condition where consecutive executeQuery() and getLastInsertId() calls could return incorrect IDs when using a connection pool. Introduces insertAndGetId() that atomically executes INSERT and retrieves the ID on the same pinned connection. Replaces unsafe patterns in guild creation, player statements, and market offers. Also adds market query limits to prevent client debug issues.
Enforce market offer creation limits to prevent abuse: - Maximum offers per player per side (buy/sell): 1000 - Maximum offers per item per side: 700 Add helper functions getPlayerOfferCountPerSide() and getItemOfferCountPerSide() to query current offer counts.
jprzimba
marked this pull request as draft
July 10, 2026 12:51
Contributor
|
works fine didn't notice any issues with this and player gets a kick when trying abuse the market now, only issue were the amount of existing market offers so I had to raise from default 4 to 16. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Database Connection Pool
Problem
The
Databaseclass used a single MySQL connection (MYSQL* handle) protected by astd::recursive_mutex. Every single query — whether from the main game loop, a thread pool task, or an asynchronousDatabaseTaskscall — serialized through this mutex.Under high concurrency (e.g., many players browsing, creating, or accepting market offers simultaneously), queries would pile up behind the single connection lock. The server became unresponsive and eventually crashed as requests accumulated faster than they could be processed.
Solution
Replaced the single-connection architecture with a connection pool of N reusable MySQL connections (default: 4, configurable up to 32).
Key Changes
src/database/database.hppDBConnectionstruct wrapping aMYSQL*handle and astd::mutexMYSQL* handle+std::recursive_mutex databaseLockwithstd::vector<std::unique_ptr<DBConnection>> connectionsgetConnection()/putConnection()for round-robin pool managementtls_pinnedConnectionfor safe transaction supportescapeString(),escapeBlob(), andgetLastInsertId()fromconstto non-const (they now acquire a connection from the pool)src/database/database.cppconnect()now initializes N connections instead of oneexecuteQuery()/storeQuery()acquire a connection from the pool, execute the query, and return itescapeString()/escapeBlob()acquire any available connection for the escape operationgetLastInsertId()queries the last insert ID from the connection usedbeginTransaction()acquires a connection from the pool and pins it to the calling thread viathread_localcommit()/rollback()use the pinned connection, then unpin and release it back to the poolConfiguration
MYSQL_POOL_SIZEinconfig_enums.hppmysqlPoolSize(default: 4) inconfig.lua.distconfigmanager.cppwith bounds clamping (min 1, max 32)How It Works
Normal Queries
Threads A and B now run their queries concurrently on different connections.
Transactions
The pinned connection is stored in
thread_localstorage, so nested queries within the same transaction automatically use the correct connection without deadlocking.Connection Selection
Round-robin via
std::atomic<size_t> nextIndexensures even distribution across all connections. If a connection is busy, the calling thread blocks on that connection's mutex — identical to the old behavior but with N parallel slots instead of 1.Benefits
Configuration
In
config.lua:Recommended values: