Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PoolConnection leaked by MysqlDriver.js #8790

Closed
DillonSadofsky opened this issue Mar 23, 2022 · 3 comments · Fixed by #8803
Closed

PoolConnection leaked by MysqlDriver.js #8790

DillonSadofsky opened this issue Mar 23, 2022 · 3 comments · Fixed by #8803
Labels

Comments

@DillonSadofsky
Copy link

DillonSadofsky commented Mar 23, 2022

Issue Description

Using the current version of TypeORM (0.3.3), I noticed if I set my MySQL connection pool to a particularly small size, my app would lock up on attempting to get the first exclusive connection via QueryRunner.connect(). I poked around and stepped through the MysqlDriver code and I believe this is because on line 306 of MysqlDriver.js there is the line:

        const result = (await this.createQueryRunner("master").query(`SELECT VERSION() AS \`version\``));

It appears to want to run the version query to know whether certain features are available, but the queryrunner that is created here never releases back into the pool, which eats up a connection until the server shuts down.

Inlining QueryRunner.query() like this implicitly calls connect() on line 145 of MysqlQueryRunner.js but it is never released.

As documented here: https://github.com/typeorm/typeorm/blob/master/docs/query-runner.md
a query runner that gets connected needs to be explicitly released after we're done.

createQueryRunner("master") is called several other times in MysqlDriver.js but it seems like the connection is released correctly in the other areas.

Expected Behavior

If a connection is implicitly pulled from the query pool by any driver overhead, it should be released back into the pool or terminated so that the pool can reallocate that connection later.

Actual Behavior

One connection is pulled from the pool and never released back.

Steps to Reproduce

Create a typeorm project, establish a mysql connection pool of size 1, and then attempt to retrieve an exclusive connection from the pool using the following code:

		const connection = pool.createQueryRunner()
		const rawConnection = await connection.connect() as mysql.PoolConnection

My Environment

typeorm .3.3

Relevant Database Driver(s)

DB Type Reproducible
aurora-mysql no
aurora-postgres no
better-sqlite3 no
cockroachdb no
cordova no
expo no
mongodb no
mysql yes
nativescript no
oracle no
postgres no
react-native no
sap no
sqlite no
sqlite-abstract no
sqljs no
sqlserver no

Are you willing to resolve this issue by submitting a Pull Request?

  • ✖️Yes, I have the time, and I know how to start.
  • ✖️ Yes, I have the time, but I don't know how to start. I would need guidance.
  • ✖️ No, I don’t have the time, but I can support (using donations) development.
  • ✅ No, I don’t have the time and I’m okay to wait for the community / maintainers to resolve this issue.
@DillonSadofsky
Copy link
Author

I did a quick test and changed that line in MysqlDriver.js to:

		const queryRunner = await this.createQueryRunner("master");
		const result = await queryRunner.query(`SELECT VERSION() AS \`version\``);
		await queryRunner.release();
		const dbVersion = result[0].version;

And it seemed like it fixed it, but I'm not super familiar with the internals, so I'll leave it to others to review.

@MonkeysFletch
Copy link

@DillonSadofsky Glad you figured this out. Upgrading to 0.3.3 from 0.2.x had my single connection Lambdas hanging. Upping them to 2 connections in the settings allowed them to succeed for the time being.

Here's my relevant information if it helps anyone out with confirming/fixing:

export class Database {
    private source: DataSource;

    constructor(options?: DeepPartial<DataSourceOptions>) {
        let defaultOptions: DataSourceOptions = {
            type: "mysql",
            host: process.env.DB_HOST,
            port: 3306,
            username: process.env.DB_USER,
            password: process.env.DB_PASS,
            database: process.env.DB_NAME,
            connectTimeout: 20000,
            acquireTimeout: 20000,
            synchronize: true,
            logging: "all",
            entities: [
                ...
            ],
            extra: {
                connectionLimit: 1,
            },
        };
        if (options) _.merge(defaultOptions, options)
        this.source = new DataSource(defaultOptions)
    }

    public async getConnection(): Promise<DataSource> {
        const connection = this.source.initialize();
        return connection;
    } 
}
const database = new Database();
let connection = database.getConnection();

const Handler: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event, context) => {
    context.callbackWaitsForEmptyEventLoop = false;
    try {
        const connected = await connection;
        // DB stuff here
    
    } catch (err){
        // Error handling
    }
}

I was able to log all the way up to connected, and then it hangs on the same single query followed by a Lambda timeout and failure:

INFO	query: SELECT VERSION() AS `version`

@pleerock
Copy link
Member

pleerock commented Mar 26, 2022

looks like bug was introduced in #8673

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants