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
  • Loading branch information
Linkgoron committed Mar 26, 2021
1 parent a9cdeed commit e165f34
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 2 deletions.
2 changes: 2 additions & 0 deletions doc/api/readline.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,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
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const {
ERR_INVALID_CURSOR_POS,
} = codes;
const {
validateAbortSignal,
validateArray,
validateCallback,
validateString,
Expand Down Expand Up @@ -150,14 +151,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 @@ -176,6 +178,11 @@ function Interface(input, output, completer, terminal) {
);
}
}

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

crlfDelay = input.crlfDelay;
input = input.input;
}
Expand Down Expand Up @@ -320,6 +327,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
Original file line number Diff line number Diff line change
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 @@ -1132,3 +1132,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 e165f34

Please sign in to comment.