{"id":"923b90b008e0fefd","repo":"brianc/node-postgres","slug":"release-called-on-client-which-has-already-been-re","errorCode":null,"errorMessage":"Release called on client which has already been released to the pool.","messagePattern":"Release called on client which has already been released to the pool\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/pg-pool/index.js","lineNumber":27,"sourceCode":"  return i === -1 ? undefined : list.splice(i, 1)[0]\n}\n\nclass IdleItem {\n  constructor(client, idleListener, timeoutId) {\n    this.client = client\n    this.idleListener = idleListener\n    this.timeoutId = timeoutId\n  }\n}\n\nclass PendingItem {\n  constructor(callback) {\n    this.callback = callback\n  }\n}\n\nfunction throwOnDoubleRelease() {\n  throw new Error('Release called on client which has already been released to the pool.')\n}\n\nfunction promisify(Promise, callback) {\n  if (callback) {\n    return { callback: callback, result: undefined }\n  }\n  let rej\n  let res\n  const cb = function (err, client) {\n    err ? rej(err) : res(client)\n  }\n  const result = new Promise(function (resolve, reject) {\n    res = resolve\n    rej = reject\n  }).catch((err) => {\n    // replace the stack trace that leads to `TCP.onStreamRead` with one that leads back to the\n    // application that created the query\n    Error.captureStackTrace(err)","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/brianc/node-postgres/blob/c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711/packages/pg-pool/index.js#L9-L45","documentation":"Thrown by the pool when client.release() is called more than once on the same checked-out client. The pool wraps each client's release function with _releaseOnce (index.js:369-380), which tracks a 'released' boolean; the second invocation calls throwOnDoubleRelease() at line 374. This protects pool integrity — returning the same client twice would corrupt the idle list and let two consumers use one connection.","triggerScenarios":"pool.connect((err, client, release) => { release(); release(); }). Also: calling client.release() in an error handler and again in the success handler of the same query. Using pool.query() internally does not trigger this (it manages release itself), but manual pool.connect() with overlapping release calls does.","commonSituations":"An error-handling path calls release(err) and then the normal completion path also calls release(). Async control flow (e.g., a timeout race) that releases then the query callback also releases. Wrapping pooled client usage in a helper that releases on exit but the caller also releases.","solutions":["Track release state with a boolean flag and only call release() when not already released.","Prefer pool.query() over manual pool.connect()/release() so the pool handles lifecycle automatically.","Ensure error and success handlers are mutually exclusive (early-return after the first release)."],"exampleFix":"// before\npool.connect(async (err, client, release) => {\n  if (err) { release(err); return; }\n  try {\n    await client.query('SELECT 1');\n    release();\n  } catch (e) {\n    release(e);\n  }\n  release(); // bug: always runs\n});\n\n// after\npool.connect(async (err, client, release) => {\n  if (err) { release(err); return; }\n  try {\n    await client.query('SELECT 1');\n    release();\n  } catch (e) {\n    release(e);\n  }\n  // no unconditional release\n});\n// or simpler:\npool.query('SELECT 1').then(...)","handlingStrategy":"validation","validationCode":"function makeSafeRelease(release) {\n  let released = false;\n  return (err) => {\n    if (released) return; // silently ignore double release\n    released = true;\n    release(err);\n  };\n}\n\n// usage:\npool.connect((err, client, done) => {\n  const release = makeSafeRelease(done);\n  // use release() everywhere; double calls are no-ops\n});","typeGuard":null,"tryCatchPattern":"pool.connect(async (err, client, release) => {\n  if (err) throw err;\n  let released = false;\n  const safeRelease = (e) => {\n    if (released) return;\n    released = true;\n    release(e);\n  };\n  try {\n    await client.query('SELECT 1');\n    safeRelease();\n  } catch (e) {\n    safeRelease(e);\n  }\n});","preventionTips":["Prefer pool.query() which handles acquire/release automatically and cannot double-release.","Wrap manual release calls in an idempotent guard so the second call is a no-op.","Make error and success paths mutually exclusive with early returns after the first release.","Use async/await with try/finally to ensure a single, guaranteed release."],"tags":["pool","connection-management","lifecycle"],"analyzedSha":"c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711","analyzedAt":"2026-08-03T18:47:28.334Z","schemaVersion":2}