Back to Blog
Node.jsProcessSignalsProduction

Node.js Process Management

Master Node.js process handling. From signals to child processes to graceful shutdown patterns.

B
Bootspring Team
Engineering
March 29, 2021
7 min read

Understanding process management is essential for production Node.js applications. Here's what you need to know.

Process Information#

1// Process basics 2console.log('PID:', process.pid); 3console.log('PPID:', process.ppid); 4console.log('Platform:', process.platform); 5console.log('Architecture:', process.arch); 6console.log('Node version:', process.version); 7console.log('Working directory:', process.cwd()); 8 9// Environment variables 10console.log('NODE_ENV:', process.env.NODE_ENV); 11console.log('All env:', process.env); 12 13// Memory usage 14const used = process.memoryUsage(); 15console.log('Memory:', { 16 heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`, 17 heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`, 18 external: `${Math.round(used.external / 1024 / 1024)}MB`, 19 rss: `${Math.round(used.rss / 1024 / 1024)}MB`, 20}); 21 22// CPU usage 23const startUsage = process.cpuUsage(); 24// ... do work ... 25const endUsage = process.cpuUsage(startUsage); 26console.log('CPU time:', { 27 user: `${endUsage.user / 1000}ms`, 28 system: `${endUsage.system / 1000}ms`, 29}); 30 31// Uptime 32console.log('Uptime:', process.uptime(), 'seconds');

Signal Handling#

1// Common signals 2// SIGTERM - Graceful shutdown request 3// SIGINT - Ctrl+C interrupt 4// SIGHUP - Terminal closed 5// SIGKILL - Force kill (cannot be caught) 6 7// Handle SIGTERM (graceful shutdown) 8process.on('SIGTERM', () => { 9 console.log('SIGTERM received, shutting down...'); 10 gracefulShutdown(); 11}); 12 13// Handle SIGINT (Ctrl+C) 14process.on('SIGINT', () => { 15 console.log('SIGINT received, shutting down...'); 16 gracefulShutdown(); 17}); 18 19// Handle SIGHUP (terminal closed) 20process.on('SIGHUP', () => { 21 console.log('SIGHUP received'); 22 // Reload configuration, rotate logs, etc. 23}); 24 25// Windows equivalent 26if (process.platform === 'win32') { 27 const readline = require('readline'); 28 const rl = readline.createInterface({ 29 input: process.stdin, 30 output: process.stdout, 31 }); 32 33 rl.on('SIGINT', () => { 34 process.emit('SIGINT'); 35 }); 36}

Graceful Shutdown#

1const http = require('http'); 2 3class GracefulShutdown { 4 constructor(server, options = {}) { 5 this.server = server; 6 this.timeout = options.timeout || 30000; 7 this.connections = new Set(); 8 this.shuttingDown = false; 9 10 // Track connections 11 server.on('connection', (conn) => { 12 this.connections.add(conn); 13 conn.on('close', () => this.connections.delete(conn)); 14 }); 15 } 16 17 async shutdown(signal) { 18 if (this.shuttingDown) return; 19 this.shuttingDown = true; 20 21 console.log(`${signal} received, starting graceful shutdown...`); 22 23 // Stop accepting new connections 24 this.server.close(() => { 25 console.log('Server closed'); 26 }); 27 28 // Set timeout for forced shutdown 29 const forceShutdown = setTimeout(() => { 30 console.error('Forcing shutdown...'); 31 process.exit(1); 32 }, this.timeout); 33 34 try { 35 // Close database connections 36 await this.closeDatabase(); 37 38 // Finish processing requests 39 await this.closeConnections(); 40 41 // Clear timeout and exit 42 clearTimeout(forceShutdown); 43 console.log('Graceful shutdown complete'); 44 process.exit(0); 45 } catch (error) { 46 console.error('Error during shutdown:', error); 47 process.exit(1); 48 } 49 } 50 51 async closeConnections() { 52 // End idle connections 53 for (const conn of this.connections) { 54 conn.end(); 55 } 56 57 // Wait for connections to close 58 await new Promise((resolve) => { 59 const checkConnections = () => { 60 if (this.connections.size === 0) { 61 resolve(); 62 } else { 63 setTimeout(checkConnections, 100); 64 } 65 }; 66 checkConnections(); 67 }); 68 } 69 70 async closeDatabase() { 71 // Close database pool 72 if (global.db) { 73 await global.db.end(); 74 } 75 } 76} 77 78// Usage 79const server = http.createServer(app); 80const shutdown = new GracefulShutdown(server); 81 82process.on('SIGTERM', () => shutdown.shutdown('SIGTERM')); 83process.on('SIGINT', () => shutdown.shutdown('SIGINT'));

Child Processes#

1const { spawn, exec, execFile, fork } = require('child_process'); 2 3// spawn - Stream output 4const ls = spawn('ls', ['-la']); 5 6ls.stdout.on('data', (data) => { 7 console.log(`stdout: ${data}`); 8}); 9 10ls.stderr.on('data', (data) => { 11 console.error(`stderr: ${data}`); 12}); 13 14ls.on('close', (code) => { 15 console.log(`Process exited with code ${code}`); 16}); 17 18// exec - Buffer output 19exec('ls -la', (error, stdout, stderr) => { 20 if (error) { 21 console.error(`Error: ${error.message}`); 22 return; 23 } 24 console.log(`stdout: ${stdout}`); 25}); 26 27// exec with promise 28const { promisify } = require('util'); 29const execPromise = promisify(exec); 30 31async function runCommand() { 32 const { stdout } = await execPromise('ls -la'); 33 console.log(stdout); 34} 35 36// fork - For Node.js scripts 37const child = fork('./worker.js'); 38 39child.send({ task: 'process', data: [1, 2, 3] }); 40 41child.on('message', (result) => { 42 console.log('Result:', result); 43}); 44 45// worker.js 46process.on('message', (msg) => { 47 const result = processData(msg.data); 48 process.send(result); 49});

Process Pool#

1const { fork } = require('child_process'); 2const os = require('os'); 3 4class WorkerPool { 5 constructor(workerScript, poolSize = os.cpus().length) { 6 this.workerScript = workerScript; 7 this.poolSize = poolSize; 8 this.workers = []; 9 this.queue = []; 10 11 this.initialize(); 12 } 13 14 initialize() { 15 for (let i = 0; i < this.poolSize; i++) { 16 this.createWorker(); 17 } 18 } 19 20 createWorker() { 21 const worker = fork(this.workerScript); 22 23 worker.on('message', (result) => { 24 worker.busy = false; 25 worker.currentTask?.resolve(result); 26 this.processQueue(); 27 }); 28 29 worker.on('error', (error) => { 30 worker.currentTask?.reject(error); 31 this.replaceWorker(worker); 32 }); 33 34 worker.on('exit', (code) => { 35 if (code !== 0) { 36 this.replaceWorker(worker); 37 } 38 }); 39 40 worker.busy = false; 41 this.workers.push(worker); 42 } 43 44 replaceWorker(deadWorker) { 45 const index = this.workers.indexOf(deadWorker); 46 if (index !== -1) { 47 this.workers.splice(index, 1); 48 this.createWorker(); 49 } 50 } 51 52 async execute(task) { 53 return new Promise((resolve, reject) => { 54 this.queue.push({ task, resolve, reject }); 55 this.processQueue(); 56 }); 57 } 58 59 processQueue() { 60 if (this.queue.length === 0) return; 61 62 const availableWorker = this.workers.find((w) => !w.busy); 63 if (!availableWorker) return; 64 65 const { task, resolve, reject } = this.queue.shift(); 66 67 availableWorker.busy = true; 68 availableWorker.currentTask = { resolve, reject }; 69 availableWorker.send(task); 70 } 71 72 async shutdown() { 73 await Promise.all( 74 this.workers.map( 75 (worker) => 76 new Promise((resolve) => { 77 worker.once('exit', resolve); 78 worker.kill('SIGTERM'); 79 }) 80 ) 81 ); 82 } 83} 84 85// Usage 86const pool = new WorkerPool('./worker.js', 4); 87 88const results = await Promise.all([ 89 pool.execute({ type: 'compute', data: 1 }), 90 pool.execute({ type: 'compute', data: 2 }), 91 pool.execute({ type: 'compute', data: 3 }), 92]);

Error Handling#

1// Uncaught exceptions 2process.on('uncaughtException', (error) => { 3 console.error('Uncaught Exception:', error); 4 // Log error, clean up, then exit 5 process.exit(1); 6}); 7 8// Unhandled promise rejections 9process.on('unhandledRejection', (reason, promise) => { 10 console.error('Unhandled Rejection:', reason); 11 // In Node.js 15+, this will crash the process by default 12}); 13 14// Better: Use a proper error handler 15process.on('uncaughtException', async (error) => { 16 console.error('Uncaught Exception:', error); 17 18 try { 19 // Log to error monitoring service 20 await logToService(error); 21 22 // Graceful shutdown 23 await gracefulShutdown(); 24 } catch (shutdownError) { 25 console.error('Shutdown error:', shutdownError); 26 } 27 28 process.exit(1); 29}); 30 31// Warning events 32process.on('warning', (warning) => { 33 console.warn('Warning:', warning.name, warning.message); 34});

Exit Codes#

1// Common exit codes 2// 0 - Success 3// 1 - General error 4// 2 - Misuse of command 5// 126 - Command not executable 6// 127 - Command not found 7// 128+N - Fatal signal N 8 9// Exit with code 10process.exit(0); // Success 11process.exit(1); // Error 12 13// Set exit code without exiting 14process.exitCode = 1; 15 16// Exit event 17process.on('exit', (code) => { 18 console.log(`Exiting with code ${code}`); 19 // Only synchronous operations work here 20}); 21 22// Before exit (can be async) 23process.on('beforeExit', async (code) => { 24 console.log(`Before exit with code ${code}`); 25 // Can do async operations 26 await cleanup(); 27});

Health Checks#

1const http = require('http'); 2 3class HealthChecker { 4 constructor() { 5 this.checks = new Map(); 6 } 7 8 addCheck(name, checkFn) { 9 this.checks.set(name, checkFn); 10 } 11 12 async runChecks() { 13 const results = {}; 14 let healthy = true; 15 16 for (const [name, checkFn] of this.checks) { 17 try { 18 await checkFn(); 19 results[name] = { status: 'healthy' }; 20 } catch (error) { 21 results[name] = { status: 'unhealthy', error: error.message }; 22 healthy = false; 23 } 24 } 25 26 return { healthy, checks: results }; 27 } 28} 29 30// Usage 31const health = new HealthChecker(); 32 33health.addCheck('database', async () => { 34 await db.query('SELECT 1'); 35}); 36 37health.addCheck('redis', async () => { 38 await redis.ping(); 39}); 40 41health.addCheck('memory', async () => { 42 const used = process.memoryUsage(); 43 if (used.heapUsed > 500 * 1024 * 1024) { 44 throw new Error('Memory usage too high'); 45 } 46}); 47 48// Health endpoint 49app.get('/health', async (req, res) => { 50 const result = await health.runChecks(); 51 res.status(result.healthy ? 200 : 503).json(result); 52});

Best Practices#

Signals: ✓ Handle SIGTERM for graceful shutdown ✓ Handle SIGINT for development ✓ Set timeout for forced shutdown ✓ Log shutdown progress Error Handling: ✓ Catch uncaught exceptions ✓ Handle unhandled rejections ✓ Exit after uncaught exceptions ✓ Use proper exit codes Production: ✓ Implement health checks ✓ Monitor memory usage ✓ Use process managers (PM2) ✓ Log to external service

Conclusion#

Proper process management ensures reliable Node.js applications. Handle signals for graceful shutdown, manage child processes carefully, and always have proper error handling. Use health checks and monitoring for production visibility.

Share this article

Help spread the word about Bootspring