This error occurs when attempting to register multiple PerformanceObserver instances for the same entry type in Node.js. Only one observer can monitor a specific entry type at a time, and duplicate registrations trigger an assertion failure that can crash the process.
This error indicates that Node.js is preventing you from creating multiple PerformanceObserver instances that observe the same entry type (such as 'measure', 'mark', 'gc', 'function', or 'http'). The perf_hooks module enforces a constraint where each performance entry type can only be observed by one active observer at a time. When you call observer.observe() with an entryTypes array, Node.js checks if any of those types are already being monitored by another observer. If a duplicate is detected, the error is thrown to prevent conflicting observers that could lead to data corruption or unexpected behavior. This was particularly problematic in Node.js 16.0.0 where duplicate registrations would kill the process entirely with an assertion failure, though the behavior has been refined in later versions to throw a proper error instead.
Maintain a registry of active observers to prevent duplicate registrations:
// Create a global registry for observers
const observerRegistry = new Map();
function getOrCreateObserver(entryType, callback) {
if (observerRegistry.has(entryType)) {
return observerRegistry.get(entryType);
}
const observer = new PerformanceObserver(callback);
observer.observe({ entryTypes: [entryType] });
observerRegistry.set(entryType, observer);
return observer;
}
// Usage
const gcObserver = getOrCreateObserver('gc', (list) => {
console.log('GC events:', list.getEntries());
});This pattern ensures only one observer exists per entry type.
If you need to replace an observer, properly disconnect the old one first:
const { PerformanceObserver } = require('perf_hooks');
// Store reference to current observer
let currentObserver = null;
function setupPerformanceMonitoring(callback) {
// Disconnect previous observer if it exists
if (currentObserver) {
currentObserver.disconnect();
}
// Create new observer
currentObserver = new PerformanceObserver(callback);
currentObserver.observe({ entryTypes: ['measure', 'mark'] });
}
// Later, when shutting down:
function cleanup() {
if (currentObserver) {
currentObserver.disconnect();
currentObserver = null;
}
}Always call disconnect() to properly clean up observers.
Instead of creating separate observers, observe multiple entry types with one observer:
const { PerformanceObserver } = require('perf_hooks');
// Single observer for all entry types
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
switch (entry.entryType) {
case 'measure':
console.log('Measure:', entry.name, entry.duration);
break;
case 'mark':
console.log('Mark:', entry.name);
break;
case 'gc':
console.log('GC event:', entry.kind);
break;
}
});
});
// Observe multiple types at once
observer.observe({
entryTypes: ['measure', 'mark', 'gc']
});This approach eliminates the possibility of duplicate registrations.
If using observers in a module, ensure they're created only once:
// performance-monitor.js
const { PerformanceObserver } = require('perf_hooks');
let observerInstance = null;
function initializeMonitoring() {
if (observerInstance) {
console.log('Performance monitoring already initialized');
return observerInstance;
}
observerInstance = new PerformanceObserver((list) => {
// Handle performance entries
const entries = list.getEntries();
entries.forEach((entry) => {
console.log(`[${entry.entryType}] ${entry.name}`);
});
});
observerInstance.observe({
entryTypes: ['measure', 'mark', 'function', 'http']
});
console.log('Performance monitoring initialized');
return observerInstance;
}
function shutdown() {
if (observerInstance) {
observerInstance.disconnect();
observerInstance = null;
}
}
module.exports = { initializeMonitoring, shutdown };Call initializeMonitoring() once at application startup.
In development environments with hot reloading, clean up observers properly:
// performance-tracker.js
const { PerformanceObserver } = require('perf_hooks');
let observer = null;
function setupObserver() {
if (observer) {
observer.disconnect();
}
observer = new PerformanceObserver((list) => {
// Handle entries
});
observer.observe({ entryTypes: ['measure'] });
}
// Clean up on hot reload (for webpack/vite)
if (module.hot) {
module.hot.dispose(() => {
if (observer) {
observer.disconnect();
observer = null;
}
});
}
setupObserver();
module.exports = { observer };This prevents observers from accumulating during development.
Version-specific behavior: In Node.js 16.0.0, duplicate observer registrations would trigger an assertion failure and immediately kill the process. Later versions (16.1.0+) improved this to throw a proper error instead, making it easier to catch and handle gracefully.
Entry type limitations: Each entry type ('measure', 'mark', 'gc', 'function', 'http', 'dns', 'net') can only have one active observer. This is a design decision to prevent performance overhead and conflicting callbacks.
Third-party conflicts: If using monitoring libraries (like APM tools or performance profilers), they may create their own PerformanceObserver instances. Check library documentation for configuration options to disable internal observers if you need to create your own.
Buffered vs. non-buffered: When using observe({ buffered: true }), you can retrieve historical entries, but this doesn't bypass the single-observer-per-type restriction. The buffered flag affects what entries are delivered, not how many observers can exist.
Process exit hooks: Always implement proper cleanup in process exit handlers to disconnect observers:
process.on('SIGTERM', () => {
if (observer) observer.disconnect();
process.exit(0);
});
process.on('SIGINT', () => {
if (observer) observer.disconnect();
process.exit(0);
});Testing considerations: In test environments where tests run in parallel or sequentially in the same process, ensure observers are created and destroyed properly between test runs to avoid conflicts.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams