3

I just don't understand why the below code opens a new connection/pool for every request it gets. As a result, this code generates hundreds of open connections and eventually crashes within an hour or less.

Added the error message at the bottom.

db.js

function connection() {
    try {
        const mysql = require('mysql2');
        const config = require('../config');

        const pool = mysql.createPool(config.db);
        const promisePool = pool.promise();

        return promisePool;
    } catch (error) {
        return console.log(`Could not connect - ${error}`);
    }
}

const pool = connection();

module.exports = {
    connection: async () => pool.getConnection(),
    execute: (...params) => pool.execute(...params)
};

config.js

const config = {
    db: {
        host: process.env.DB_HOST,
        port: process.env.DB_PORT,
        user: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
        connectionLimit: 10,
        waitForConnections: true,
        queueLimit: 0
    },
    listPerPage: 10,
};

module.exports = config;

And now the file where I query the database:

const db = require('./db');

async function getByName(name) {
    const sql = `SELECT name, pubkey FROM name WHERE name="${name}"`;

    const [result] = await db.execute(sql);

    if (result && result.length == 1) {
        var resp = JSON.parse(`{
            "names": {
                "${result[0].name}": "${result[0].pubkey}"
            }
        }`);

        return resp;
    } else {
        return {};
    }
}

Thanks for your help!

======= Update 1 =======

Full code can be found here: https://github.com/sebastiansieber/nostr-nip05-verification

(Don’t worry it’s a small repository, you’ll find above files easily)

======= Update 2 =======

This is the error message I receive when the application exits due to too many connections

Error: Too many connections
    at Packet.asError (/usr/src/app/node_modules/mysql2/lib/packets/packet.js:728:17)
    at ClientHandshake.execute (/usr/src/app/node_modules/mysql2/lib/commands/command.js:29:26)
    at PoolConnection.handlePacket (/usr/src/app/node_modules/mysql2/lib/connection.js:487:32)
    at PacketParser.onPacket (/usr/src/app/node_modules/mysql2/lib/connection.js:94:12)
    at PacketParser.executeStart (/usr/src/app/node_modules/mysql2/lib/packet_parser.js:75:16)
    at Socket.<anonymous> (/usr/src/app/node_modules/mysql2/lib/connection.js:101:25)
    at Socket.emit (node:events:394:28)
    at addChunk (node:internal/streams/readable:312:12)
    at readableAddChunk (node:internal/streams/readable:287:9)
    at Socket.Readable.push (node:internal/streams/readable:226:10) {
  code: 'ER_CON_COUNT_ERROR',
  errno: 1040,
  sqlState: '',
  sqlMessage: 'Too many connections',

======= Update 3 =======

As requested please see how the process list looks like:

enter image description here

sebastiansieber
  • 63
  • 1
  • 15
  • What leads you to believe that multiple connections are being made? – Phil Jan 24 '23 at 04:20
  • Are you sure that the pool is only created once and not for every connection? – Georg Richter Jan 24 '23 at 06:18
  • @Phil I use << show status where variable_name = 'threads_connected'; >> and similar to check the number of connections and who is connecting – sebastiansieber Jan 24 '23 at 06:32
  • @GeorgRichter no, this might be the issue but not sure how I can check, a simple console.log debugging doesn’t indicate that I open a new connection for every request – sebastiansieber Jan 24 '23 at 06:33
  • Sorry, I have no idea what that means? Is that some kind of code? If so, for what? – Phil Jan 24 '23 at 06:41
  • With Mariadb this allows you to see the number of open connections – sebastiansieber Jan 24 '23 at 06:47
  • What do you see when you run a `show processlist` on your MySQL server after running the script for a while? I reviewed your code and (while I would remove your function usage on "db.js") it looks like everything should work. I would analyse what the processes are doing (are they getting stuck on a query?), also I would change `execute` to `query` (and move your concatenated parameters to the function to have them escaped - your code is currently allowing SQL injections). If possible, update your questions with the output from your process list. – Diogo Raminhos Jan 26 '23 at 04:47
  • @DiogoRaminhos thanks for your input, I have updated the post with the output of the processlist. I will make the change from execute to query, but I doubt that makes a difference (I'm aware of the vulnerability in terms of SQL injections, but I'll deal with that later) – sebastiansieber Jan 26 '23 at 08:25
  • [Please do not upload images of code/data/errors.](//meta.stackoverflow.com/q/285551) – Georg Richter Jan 26 '23 at 09:12

2 Answers2

1

Okay the issue was a misconfigured MariaDB instead of an issue with the code.

The following question / answer pointed me in the right direction: MySql Proccesslist filled with "Sleep" Entries leading to "Too many Connections"?

So I ran an SQL command to check what the wait_timeout is:

SQL Output

As you can see the timeout is 28800 seconds, so it will keep them sleeping for 28800 seconds before closing them. Before the connections are closed the database reaches its limit of max_connections of 500 and then the application crashes.

The solution was to change the configuration of MariaDB by changing the wait_timeout=120 which closes sleeping connections after 120 seconds.

Hope this helps someone else.

node_modules
  • 4,790
  • 6
  • 21
  • 37
sebastiansieber
  • 63
  • 1
  • 15
  • 1
    [Please do not upload images of code/data/errors.](//meta.stackoverflow.com/q/285551) – Georg Richter Jan 26 '23 at 09:12
  • If connections aren't used anymore, they should be closed from client. Killing the connection by lowering the wait timeout only frees resources on MariaDB database server, but not on the client. – Georg Richter Jan 26 '23 at 10:36
  • Everything I read says you shouldn't manually close connections as this should happen automatically, what do you suggest I do? – sebastiansieber Jan 26 '23 at 10:41
  • Georg is right. You're not solving the issue - you're just using duct tape. I ran your script locally and bombarded it with requests (and my wait_timeout is 28800) and it worked fine. I'll be posting an answer soon, standby. – Diogo Raminhos Jan 26 '23 at 11:22
0

I ran your script locally and bombarded it with requests - it ran just fine, complied with the connections limit and never spawned more than 10 connections.

At this point, I'm convinced that you're not facing a MySQL leak, but rather what I would call an "execution" leak - based on my observations, you're either spawning the project process multiple times or the connections come from elsewhere.

How are you running the script? Are you using docker? If so, stop all your containers, run the script directly on your machine (using node directly) and see if the problem keeps happening.

Before doing so you may want to refer to Ensuring that only a single instance of a nodejs application is running.

Your code is not leaking, therefore something else must be causing this.

Diogo Raminhos
  • 2,023
  • 16
  • 24
  • I have removed the "accepted flag" from my own answer, will review what you have shared, but will take me a bit of time, thanks for now – sebastiansieber Jan 27 '23 at 08:22