Skip to content

Commit

Permalink
readline: add AbortSignal support to interface
Browse files Browse the repository at this point in the history
Add abort signal support to Interface

PR-URL: #37932
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
Linkgoron authored and targos committed Sep 4, 2021
1 parent f3d2e6a commit 526e6c7
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 2 deletions.
5 changes: 5 additions & 0 deletions doc/api/readline.md
Expand Up @@ -544,6 +544,9 @@ the current position of the cursor down.
<!-- YAML
added: v0.1.98
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37932
description: The `signal` option is supported now.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/33662
description: The `history` option is supported now.
Expand Down Expand Up @@ -601,6 +604,8 @@ changes:
**Default:** `500`.
* `tabSize` {integer} The number of spaces a tab is equal to (minimum 1).
**Default:** `8`.
* `signal` {AbortSignal} Allows closing the interface using an AbortSignal.
Aborting the signal will internally call `close` on the interface.
* Returns: {readline.Interface}

The `readline.createInterface()` method creates a new `readline.Interface`
Expand Down
19 changes: 18 additions & 1 deletion lib/readline.js
Expand Up @@ -75,6 +75,7 @@ const {
ERR_INVALID_OPT_VALUE,
} = codes;
const {
validateAbortSignal,
validateArray,
validateString,
validateUint32,
Expand Down Expand Up @@ -149,14 +150,15 @@ function Interface(input, output, completer, terminal) {
let removeHistoryDuplicates = false;
let crlfDelay;
let prompt = '> ';

let signal;
if (input && input.input) {
// An options object was given
output = input.output;
completer = input.completer;
terminal = input.terminal;
history = input.history;
historySize = input.historySize;
signal = input.signal;
if (input.tabSize !== undefined) {
validateUint32(input.tabSize, 'tabSize', true);
this.tabSize = input.tabSize;
Expand All @@ -175,6 +177,11 @@ function Interface(input, output, completer, terminal) {
);
}
}

if (signal) {
validateAbortSignal(signal, 'options.signal');
}

crlfDelay = input.crlfDelay;
input = input.input;
}
Expand Down Expand Up @@ -311,6 +318,16 @@ function Interface(input, output, completer, terminal) {
self.once('close', onSelfCloseWithTerminal);
}

if (signal) {
const onAborted = () => self.close();
if (signal.aborted) {
process.nextTick(onAborted);
} else {
signal.addEventListener('abort', onAborted, { once: true });
self.once('close', () => signal.removeEventListener('abort', onAborted));
}
}

// Current line
this.line = '';

Expand Down
57 changes: 56 additions & 1 deletion test/parallel/test-readline-interface.js
Expand Up @@ -31,7 +31,7 @@ const {
getStringWidth,
stripVTControlCharacters
} = require('internal/util/inspect');
const EventEmitter = require('events').EventEmitter;
const { EventEmitter, getEventListeners } = require('events');
const { Writable, Readable } = require('stream');

class FakeInput extends EventEmitter {
Expand Down Expand Up @@ -1144,3 +1144,58 @@ for (let i = 0; i < 12; i++) {
rl.line = `a${' '.repeat(1e6)}a`;
rl.cursor = rl.line.length;
}

{
const fi = new FakeInput();
const signal = AbortSignal.abort();

const rl = readline.createInterface({
input: fi,
output: fi,
signal,
});
rl.on('close', common.mustCall());
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
}

{
const fi = new FakeInput();
const ac = new AbortController();
const { signal } = ac;
const rl = readline.createInterface({
input: fi,
output: fi,
signal,
});
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
rl.on('close', common.mustCall());
ac.abort();
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
}

{
const fi = new FakeInput();
const ac = new AbortController();
const { signal } = ac;
const rl = readline.createInterface({
input: fi,
output: fi,
signal,
});
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
rl.close();
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
}

{
// Constructor throws if signal is not an abort signal
assert.throws(() => {
readline.createInterface({
input: new FakeInput(),
signal: {},
});
}, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
});
}

0 comments on commit 526e6c7

Please sign in to comment.