Skip to content

Commit a1123f0

Browse files
LinkgoronMylesBorins
authored andcommitted
readline: add AbortSignal support to interface
Add abort signal support to Interface PR-URL: #37932 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 669b81c commit a1123f0

File tree

3 files changed

+79
-2
lines changed

3 files changed

+79
-2
lines changed

doc/api/readline.md

+5
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,9 @@ the current position of the cursor down.
544544
<!-- YAML
545545
added: v0.1.98
546546
changes:
547+
- version: REPLACEME
548+
pr-url: https://github.com/nodejs/node/pull/37932
549+
description: The `signal` option is supported now.
547550
- version: v15.8.0
548551
pr-url: https://github.com/nodejs/node/pull/33662
549552
description: The `history` option is supported now.
@@ -601,6 +604,8 @@ changes:
601604
**Default:** `500`.
602605
* `tabSize` {integer} The number of spaces a tab is equal to (minimum 1).
603606
**Default:** `8`.
607+
* `signal` {AbortSignal} Allows closing the interface using an AbortSignal.
608+
Aborting the signal will internally call `close` on the interface.
604609
* Returns: {readline.Interface}
605610

606611
The `readline.createInterface()` method creates a new `readline.Interface`

lib/readline.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const {
7474
ERR_INVALID_CURSOR_POS,
7575
} = codes;
7676
const {
77+
validateAbortSignal,
7778
validateArray,
7879
validateCallback,
7980
validateString,
@@ -150,14 +151,15 @@ function Interface(input, output, completer, terminal) {
150151
let removeHistoryDuplicates = false;
151152
let crlfDelay;
152153
let prompt = '> ';
153-
154+
let signal;
154155
if (input && input.input) {
155156
// An options object was given
156157
output = input.output;
157158
completer = input.completer;
158159
terminal = input.terminal;
159160
history = input.history;
160161
historySize = input.historySize;
162+
signal = input.signal;
161163
if (input.tabSize !== undefined) {
162164
validateUint32(input.tabSize, 'tabSize', true);
163165
this.tabSize = input.tabSize;
@@ -176,6 +178,11 @@ function Interface(input, output, completer, terminal) {
176178
);
177179
}
178180
}
181+
182+
if (signal) {
183+
validateAbortSignal(signal, 'options.signal');
184+
}
185+
179186
crlfDelay = input.crlfDelay;
180187
input = input.input;
181188
}
@@ -312,6 +319,16 @@ function Interface(input, output, completer, terminal) {
312319
self.once('close', onSelfCloseWithTerminal);
313320
}
314321

322+
if (signal) {
323+
const onAborted = () => self.close();
324+
if (signal.aborted) {
325+
process.nextTick(onAborted);
326+
} else {
327+
signal.addEventListener('abort', onAborted, { once: true });
328+
self.once('close', () => signal.removeEventListener('abort', onAborted));
329+
}
330+
}
331+
315332
// Current line
316333
this.line = '';
317334

test/parallel/test-readline-interface.js

+56-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const {
3131
getStringWidth,
3232
stripVTControlCharacters
3333
} = require('internal/util/inspect');
34-
const EventEmitter = require('events').EventEmitter;
34+
const { EventEmitter, getEventListeners } = require('events');
3535
const { Writable, Readable } = require('stream');
3636

3737
class FakeInput extends EventEmitter {
@@ -1132,3 +1132,58 @@ for (let i = 0; i < 12; i++) {
11321132
rl.line = `a${' '.repeat(1e6)}a`;
11331133
rl.cursor = rl.line.length;
11341134
}
1135+
1136+
{
1137+
const fi = new FakeInput();
1138+
const signal = AbortSignal.abort();
1139+
1140+
const rl = readline.createInterface({
1141+
input: fi,
1142+
output: fi,
1143+
signal,
1144+
});
1145+
rl.on('close', common.mustCall());
1146+
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
1147+
}
1148+
1149+
{
1150+
const fi = new FakeInput();
1151+
const ac = new AbortController();
1152+
const { signal } = ac;
1153+
const rl = readline.createInterface({
1154+
input: fi,
1155+
output: fi,
1156+
signal,
1157+
});
1158+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
1159+
rl.on('close', common.mustCall());
1160+
ac.abort();
1161+
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
1162+
}
1163+
1164+
{
1165+
const fi = new FakeInput();
1166+
const ac = new AbortController();
1167+
const { signal } = ac;
1168+
const rl = readline.createInterface({
1169+
input: fi,
1170+
output: fi,
1171+
signal,
1172+
});
1173+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
1174+
rl.close();
1175+
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
1176+
}
1177+
1178+
{
1179+
// Constructor throws if signal is not an abort signal
1180+
assert.throws(() => {
1181+
readline.createInterface({
1182+
input: new FakeInput(),
1183+
signal: {},
1184+
});
1185+
}, {
1186+
name: 'TypeError',
1187+
code: 'ERR_INVALID_ARG_TYPE'
1188+
});
1189+
}

0 commit comments

Comments
 (0)