From d715296dfa4a97e7a4c1835a276f434d62a610aa Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 1 Feb 2026 15:10:38 +0100 Subject: [PATCH] repl: fix flaky test-repl-programmatic-history The test was flaky because the FileHandle for the history file could be garbage collected before being explicitly closed, causing ERR_INVALID_STATE errors when the handle was already closed by the GC finalizer. This fix adds an explicit closeHandle() method to ReplHistory and ensures that REPLServer.close() waits for the history file handle to be properly closed before completing the close operation. Refs: https://github.com/nodejs/reliability/issues/1450 --- lib/internal/repl/history.js | 17 ++++++++++++++++- lib/repl.js | 10 ++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/internal/repl/history.js b/lib/internal/repl/history.js index d05bb19d733a22..ed63ba01cb1be1 100644 --- a/lib/internal/repl/history.js +++ b/lib/internal/repl/history.js @@ -58,6 +58,7 @@ const kResolveHistoryPath = Symbol('_kResolveHistoryPath'); const kReplHistoryMessage = Symbol('_kReplHistoryMessage'); const kFlushHistory = Symbol('_kFlushHistory'); const kGetHistoryPath = Symbol('_kGetHistoryPath'); +const kCloseHandle = Symbol('_kCloseHandle'); class ReplHistory { constructor(context, options) { @@ -393,15 +394,29 @@ class ReplHistory { } this[kContext].off('line', this[kOnLine].bind(this)); + await this[kCloseHandle](); + } + + async [kCloseHandle]() { if (this[kHistoryHandle] !== null) { + const handle = this[kHistoryHandle]; + this[kHistoryHandle] = null; try { - await this[kHistoryHandle].close(); + await handle.close(); } catch (err) { debug('Error closing history file:', err); } } } + /** + * Closes the history file handle. + * @returns {Promise} + */ + closeHandle() { + return this[kCloseHandle](); + } + [kReplHistoryMessage]() { if (this[kHistory].length === 0) { ReplHistory[kWriteToOutput]( diff --git a/lib/repl.js b/lib/repl.js index 5ad9e4fbb1506f..878d1bb92244ab 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1039,12 +1039,18 @@ class REPLServer extends Interface { this[kBufferedCommandSymbol] = ''; } close() { - if (this.terminal && this.historyManager.isFlushing && !this._closingOnFlush) { + if (this.terminal && this.historyManager?.isFlushing && !this._closingOnFlush) { this._closingOnFlush = true; - this.once('flushHistory', () => super.close()); + this.once('flushHistory', () => this.close()); return; } + // Ensure the history file handle is closed before completing + if (this.terminal && this.historyManager?.closeHandle && !this._historyHandleClosed) { + this._historyHandleClosed = true; + this.historyManager.closeHandle().then(() => super.close()); + return; + } process.nextTick(() => super.close()); } createContext() {