Manage async operation lifecycle

When working with async operations, carefully manage execution context and resource references across await boundaries to prevent context corruption and resource leaks.

copy reviewer prompt

Prompt

Reviewer Prompt

When working with async operations, carefully manage execution context and resource references across await boundaries to prevent context corruption and resource leaks.

For execution context, avoid patterns where context enters/exits span async boundaries, as context may not be properly restored after await points. Instead, structure code to contain async operations within the context scope:

// Problematic - context lost across await
if (TRACING_ENABLED) {
  span = builtinTracer().startSpan(this.method, { kind: 2 });
  context = enterSpan(span);
  // await operations here lose context
}

// Better - use IIFE to contain async operations
const old = getAsyncContext();
try {
  setAsyncContext('inside operation');
  return (async () => {
    await asyncOperation();
    // context preserved here
  })();
} finally {
  setAsyncContext(old);
}

For resource management, distinguish between ongoing and future async operations when managing references. Use unref/ref patterns to handle current operations without affecting future ones:

// Unref ongoing reads but not future reads
if (this.unref) {
  this.unref();  // Clear current operation references
  this.ref();    // Re-establish for future operations
}

This prevents resource leaks while maintaining proper reference counting for subsequent async operations.

Source discussions