summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAnna Henningsen <anna@addaleax.net>2016-05-08 03:30:23 +0200
committerJeremiah Senkpiel <fishrock123@rocketmail.com>2016-07-05 22:10:23 +0200
commitda8e510ee065fe50ee9a63e1e9e20b2ba7d5a85a (patch)
tree305c27684030d9971c28e44ec981ffd42700c251 /lib
parent72d659a000917e848f49c81e63ee2b0524b73de0 (diff)
downloadnode-new-da8e510ee065fe50ee9a63e1e9e20b2ba7d5a85a.tar.gz
repl: break on sigint/ctrl+c
Adds the ability to stop execution of the current REPL command when receiving SIGINT. This applies only to the default eval function. Fixes: https://github.com/nodejs/node/issues/6612 PR-URL: https://github.com/nodejs/node/pull/6635 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Diffstat (limited to 'lib')
-rw-r--r--lib/internal/repl.js3
-rw-r--r--lib/repl.js51
2 files changed, 48 insertions, 6 deletions
diff --git a/lib/internal/repl.js b/lib/internal/repl.js
index b72741609b..dd14f42fa5 100644
--- a/lib/internal/repl.js
+++ b/lib/internal/repl.js
@@ -22,7 +22,8 @@ function createRepl(env, opts, cb) {
opts = opts || {
ignoreUndefined: false,
terminal: process.stdout.isTTY,
- useGlobal: true
+ useGlobal: true,
+ breakEvalOnSigint: true
};
if (parseInt(env.NODE_NO_READLINE)) {
diff --git a/lib/repl.js b/lib/repl.js
index 387e3b5446..db5754ec04 100644
--- a/lib/repl.js
+++ b/lib/repl.js
@@ -24,6 +24,7 @@
const internalModule = require('internal/module');
const internalUtil = require('internal/util');
const util = require('util');
+const utilBinding = process.binding('util');
const inherits = util.inherits;
const Stream = require('stream');
const vm = require('vm');
@@ -178,7 +179,7 @@ function REPLServer(prompt,
replMode);
}
- var options, input, output, dom;
+ var options, input, output, dom, breakEvalOnSigint;
if (prompt !== null && typeof prompt === 'object') {
// an options object was given
options = prompt;
@@ -191,10 +192,17 @@ function REPLServer(prompt,
prompt = options.prompt;
dom = options.domain;
replMode = options.replMode;
+ breakEvalOnSigint = options.breakEvalOnSigint;
} else {
options = {};
}
+ if (breakEvalOnSigint && eval_) {
+ // Allowing this would not reflect user expectations.
+ // breakEvalOnSigint affects only the behaviour of the default eval().
+ throw new Error('Cannot specify both breakEvalOnSigint and eval for REPL');
+ }
+
var self = this;
self._domain = dom || domain.create();
@@ -204,6 +212,7 @@ function REPLServer(prompt,
self.replMode = replMode || exports.REPL_MODE_SLOPPY;
self.underscoreAssigned = false;
self.last = undefined;
+ self.breakEvalOnSigint = !!breakEvalOnSigint;
self._inTemplateLiteral = false;
@@ -267,14 +276,46 @@ function REPLServer(prompt,
regExMatcher.test(savedRegExMatches.join(sep));
if (!err) {
+ // Unset raw mode during evaluation so that Ctrl+C raises a signal.
+ let previouslyInRawMode;
+ if (self.breakEvalOnSigint) {
+ // Start the SIGINT watchdog before entering raw mode so that a very
+ // quick Ctrl+C doesn’t lead to aborting the process completely.
+ utilBinding.startSigintWatchdog();
+ previouslyInRawMode = self._setRawMode(false);
+ }
+
try {
- if (self.useGlobal) {
- result = script.runInThisContext({ displayErrors: false });
- } else {
- result = script.runInContext(context, { displayErrors: false });
+ try {
+ const scriptOptions = {
+ displayErrors: false,
+ breakOnSigint: self.breakEvalOnSigint
+ };
+
+ if (self.useGlobal) {
+ result = script.runInThisContext(scriptOptions);
+ } else {
+ result = script.runInContext(context, scriptOptions);
+ }
+ } finally {
+ if (self.breakEvalOnSigint) {
+ // Reset terminal mode to its previous value.
+ self._setRawMode(previouslyInRawMode);
+
+ // Returns true if there were pending SIGINTs *after* the script
+ // has terminated without being interrupted itself.
+ if (utilBinding.stopSigintWatchdog()) {
+ self.emit('SIGINT');
+ }
+ }
}
} catch (e) {
err = e;
+ if (err.message === 'Script execution interrupted.') {
+ // The stack trace for this case is not very useful anyway.
+ Object.defineProperty(err, 'stack', { value: '' });
+ }
+
if (err && process.domain) {
debug('not recoverable, send to domain');
process.domain.emit('error', err);