简体   繁体   中英

Node.js connection pool produces infinite loop on error

I recently upgraded my generic-pool package to version 3 that makes use of promises - a concept I probably don't understand enough. I have managed to get it working, despite some unexpected differences in operation.

The problem I have is when I start testing error conditions. I have purposely set the password wrong, and when testing, I get an infinite loop of "connection failed" errors - indicating something is triggering the create function despite the error. I assume I have either configured the pool incorrectly, or I am acquiring improperly.

Generic pool factory:

const poolFactory = {
  create: function() {
    return new Promise(function(resolve, reject) {
      var client = mysql.createConnection({
        host: config.host,
        user: config.user,
        password: config.pass,
      });
      client.connect(function(err) {
        if (err != null) {
          log.write('ERROR', "Connection Error: MySQL: " + err.message);
          reject(err);
        } else {
          log.write('INFO', "MySQL Connection created.");
          resolve(client);
        }
      });

    })
  },
  destroy: function(client) {
    return new Promise(function(resolve) {
      client.end(function(err) {
        if (err != null) {
          log.write('ERROR', "DB Error: MySQL: " + err.message);
        } else {
          log.write('INFO', "Database connection closed.");
          resolve();
        }
      });
    })
  }
}

const cp = genericPool.createPool(poolFactory);

Test query that triggers the connection error:

cp.acquire().then(
  function(client) {
    client.query('USE ' + config.db, function(err, results, fields) {
      if (err != null) {
        log.write('ERROR', "DB test error: MySQL: " + err.message);
      } else {
        log.write('INFO', "MySQL connection tested successfully.");
        cp.release(client)
      }
    });
  }).catch(function(err) {
  cp.release(client);
  log.write('ERROR', "Pool Error: " + err.message);
});

My error log fills up with a million lines of:

 Connection Error: MySQL: ER_ACCESS_DENIED_ERROR: Access denied for user 'user'@'localhost' (using password: YES)

I am expecting a single error because I am testing error conditions. What am I doing wrong to get the infinite loop? I thought the reject(err) was supposed to put the promise in a state where it would not answer any more queries?

Can anyone point me in the right direction?

As always - thank-you very much!

EDIT : Here is a complete script that illustrate the problem if anyone cares to see the problem first hand! The console fills up with "ERROR MySQL Connection Error: ER_ACCESS_DENIED_ERROR: Access denied for user 'devUser'@'localhost' (using password: YES)". Thanks again.

// Test App to show logging issue

var pool = require('generic-pool'),
  mysql = require('mysql')

var config = {
  port: 8880,
  host: 'localhost',
  user: 'devUser',
  pass: 'wrong-pass',
  db: 'node-app-db'
}

const poolConfig = {
  max: 3
};

const poolFactory = {
  create: function() {
    return new Promise(function(resolve, reject) {
      var client = mysql.createConnection({
        host: config.host,
        user: config.user,
        password: config.pass,
      });
      client.connect(function(err) {
        if (err != null) {
          console.log('ERROR', "MySQL Connection Error: " + err.message);
          reject(err);
        } else {
          console.log('USAGE', "MySQL Connection created. " + cp.size + " of " + config.poolSize + " connections to DB in use.");
          resolve(client);
        }
      });

    })
  },
  destroy: function(client) {
    return new Promise(function(resolve) {
      client.end(function(err) {
        if (err != null) {
          console.log('ERROR', "DB Error: MySQL: " + err.message);
        } else {
          console.log('USAGE', "Database connection closed. Pool contains ' + cp.size + ' more connections.");
          resolve();
        }
      });
    })
  }
}

const cp = pool.createPool(poolFactory, poolConfig);

cp.acquire().then(
  function(client) {
    client.query('USE ' + config.db, function(err, results, fields) {
      if (err != null) {
        console.log('ERROR', "DB test error: MySQL: " + err.message);
      } else {
        console.log('READY', "MySQL connection tested successfully. DataServer ready for connections.");
        cp.release(client)
      }
    });
  }).catch(function(err) {
  cp.release(client);
  console.log('ERROR', "Pool Error: " + err.message);
});

Cross posting from my original response on the github issue

hey @whiteatom - yep you've hit "known problem" land...

for some history... In v2 a given call to pool.acquire was directly tied to a single call to factory.create and an error in the factory.create would bubble through to the pool.acquire . (this was generally bad ™️ ) In v3 calls to pool.acquire were no longed tied to calls to factory.create and therefore any errors in factory.create could not bubble up to pool.acquire calls because it didn't make any semantic sense. Instead factory errors are now exposed via the Pool itself via event emitters

So to sum up slightly: the promise returned by pool.acquire will only reject due to errors specifically related to the acquire call (such as timeout), and not because of any other errors in the pool. To catch general errors with the pool you will need to attach some event listeners to the Pool instance you have.

There is still the problem that it's possible for the pool to end up in some infinite loop if your factory.create returns promises that only ever reject. To get around this you can build backoff functionality into your factory.create function (this is a bit of a hack though, and I really need to find some way to put backoff in the pool itself.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM