Skip to content

Commit 526e6c7

Browse files
Linkgorontargos
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 f3d2e6a commit 526e6c7

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: REPLACEME
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
@@ -75,6 +75,7 @@ const {
7575
ERR_INVALID_OPT_VALUE,
7676
} = codes;
7777
const {
78+
validateAbortSignal,
7879
validateArray,
7980
validateString,
8081
validateUint32,
@@ -149,14 +150,15 @@ function Interface(input, output, completer, terminal) {
149150
let removeHistoryDuplicates = false;
150151
let crlfDelay;
151152
let prompt = '> ';
152-
153+
let signal;
153154
if (input && input.input) {
154155
// An options object was given
155156
output = input.output;
156157
completer = input.completer;
157158
terminal = input.terminal;
158159
history = input.history;
159160
historySize = input.historySize;
161+
signal = input.signal;
160162
if (input.tabSize !== undefined) {
161163
validateUint32(input.tabSize, 'tabSize', true);
162164
this.tabSize = input.tabSize;
@@ -175,6 +177,11 @@ function Interface(input, output, completer, terminal) {
175177
);
176178
}
177179
}
180+
181+
if (signal) {
182+
validateAbortSignal(signal, 'options.signal');
183+
}
184+
178185
crlfDelay = input.crlfDelay;
179186
input = input.input;
180187
}
@@ -311,6 +318,16 @@ function Interface(input, output, completer, terminal) {
311318
self.once('close', onSelfCloseWithTerminal);
312319
}
313320

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

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 {
@@ -1144,3 +1144,58 @@ for (let i = 0; i < 12; i++) {
11441144
rl.line = `a${' '.repeat(1e6)}a`;
11451145
rl.cursor = rl.line.length;
11461146
}
1147+
1148+
{
1149+
const fi = new FakeInput();
1150+
const signal = AbortSignal.abort();
1151+
1152+
const rl = readline.createInterface({
1153+
input: fi,
1154+
output: fi,
1155+
signal,
1156+
});
1157+
rl.on('close', common.mustCall());
1158+
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
1159+
}
1160+
1161+
{
1162+
const fi = new FakeInput();
1163+
const ac = new AbortController();
1164+
const { signal } = ac;
1165+
const rl = readline.createInterface({
1166+
input: fi,
1167+
output: fi,
1168+
signal,
1169+
});
1170+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
1171+
rl.on('close', common.mustCall());
1172+
ac.abort();
1173+
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
1174+
}
1175+
1176+
{
1177+
const fi = new FakeInput();
1178+
const ac = new AbortController();
1179+
const { signal } = ac;
1180+
const rl = readline.createInterface({
1181+
input: fi,
1182+
output: fi,
1183+
signal,
1184+
});
1185+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
1186+
rl.close();
1187+
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
1188+
}
1189+
1190+
{
1191+
// Constructor throws if signal is not an abort signal
1192+
assert.throws(() => {
1193+
readline.createInterface({
1194+
input: new FakeInput(),
1195+
signal: {},
1196+
});
1197+
}, {
1198+
name: 'TypeError',
1199+
code: 'ERR_INVALID_ARG_TYPE'
1200+
});
1201+
}

0 commit comments

Comments
 (0)