Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 10 additions & 41 deletions lib/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,17 @@ const pool = mysql.createPool({
queueLimit: 0
});

// Variable to store interval ID
let keepAliveInterval = null;

// Event listener for monitoring pool
pool.on('connection', function (connection) {
console.log('New MySQL connection established as id ' + connection.threadId);

// Set session timeout for this connection
connection.query("SET SESSION wait_timeout = 28800");
connection.query("SET SESSION interactive_timeout = 28800");
});

pool.on('error', function(err) {
console.log('MySQL Pool Error:', err);
if(err.code === 'PROTOCOL_CONNECTION_LOST') {
console.log('Connection lost, pool will handle reconnection automatically');
}
console.error('MySQL Pool Error:', err);
});

// Function to retry query with exponential backoff
const retryQuery = async (queryStr, bindings = [], maxRetries = 3, delay = 1000) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
Expand All @@ -44,27 +35,23 @@ const retryQuery = async (queryStr, bindings = [], maxRetries = 3, delay = 1000)
});
});
} catch (err) {
// Only retry for connection-related errors
const isConnectionError = (
err.code === 'PROTOCOL_CONNECTION_LOST' ||
err.code === 'ECONNRESET' ||
err.code === 'ETIMEDOUT' ||
err.code === 'ENOTFOUND' ||
err.code === 'ECONNREFUSED' ||
err.code === 4031 ||
err.errno === 2013 || // Lost connection to MySQL server during query
err.errno === 2006 // MySQL server has gone away
err.errno === 2013 ||
err.errno === 2006
Comment on lines +45 to +46
Copy link

Copilot AI Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error code duplication between retryQuery and query functions creates maintenance burden. Consider extracting this logic into a shared helper function like isConnectionError(err) to avoid code duplication and ensure consistency.

Copilot uses AI. Check for mistakes.
);

// If not a connection error or already reached max retry, throw immediately
if (!isConnectionError || attempt >= maxRetries) {
throw err;
}

console.log(`Query attempt ${attempt} failed (connection error):`, err.message);
console.log(`Retrying in ${delay}ms... (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // Exponential backoff
delay *= 2;
}
}
};
Expand All @@ -73,7 +60,6 @@ const query = async (queryStr, bindings = []) => {
try {
return await retryQuery(queryStr, bindings);
} catch (err) {
// Only log error if this is a connection error that has been retried
const isConnectionError = (
err.code === 'PROTOCOL_CONNECTION_LOST' ||
err.code === 'ECONNRESET' ||
Expand All @@ -85,71 +71,56 @@ const query = async (queryStr, bindings = []) => {
err.errno === 2006
);

// Only log if connection error (since it has been retried)
if (isConnectionError) {
console.log('Final query error after retries:', err);
console.error('Final query error after retries:', err);
}

throw err;
}
};

// Function to test connection
const testConnection = async () => {
try {
await query('SELECT 1');
console.log('Database connection test successful');
return true;
} catch (err) {
console.log('Database connection test failed:', err.message);
console.error('Database connection test failed:', err.message);
return false;
}
};

// Function to start keep-alive
const startKeepAlive = () => {
if (keepAliveInterval) {
console.log('Keep-alive already running');
return;
}

keepAliveInterval = setInterval(async () => {
try {
await query('SELECT 1');
console.log('Keep-alive query successful');
} catch (err) {
console.log('Keep-alive query failed:', err.message);
console.error('Keep-alive query failed:', err.message);
}
}, 300000); // Every 5 minutes

console.log('Keep-alive started (every 5 minutes)');
}, 300000);
};

// Function to stop keep-alive
const stopKeepAlive = () => {
if (keepAliveInterval) {
clearInterval(keepAliveInterval);
keepAliveInterval = null;
console.log('Keep-alive stopped');
} else {
console.log('Keep-alive is not running');
}
};

// Function to close pool with graceful shutdown
const closePool = () => {
return new Promise((resolve) => {
// Stop keep-alive first
stopKeepAlive();

pool.end(() => {
console.log('MySQL pool closed');
resolve();
});
});
};

// Function for comprehensive health check
const healthCheck = async () => {
const health = {
status: 'unknown',
Expand Down Expand Up @@ -180,7 +151,6 @@ const healthCheck = async () => {
health.database.responseTime = responseTime;
health.status = 'healthy';

// Additional check for response time
if (responseTime > 5000) {
health.status = 'slow';
} else if (responseTime > 1000) {
Expand All @@ -191,8 +161,7 @@ const healthCheck = async () => {
health.database.connected = false;
health.database.error = err.message;
health.status = 'unhealthy';

console.log('Health check failed:', err.message);
console.error('Health check failed:', err.message);
}

return health;
Expand All @@ -204,7 +173,7 @@ const isHealthy = async () => {
await query('SELECT 1');
return true;
} catch (err) {
console.log('Health check failed:', err.message);
console.error('Health check failed:', err.message);
return false;
}
};
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "letsql",
"version": "1.2.1",
"version": "1.2.2",
"description": "A lightweight and user-friendly Node.js ORM module for MySQL databases. Inspired by Eloquent in Laravel.",
"main": "index.js",
"scripts": {
Expand Down