Problem
getOrchestrationHistory() in packages/durabletask-js/src/client/client.ts (line ~955) calls stream.removeAllListeners() when the gRPC server stream ends or errors, but does not call stream.destroy() or register a no-op error handler afterward.
This creates two risks:
-
Resource leak: The underlying gRPC HTTP/2 stream is never explicitly released. Node.js may eventually garbage-collect it, but during high-throughput scenarios (e.g., polling many orchestration histories), leaked streams can exhaust file descriptors or memory.
-
Unhandled error crash: After removeAllListeners(), if the gRPC transport emits a late "error" event (e.g., connection reset during teardown), there is no handler registered. Node.js treats unhandled "error" events as fatal, crashing the process.
Root Cause
The cleanup pattern in the "end" and "error" handlers of getOrchestrationHistory() is incomplete. It removes listeners but does not destroy the stream or guard against late errors.
Correct Pattern (Already Used in Worker)
task-hub-grpc-worker.ts (lines 388-395, 415-417) uses the correct three-step cleanup:
stream.removeAllListeners();
stream.on("error", () => {}); // Guard against late errors
stream.destroy(); // Release resources
This pattern was missed in the client's streaming method.
Proposed Fix
Add stream.on("error", () => {}) and stream.destroy() after stream.removeAllListeners() in both the "end" and "error" handlers of getOrchestrationHistory(), matching the worker's established pattern.
Impact
- Severity: Medium — resource leak under normal operation; potential process crash under network instability
- Affected scenarios: Any code calling
client.getOrchestrationHistory(), including the export-history package and user applications that inspect orchestration history
- Likelihood: Low for the crash (requires specific timing of late gRPC errors), moderate for the resource leak (every call leaks until GC)
Problem
getOrchestrationHistory()inpackages/durabletask-js/src/client/client.ts(line ~955) callsstream.removeAllListeners()when the gRPC server stream ends or errors, but does not callstream.destroy()or register a no-op error handler afterward.This creates two risks:
Resource leak: The underlying gRPC HTTP/2 stream is never explicitly released. Node.js may eventually garbage-collect it, but during high-throughput scenarios (e.g., polling many orchestration histories), leaked streams can exhaust file descriptors or memory.
Unhandled error crash: After
removeAllListeners(), if the gRPC transport emits a late"error"event (e.g., connection reset during teardown), there is no handler registered. Node.js treats unhandled"error"events as fatal, crashing the process.Root Cause
The cleanup pattern in the
"end"and"error"handlers ofgetOrchestrationHistory()is incomplete. It removes listeners but does not destroy the stream or guard against late errors.Correct Pattern (Already Used in Worker)
task-hub-grpc-worker.ts(lines 388-395, 415-417) uses the correct three-step cleanup:This pattern was missed in the client's streaming method.
Proposed Fix
Add
stream.on("error", () => {})andstream.destroy()afterstream.removeAllListeners()in both the"end"and"error"handlers ofgetOrchestrationHistory(), matching the worker's established pattern.Impact
client.getOrchestrationHistory(), including the export-history package and user applications that inspect orchestration history