node-mysql2 icon indicating copy to clipboard operation
node-mysql2 copied to clipboard

Remove callback layer to catch sync connection exceptions

Open jafl opened this issue 5 years ago • 27 comments

This PR restructures PromisePool's query and execute so they catch synchronous exceptions.

I don't think Connection.query actually throws synchronous exceptions, so we could remove the changes to support PromisePool.query, but I felt that would be going only half way. Why doesn't Connection.query throw exceptions like Connection.execute?

Alternative to #931

jafl avatar Mar 28 '19 22:03 jafl

I tried merging the latest from master, but the precommit hook does a major reformatting job. This happens even when I pull a clean copy without any pre-existing node_modules directory. How can I get around this?

jafl avatar May 09 '19 17:05 jafl

Any idea when this PR will be merged? I am having the same issue where the Promise version is not catching this error and I just get an app crash

th317erd avatar Sep 12 '19 22:09 th317erd

@th317erd can you show some repro code for this behaviour?

@jafl sorry I missed this pr. One thing I don't like is that you want to duplicate pool connection release logic in promise wrapper while currently it's delegated to base implementation

sidorares avatar Sep 13 '19 00:09 sidorares

@sidorares I don't think any reproducible code needs to be shown. The situation is simple: A throw is happening inside a nextTick (without a try/catch in internal code). This will always result in an app-crash (unless I am mistaken?).

Here is a backtrace:

connection.js:575
          throw new TypeError(
          ^

TypeError: Bind parameters must not contain undefined. To pass SQL NULL specify JS null
    at options.values.forEach.val (/home/wyatt/Projects/work/eVault-Node/node_modules/mysql2/lib/connection.js:575:17)
    at Array.forEach (<anonymous>)
    at PoolConnection.execute (/home/wyatt/Projects/work/eVault-Node/node_modules/mysql2/lib/connection.js:567:22)
    at getConnection (/home/wyatt/Projects/work/eVault-Node/node_modules/mysql2/lib/pool.js:163:31)
    at process.nextTick (/home/wyatt/Projects/work/eVault-Node/node_modules/mysql2/lib/pool.js:44:37)
    at process.internalTickCallback (internal/process/next_tick.js:70:11)

And, just for kix, this will do it:

var mysql2 = require('mysql2/promise');
var pool = mysql2.createPool(...);

(async function() {
  try {
    await pool.execute('some statement ?', [ undefined ]);
  } catch (e) {
    console.log('It would be great if I could catch the ball!');
  } finally {
    await pool.end();
  }
})();

Ball dropped... hard. :)

As a workaround for now I just added my own validation and throws before I ever call execute... but still, a fix would be nice.

th317erd avatar Sep 13 '19 00:09 th317erd

Further, this is the problem: pool.js

getConnection(cb) {
    if (this._closed) {
      return process.nextTick(() => cb(new Error('Pool is closed.')));
    }
    let connection;
    if (this._freeConnections.length > 0) {
      connection = this._freeConnections.shift();
      this.emit('acquire', connection);
      return process.nextTick(() => cb(null, connection));
    }
....

The return process.nextTick(() => cb(null, connection)); needs to instead be return process.nextTick(() => { try { cb(null, connection); } catch (e) { cb(e, null); } });

th317erd avatar Sep 13 '19 00:09 th317erd

I'm happy to add extra try/catch around cb invocations, especially that now it's much cheaper in V8 compared to 6 years ago

sidorares avatar Sep 13 '19 00:09 sidorares

The return process.nextTick(() => cb(null, connection)); needs to instead be return process.nextTick(() => { try { cb(null, connection); } catch (e) { cb(e, null); } });

How does this help with catching exceptions related to undefined values?

jafl avatar Sep 13 '19 22:09 jafl

@jafl sorry I missed this pr. One thing I don't like is that you want to duplicate pool connection release logic in promise wrapper while currently it's delegated to base implementation

I wasn't happy with it, either, but I couldn't see any other way to make the exceptions catchable. Duplicating the logic removes the extra layer, so exceptions propagate correctly.

jafl avatar Sep 13 '19 22:09 jafl

@jafl yeah, I see your point

can you try to check where error stack points with your changes when you run this


(async function() {
  try {
    await pool.execute('select 1+?', [ undefined ]);
  } catch (e) {
    console.log('It would be great if I could catch the ball!');
  } finally {
    await pool.end();
  }
})();

also would be good to add unit test

sidorares avatar Sep 13 '19 22:09 sidorares

Sorry for taking so long. Life got in the way...

I added your code as a test in test-promise-wrappers.js, and it prints:

  It would be great if I could catch the ball!
  error: TypeError: Bind parameters must not contain undefined. To pass SQL NULL specify JS null
      at /Users/jolindal/tools/node-mysql2/lib/connection.js:602:17
      at Array.forEach (<anonymous>)
      at PoolConnection.execute (/Users/jolindal/tools/node-mysql2/lib/connection.js:594:22)
      at /Users/jolindal/tools/node-mysql2/promise.js:110:11
      at new Promise (<anonymous>)
      at PromisePoolConnection.execute (/Users/jolindal/tools/node-mysql2/promise.js:107:12)
      at /Users/jolindal/tools/node-mysql2/promise.js:350:19
      at processTicksAndRejections (internal/process/task_queues.js:93:5)
      at async testCatchSyncUndefinedError (/Users/jolindal/tools/nodemysql2/test/integration/promise-wrappers/test-promise-wrappers.js:452:5)```

It looks like it works.

jafl avatar Nov 08 '19 22:11 jafl

Have you had a chance to review the diffs?

jafl avatar Jan 15 '20 17:01 jafl

Thanks @jafl! could you fix link errors here? Also, is there any unit test that touches the issue that this PR fixes?

sidorares avatar Jan 23 '20 04:01 sidorares

Both builds are complaining about indentation - but that is caused by the pre-commit hook.

jafl avatar Jan 23 '20 18:01 jafl

I added the unit test in commit 70b75a7

jafl avatar Jan 23 '20 18:01 jafl

How do I force the pre-commit hook to indent the way lint wants?

jafl avatar Jan 29 '20 18:01 jafl

Build is fixed.

jafl avatar Feb 24 '20 18:02 jafl

What else needs to be done before this PR can be merged?

jafl avatar Mar 08 '20 20:03 jafl

hey @jafl , the PR probably looks good, I just need to find time to review it as it is quite big

sidorares avatar Mar 09 '20 08:03 sidorares

I wanted to use @jafl 's fix branch but it seems if call getConnection() on a pool, the query method on that connection expects a pool parameter as first argument instead of the query string. This, for example crashes: let conn = await pool.getConnection(); conn.query('BEGIN');

(later edit: testing by requiring mysql2/promise)

SebastianZaha avatar Apr 30 '20 11:04 SebastianZaha

Thank you for catching this! I have adjusted it.

jafl avatar May 02 '20 21:05 jafl

Is this PR going to be merged into master?

leibale avatar Oct 08 '20 01:10 leibale

@leibale yes

sidorares avatar Oct 08 '20 05:10 sidorares

My naive sugestion is to return cb(e) in the catch clause of execute in pool.js. It will make our code simpler unlike now manually checking for all values.

Mikapsien avatar Jun 24 '21 04:06 Mikapsien

My naive sugestion is to return cb(e) in the catch clause of execute in pool.js. It will make our code simpler unlike now manually checking for all values.

Can you provide more details? What values are you checking for? Can you provide example code?

jafl avatar Jun 24 '21 23:06 jafl

Is this PR still useful, or is it obsolete?

jafl avatar Apr 16 '24 21:04 jafl

Codecov Report

Attention: Patch coverage is 87.50000% with 6 lines in your changes are missing coverage. Please review.

Project coverage is 90.31%. Comparing base (e87ffb7) to head (273e632).

Files Patch % Lines
promise.js 87.50% 4 Missing :warning:
lib/pool.js 85.71% 2 Missing :warning:
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #938      +/-   ##
==========================================
- Coverage   90.33%   90.31%   -0.02%     
==========================================
  Files          71       71              
  Lines       15731    15749      +18     
  Branches     1344     1350       +6     
==========================================
+ Hits        14210    14224      +14     
- Misses       1521     1525       +4     
Flag Coverage Δ
compression-0 90.12% <87.50%> (-0.22%) :arrow_down:
compression-1 90.31% <87.50%> (-0.02%) :arrow_down:
tls-0 89.84% <87.50%> (-0.02%) :arrow_down:
tls-1 90.12% <87.50%> (-0.02%) :arrow_down:

Flags with carried forward coverage won't be shown. Click here to find out more.

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

codecov[bot] avatar Apr 18 '24 23:04 codecov[bot]

@sidorares I cleaned up the PR for you to review again.

jafl avatar Apr 19 '24 00:04 jafl