Vincit/tarn.js

Error: aborted

dustin-rcg opened this issue · 1 comments

I started seeing this problem after upgrading from knex 0.21.13 to 0.95.4.

In using the destroy() method of knex to destroy connection pools at the end of jest tests (to avoid hanging tests), I am intermittently getting the following error:

  ● Test suite failed to run

    Error: aborted

      94 |     async destroy() {
      95 |         try {
    > 96 |             return await Promise.all([
         |                    ^
      97 |                 this.aurora,
      98 |                 this.diligence,
      99 |                 this.core,

      at PendingOperation.abort (../RCG-Builders/node_modules/knex/node_modules/tarn/dist/PendingOperation.js:25:21)
      at ../RCG-Builders/node_modules/knex/node_modules/tarn/dist/Pool.js:208:25
          at Array.map (<anonymous>)
      at ../RCG-Builders/node_modules/knex/node_modules/tarn/dist/Pool.js:207:53
          at runMicrotasks (<anonymous>)
      at Client_MSSQL.destroy (../RCG-Builders/node_modules/knex/lib/client.js:321:9)
          at async Promise.all (index 1)
      at DataContext.destroy (../RCG-Builders/dist/DataContext.js:96:20)
      at Object.<anonymous> (integration/investments.integration.test.ts:643:3)

(node:11314) UnhandledPromiseRejectionWarning: Error: aborted
(Use `node --trace-warnings ...` to show where the warning was created)
(node:11314) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1904)
(node:11314) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:11314) UnhandledPromiseRejectionWarning: Error: aborted
(node:11314) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1905)
(node:11314) UnhandledPromiseRejectionWarning: Error: aborted
(node:11314) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1906)
(node:11314) UnhandledPromiseRejectionWarning: Error: aborted
(node:11314) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1907)
(node:11314) UnhandledPromiseRejectionWarning: Error: aborted
(node:11314) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1908)
(node:11314) UnhandledPromiseRejectionWarning: Error: aborted
(node:11314) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1909)
(node:11314) UnhandledPromiseRejectionWarning: Error: aborted
(node:11314) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1910)

My code that is erroring is

  async destroy() {
    try {
     await Promise.all(
        [
          this.aurora,
          this.diligence,
          this.core,
          this.dbaUse,
          this.risk,
        ].map((conn) => conn.destroy())
      );
    } catch {}
  }

and this is called at the end of each test suite

afterAll(async () => {
  await builder.destroy();
});

To my knowledge, the way I am awaiting inside the try block means that it should catch both errors that occur before promises are created and returned and errors due to rejected promise. However, this is still producing unhandled promise rejections, which makes no sense to me.

I see that the source code of tarn is using setInterval, so I suspect that this is causing the promise rejections to be unhandled, since tarn's creation of the promises may be deferred onto the event loop and not catchable by me. So, I don't know how to workaround this problem.

Furthermore, I don't see any problem with how my code is trying to destroy a list of connection pools (Knex<any, unknown[]>). Each is a separate connection with its own pool, so destroying one should have no effect on destroying another. I have also tried awaiting each destroy() in sequence, writing out the call to destroy for each connection, but I still get the same unhandled rejection error causing false negatives on my tests. I am running the tests sequentially (using jest --runInBand) not in parallel, so there shouldn't be any issues related to overlapping test runs.

It seems that what was happening was actually a race condition in my test code. I have multiple database mutations running in my test. One of the mutations caused a unique key violation.

The race condition was caused by the way I used Promise.all to execute multiple database mutations concurrently, with only some of them causing the unique key violation. Other of these concurrent mutations happened to execute after jest caught the Promise.all rejection, ended the test, and ran the afterAll function, which called destroy(). Hence the race condition.

Lesson learned that I should not be executing multiple database queries concurrently like this, and would be better off looping with for and awaiting each mutation serially.