Reuse MySQL connections within requests

This commit is contained in:
root
2026-07-13 00:08:07 +00:00
parent 0ff42f357f
commit b1dfe6c807
5 changed files with 73 additions and 21 deletions
+30 -4
View File
@@ -3,10 +3,9 @@
#include <stdlib.h>
#include "mysql-connector.h"
// MySQL handles are request-scoped framework resources. User code may call
// mysql_disconnect() to close early, but leaked/exception-skipped handles are
// still closed by cleanup_mysql_connections() at request end. Handles must not
// be cached across requests or stored in globals/sessions.
// Same-credential mysql_connect() calls lease one request-local connection.
// mysql_disconnect() releases a lease; cleanup_mysql_connections() owns the
// actual close at request end, including exception/fatal recovery paths.
static void mysql_register_request_connection(MySQL* db)
{
if(!context || !db)
@@ -16,6 +15,31 @@ static void mysql_register_request_connection(MySQL* db)
connections.push_back(db);
}
MySQL* mysql_connect(String host, String username, String password)
{
if(context)
{
for(void* raw : context->resources.mysql_connections)
{
MySQL* db = (MySQL*)raw;
if(db && db->request_pooled && db->connection && db->request_host == host && db->request_username == username && db->request_password == password)
{
db->request_leases++;
return(db);
}
}
}
MySQL* db = new MySQL();
db->request_cleanup_delete = true;
db->request_pooled = context != 0;
db->request_leases = 1;
db->request_host = host;
db->request_username = username;
db->request_password = password;
db->connect(host, username, password);
return(db);
}
bool MySQL::connect(String host, String username, String password)
{
// Register regardless of outcome: tracking is about the wrapper's
@@ -325,6 +349,7 @@ void MySQL::disconnect()
if(connection)
mysql_close((MYSQL*)connection);
connection = NULL;
request_leases = 0;
if(context)
{
auto& connections = context->resources.mysql_connections;
@@ -389,6 +414,7 @@ void cleanup_mysql_connections()
MySQL* db = (MySQL*)context->resources.mysql_connections.back();
context->resources.mysql_connections.pop_back();
bool should_delete = db->request_cleanup_delete;
db->request_pooled = false;
db->disconnect();
if(should_delete)
delete db;
+14 -11
View File
@@ -21,6 +21,11 @@ struct MySQL {
u64 insert_id = 0;
String statement_info = ""; //
bool request_cleanup_delete = false;
bool request_pooled = false;
u32 request_leases = 0;
String request_host;
String request_username;
String request_password;
std::vector<MySQLFieldInfo> field_info;
@@ -33,26 +38,24 @@ struct MySQL {
DValue query(String q, StringMap params);
DValue get_pending_result();
// Unregisters from request connection tracking, so stack-allocated instances
// cannot leave dangling pointers behind for request cleanup. Heap handles
// returned by mysql_connect() are also request-owned and auto-closed at the
// end of the request if user code does not call mysql_disconnect() first.
// Unregisters non-pooled instances from request tracking. mysql_connect()
// handles remain request-owned until cleanup after their leases are released.
~MySQL() { disconnect(); }
};
inline MySQL* mysql_connect(String host = "localhost", String username = "root", String password = "")
{
MySQL* m = new MySQL();
m->request_cleanup_delete = true;
m->connect(host, username, password);
return(m);
}
MySQL* mysql_connect(String host = "localhost", String username = "root", String password = "");
inline void mysql_disconnect(MySQL* m)
{
if(!m)
return;
if(m->request_pooled)
{
if(m->request_leases > 0)
m->request_leases--;
return;
}
m->disconnect();
delete m;
}
+27 -4
View File
@@ -804,6 +804,7 @@ public:
// never cache these opaque handles across requests.
std::vector<SQLite*> sqlite_handles;
std::vector<MySQL*> mysql_handles;
std::vector<MySQL*> mysql_request_pool;
#endif
~WasmWorkspace()
{
@@ -820,7 +821,7 @@ public:
for(auto* db : sqlite_handles)
if(db)
delete db; // ~SQLite disconnects
for(auto* db : mysql_handles)
for(auto* db : mysql_request_pool)
if(db)
delete db; // ~MySQL disconnects
#endif
@@ -2409,11 +2410,32 @@ private:
String op = request["op"].to_string();
if(op == "connect")
{
MySQL* db = new MySQL();
bool ok = db->connect(request["host"].to_string(), request["username"].to_string(), request["password"].to_string());
String host = request["host"].to_string();
String username = request["username"].to_string();
String password = request["password"].to_string();
MySQL* db = 0;
for(auto* pooled : self->mysql_request_pool)
if(pooled && pooled->connection && pooled->request_host == host && pooled->request_username == username && pooled->request_password == password)
{
db = pooled;
break;
}
bool ok = db != 0;
if(!db)
{
db = new MySQL();
db->request_pooled = true;
db->request_host = host;
db->request_username = username;
db->request_password = password;
ok = db->connect(host, username, password);
if(ok && db->connection)
self->mysql_request_pool.push_back(db);
}
u64 handle = 0;
if(ok && db->connection)
{
db->request_leases++;
self->mysql_handles.push_back(db);
handle = self->mysql_handles.size();
}
@@ -2443,7 +2465,8 @@ private:
}
else if(op == "disconnect" && db)
{
delete db;
if(db->request_leases > 0)
db->request_leases--;
self->mysql_handles[(size_t)handle - 1] = 0;
}
}