diff --git a/lib/postgresql.js b/lib/postgresql.js index 91f6ebc0..1b55e1d1 100644 --- a/lib/postgresql.js +++ b/lib/postgresql.js @@ -103,11 +103,19 @@ function PostgreSQL(postgresql, settings) { function attachErrorHandler(settings, pg) { if (settings.onError) { if (settings.onError === 'ignore') { - pg.on('error', function(err) { - debug(err); - }); + // Check if the error handler is already attached + // to avoid possible memory leak + if (!pg.listenerCount('error')) { + pg.on('error', function(err) { + debug(err); + }); + } } else { - pg.on('error', settings.onError); + // Check if the error handler is already attached + // to avoid possible memory leak + if (!pg.listenerCount('error')) { + pg.on('error', settings.onError); + } } } } @@ -165,8 +173,6 @@ PostgreSQL.prototype.executeSQL = function(sql, params, options, callback) { // Release the connection back to the pool. if (releaseCb) { releaseCb(err); - // Remove error event listener to avoid possible memory leak - connection.removeAllListeners('error'); } let result = null; if (data) { diff --git a/package-lock.json b/package-lock.json index 1a80eaed..6b417f6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "loopback-datasource-juggler": "^5.1.5", "mocha": "^11.0.0", "rc": "^1.0.0", + "rewire": "^8.0.0", "should": "^13.2.3", "sinon": "^21.0.0" }, @@ -3660,6 +3661,16 @@ "node": ">=0.10.0" } }, + "node_modules/rewire": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-8.0.0.tgz", + "integrity": "sha512-30aZREqOFlNkNFbv6aSen7TMaS+jAKh2O+EH8EB1/THhgzZ/vIwqh0cDgyud1v+fanFinxXF//Q9SJEBwc/mHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint": "^8.47.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", diff --git a/package.json b/package.json index 1e066f1f..60a3ed59 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "pretest": "node pretest.js", "test": "mocha test/*.test.js", "posttest": "npm run lint", - "lint": "eslint ." + "lint": "eslint .", + "lint-fix": "eslint . --fix" }, "files": [ "intl", @@ -47,6 +48,7 @@ "loopback-datasource-juggler": "^5.1.5", "mocha": "^11.0.0", "rc": "^1.0.0", + "rewire": "^8.0.0", "should": "^13.2.3", "sinon": "^21.0.0" } diff --git a/test/postgresql.attachErrorHandler.test.js b/test/postgresql.attachErrorHandler.test.js new file mode 100644 index 00000000..39ae9927 --- /dev/null +++ b/test/postgresql.attachErrorHandler.test.js @@ -0,0 +1,42 @@ +// Copyright IBM Corp. 2013,2025. All Rights Reserved. +// Node module: loopback-connector-postgresql +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +'use strict'; +const sinon = require('sinon'); +const assert = require('assert'); +const rewire = require('rewire'); +const postgresqlModule = rewire('../lib/postgresql'); +const attachErrorHandler = postgresqlModule.__get__('attachErrorHandler'); + +describe('attachErrorHandler', function() { + let pg; + beforeEach(function() { + pg = { + on: sinon.spy(), + listenerCount: sinon.stub().returns(0), + }; + }); + + it('should attach custom handler if onError is a function', function() { + const handler = sinon.spy(); + const settings = {onError: handler}; + attachErrorHandler(settings, pg); + assert(pg.on.calledOnce, 'pg.on should be called once'); + assert(pg.on.firstCall.args[1] === handler, 'should attach the custom handler'); + }); + + it('should not attach handler if already attached', function() { + pg.listenerCount.returns(1); + const settings = {onError: 'ignore'}; + attachErrorHandler(settings, pg); + assert(pg.on.notCalled, 'pg.on should not be called if already attached'); + }); + + it('should do nothing if onError is not set', function() { + const settings = {}; + attachErrorHandler(settings, pg); + assert(pg.on.notCalled, 'pg.on should not be called if onError is not set'); + }); +});