node.js
couchbase
database
troubleshooting
programming

node.js never exits after insert to couchbase, opposite of most node questions

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If a Node.js script never exits after a Couchbase insert, the usual reason is that open resources keep the event loop alive. Database connections, pending timers, and background handles can all prevent process termination even when main logic finished. The fix is to close Couchbase resources explicitly and verify no lingering handles remain.

Why Node Process Stays Alive

Node exits only when event loop has no active work. Couchbase SDK maintains sockets and internal timers for cluster operations. If cluster connection is left open, process remains running.

Minimal pattern that hangs without cleanup:

javascript
1const couchbase = require('couchbase');
2
3(async () => {
4  const cluster = await couchbase.connect('couchbase://127.0.0.1', {
5    username: 'Administrator',
6    password: 'password'
7  });
8
9  const bucket = cluster.bucket('demo');
10  const collection = bucket.defaultCollection();
11
12  await collection.upsert('doc::1', { ok: true });
13  console.log('insert done');
14  // missing cluster.close()
15})();

This script may not terminate because connection remains active.

Proper Cleanup with cluster.close

Close cluster in finally block so cleanup runs on success and failure.

javascript
1const couchbase = require('couchbase');
2
3(async () => {
4  let cluster;
5  try {
6    cluster = await couchbase.connect('couchbase://127.0.0.1', {
7      username: 'Administrator',
8      password: 'password'
9    });
10
11    const collection = cluster.bucket('demo').defaultCollection();
12    await collection.upsert('doc::2', { status: 'saved' });
13    console.log('insert complete');
14  } catch (err) {
15    console.error('operation failed', err);
16    process.exitCode = 1;
17  } finally {
18    if (cluster) {
19      await cluster.close();
20    }
21  }
22})();

This usually allows process to exit naturally.

Check for Other Event Loop Handles

If script still hangs, inspect likely causes:

  • un-cleared setInterval
  • open HTTP servers
  • pending file watchers
  • unclosed database pools

Debug with active handles during troubleshooting:

javascript
1setTimeout(() => {
2  console.log('active handles:', process._getActiveHandles().length);
3  console.log('active requests:', process._getActiveRequests().length);
4}, 1000);

These internal methods are for debugging only, not production logic.

Script Versus Long-Running Service Context

For web APIs, process should stay alive by design, so open cluster connection is normal. For one-off scripts, explicit close is required.

Pattern for service startup:

  • connect once during boot
  • reuse shared cluster object
  • close gracefully on shutdown signals

Signal handler example:

javascript
1process.on('SIGTERM', async () => {
2  console.log('SIGTERM received, closing Couchbase');
3  if (global.cluster) {
4    await global.cluster.close();
5  }
6  process.exit(0);
7});

Graceful shutdown prevents leaked sockets on orchestrated deployments.

Avoid Forced Exit as First Choice

Calling process.exit() immediately after insert can hide unresolved operations and lose buffered logs. Prefer clean resource closure first.

Only use forced exit for controlled CLI tooling where you fully understand side effects.

Reuse a Single Cluster Per Process

Opening many cluster instances in one process can keep extra sockets alive and complicate shutdown. Prefer one shared cluster object and pass collections where needed.

javascript
1let sharedCluster;
2
3async function getCollection() {
4  if (!sharedCluster) {
5    sharedCluster = await couchbase.connect('couchbase://127.0.0.1', {
6      username: 'Administrator',
7      password: 'password'
8    });
9  }
10  return sharedCluster.bucket('demo').defaultCollection();
11}

This pattern simplifies lifecycle management and reduces hanging-process surprises.

Add Timeouts for Hung Operations

Network issues can hang awaits. Add operation-level timeout policies where supported and handle rejections.

javascript
const result = await collection.upsert('doc::3', { ok: 1 }, { timeout: 5000 });
console.log(result.cas.toString());

Timeouts plus retries make scripts more deterministic.

Common Pitfalls

A common pitfall is creating cluster connection inside helper function repeatedly and never closing each instance.

Another issue is mixing callback and promise APIs incorrectly, leaving pending operations unresolved.

A third issue is assuming insert completion implies all internal resources are released automatically.

Teams also ignore shutdown handling in long-running services, leading to hanging deployments during container termination.

Summary

  • Node process stays alive while Couchbase connections or other handles remain open
  • Always close Couchbase cluster in finally for one-off scripts
  • Inspect active handles when process does not terminate as expected
  • Distinguish CLI scripts from service lifecycle connection patterns
  • Prefer graceful cleanup and timeouts over immediate forced process exit

Course illustration
Course illustration

All Rights Reserved.