| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 72d659a commit da8e510
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -390,6 +390,9 @@ added: v0.1.91 | |||
| 390 | 390 | equivalent to prefacing every repl statement with `'use strict'`. | |
| 391 | 391 | * `repl.REPL_MODE_MAGIC` - attempt to evaluates expressions in default | |
| 392 | 392 | mode. If expressions fail to parse, re-try in strict mode. | |
| 393 | + * `breakEvalOnSigint` - Stop evaluating the current piece of code when | ||
| 394 | + `SIGINT` is received, i.e. `Ctrl+C` is pressed. This cannot be used together | ||
| 395 | + with a custom `eval` function. Defaults to `false`. | ||
| 393 | 396 | ||
| 394 | 397 | The `repl.start()` method creates and starts a `repl.REPLServer` instance. | |
| 395 | 398 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,7 +22,8 @@ function createRepl(env, opts, cb) { | |||
| 22 | 22 | opts = opts || { | |
| 23 | 23 | ignoreUndefined: false, | |
| 24 | 24 | terminal: process.stdout.isTTY, | |
| 25 | - useGlobal: true | ||
| 25 | + useGlobal: true, | ||
| 26 | + breakEvalOnSigint: true | ||
| 26 | 27 | }; | |
| 27 | 28 | ||
| 28 | 29 | if (parseInt(env.NODE_NO_READLINE)) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,6 +24,7 @@ | |||
| 24 | 24 | const internalModule = require('internal/module'); | |
| 25 | 25 | const internalUtil = require('internal/util'); | |
| 26 | 26 | const util = require('util'); | |
| 27 | + const utilBinding = process.binding('util'); | ||
| 27 | 28 | const inherits = util.inherits; | |
| 28 | 29 | const Stream = require('stream'); | |
| 29 | 30 | const vm = require('vm'); | |
@@ -178,7 +179,7 @@ function REPLServer(prompt, | |||
| 178 | 179 | replMode); | |
| 179 | 180 | } | |
| 180 | 181 | ||
| 181 | - var options, input, output, dom; | ||
| 182 | + var options, input, output, dom, breakEvalOnSigint; | ||
| 182 | 183 | if (prompt !== null && typeof prompt === 'object') { | |
| 183 | 184 | // an options object was given | |
| 184 | 185 | options = prompt; | |
@@ -191,10 +192,17 @@ function REPLServer(prompt, | |||
| 191 | 192 | prompt = options.prompt; | |
| 192 | 193 | dom = options.domain; | |
| 193 | 194 | replMode = options.replMode; | |
| 195 | + breakEvalOnSigint = options.breakEvalOnSigint; | ||
| 194 | 196 | } else { | |
| 195 | 197 | options = {}; | |
| 196 | 198 | } | |
| 197 | 199 | ||
| 200 | + if (breakEvalOnSigint && eval_) { | ||
| 201 | + // Allowing this would not reflect user expectations. | ||
| 202 | + // breakEvalOnSigint affects only the behaviour of the default eval(). | ||
| 203 | + throw new Error('Cannot specify both breakEvalOnSigint and eval for REPL'); | ||
| 204 | + } | ||
| 205 | + | ||
| 198 | 206 | var self = this; | |
| 199 | 207 | ||
| 200 | 208 | self._domain = dom || domain.create(); | |
@@ -204,6 +212,7 @@ function REPLServer(prompt, | |||
| 204 | 212 | self.replMode = replMode || exports.REPL_MODE_SLOPPY; | |
| 205 | 213 | self.underscoreAssigned = false; | |
| 206 | 214 | self.last = undefined; | |
| 215 | + self.breakEvalOnSigint = !!breakEvalOnSigint; | ||
| 207 | 216 | ||
| 208 | 217 | self._inTemplateLiteral = false; | |
| 209 | 218 | ||
@@ -267,14 +276,46 @@ function REPLServer(prompt, | |||
| 267 | 276 | regExMatcher.test(savedRegExMatches.join(sep)); | |
| 268 | 277 | ||
| 269 | 278 | if (!err) { | |
| 279 | + // Unset raw mode during evaluation so that Ctrl+C raises a signal. | ||
| 280 | + let previouslyInRawMode; | ||
| 281 | + if (self.breakEvalOnSigint) { | ||
| 282 | + // Start the SIGINT watchdog before entering raw mode so that a very | ||
| 283 | + // quick Ctrl+C doesn’t lead to aborting the process completely. | ||
| 284 | + utilBinding.startSigintWatchdog(); | ||
| 285 | + previouslyInRawMode = self._setRawMode(false); | ||
| 286 | + } | ||
| 287 | + | ||
| 270 | 288 | try { | |
| 271 | - if (self.useGlobal) { | ||
| 272 | - result = script.runInThisContext({ displayErrors: false }); | ||
| 273 | - } else { | ||
| 274 | - result = script.runInContext(context, { displayErrors: false }); | ||
| 289 | + try { | ||
| 290 | + const scriptOptions = { | ||
| 291 | + displayErrors: false, | ||
| 292 | + breakOnSigint: self.breakEvalOnSigint | ||
| 293 | + }; | ||
| 294 | + | ||
| 295 | + if (self.useGlobal) { | ||
| 296 | + result = script.runInThisContext(scriptOptions); | ||
| 297 | + } else { | ||
| 298 | + result = script.runInContext(context, scriptOptions); | ||
| 299 | + } | ||
| 300 | + } finally { | ||
| 301 | + if (self.breakEvalOnSigint) { | ||
| 302 | + // Reset terminal mode to its previous value. | ||
| 303 | + self._setRawMode(previouslyInRawMode); | ||
| 304 | + | ||
| 305 | + // Returns true if there were pending SIGINTs *after* the script | ||
| 306 | + // has terminated without being interrupted itself. | ||
| 307 | + if (utilBinding.stopSigintWatchdog()) { | ||
| 308 | + self.emit('SIGINT'); | ||
| 309 | + } | ||
| 310 | + } | ||
| 275 | 311 | } | |
| 276 | 312 | } catch (e) { | |
| 277 | 313 | err = e; | |
| 314 | + if (err.message === 'Script execution interrupted.') { | ||
| 315 | + // The stack trace for this case is not very useful anyway. | ||
| 316 | + Object.defineProperty(err, 'stack', { value: '' }); | ||
| 317 | + } | ||
| 318 | + | ||
| 278 | 319 | if (err && process.domain) { | |
| 279 | 320 | debug('not recoverable, send to domain'); | |
| 280 | 321 | process.domain.emit('error', err); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,50 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('assert'); | ||
| 4 | + | ||
| 5 | + const spawn = require('child_process').spawn; | ||
| 6 | + | ||
| 7 | + if (process.platform === 'win32') { | ||
| 8 | + // No way to send CTRL_C_EVENT to processes from JS right now. | ||
| 9 | + common.skip('platform not supported'); | ||
| 10 | + return; | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | + process.env.REPL_TEST_PPID = process.pid; | ||
| 14 | + const child = spawn(process.execPath, [ '-i' ], { | ||
| 15 | + stdio: [null, null, 2] | ||
| 16 | + }); | ||
| 17 | + | ||
| 18 | + let stdout = ''; | ||
| 19 | + child.stdout.setEncoding('utf8'); | ||
| 20 | + child.stdout.pipe(process.stdout); | ||
| 21 | + child.stdout.on('data', function(c) { | ||
| 22 | + stdout += c; | ||
| 23 | + }); | ||
| 24 | + | ||
| 25 | + child.stdin.write = ((original) => { | ||
| 26 | + return (chunk) => { | ||
| 27 | + process.stderr.write(chunk); | ||
| 28 | + return original.call(child.stdin, chunk); | ||
| 29 | + }; | ||
| 30 | + })(child.stdin.write); | ||
| 31 | + | ||
| 32 | + child.stdout.once('data', common.mustCall(() => { | ||
| 33 | + process.on('SIGUSR2', common.mustCall(() => { | ||
| 34 | + process.kill(child.pid, 'SIGINT'); | ||
| 35 | + child.stdout.once('data', common.mustCall(() => { | ||
| 36 | + // Make sure REPL still works. | ||
| 37 | + child.stdin.end('"foobar"\n'); | ||
| 38 | + })); | ||
| 39 | + })); | ||
| 40 | + | ||
| 41 | + child.stdin.write('process.kill(+process.env.REPL_TEST_PPID, "SIGUSR2");' + | ||
| 42 | + 'vm.runInThisContext("while(true){}", ' + | ||
| 43 | + '{ breakOnSigint: true });\n'); | ||
| 44 | + })); | ||
| 45 | + | ||
| 46 | + child.on('close', function(code) { | ||
| 47 | + assert.strictEqual(code, 0); | ||
| 48 | + assert.notStrictEqual(stdout.indexOf('Script execution interrupted.'), -1); | ||
| 49 | + assert.notStrictEqual(stdout.indexOf('foobar'), -1); | ||
| 50 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,50 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('assert'); | ||
| 4 | + | ||
| 5 | + const spawn = require('child_process').spawn; | ||
| 6 | + | ||
| 7 | + if (process.platform === 'win32') { | ||
| 8 | + // No way to send CTRL_C_EVENT to processes from JS right now. | ||
| 9 | + common.skip('platform not supported'); | ||
| 10 | + return; | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | + process.env.REPL_TEST_PPID = process.pid; | ||
| 14 | + const child = spawn(process.execPath, [ '-i' ], { | ||
| 15 | + stdio: [null, null, 2] | ||
| 16 | + }); | ||
| 17 | + | ||
| 18 | + let stdout = ''; | ||
| 19 | + child.stdout.setEncoding('utf8'); | ||
| 20 | + child.stdout.pipe(process.stdout); | ||
| 21 | + child.stdout.on('data', function(c) { | ||
| 22 | + stdout += c; | ||
| 23 | + }); | ||
| 24 | + | ||
| 25 | + child.stdin.write = ((original) => { | ||
| 26 | + return (chunk) => { | ||
| 27 | + process.stderr.write(chunk); | ||
| 28 | + return original.call(child.stdin, chunk); | ||
| 29 | + }; | ||
| 30 | + })(child.stdin.write); | ||
| 31 | + | ||
| 32 | + child.stdout.once('data', common.mustCall(() => { | ||
| 33 | + process.on('SIGUSR2', common.mustCall(() => { | ||
| 34 | + process.kill(child.pid, 'SIGINT'); | ||
| 35 | + child.stdout.once('data', common.mustCall(() => { | ||
| 36 | + // Make sure state from before the interruption is still available. | ||
| 37 | + child.stdin.end('a*2*3*7\n'); | ||
| 38 | + })); | ||
| 39 | + })); | ||
| 40 | + | ||
| 41 | + child.stdin.write('a = 1001;' + | ||
| 42 | + 'process.kill(+process.env.REPL_TEST_PPID, "SIGUSR2");' + | ||
| 43 | + 'while(true){}\n'); | ||
| 44 | + })); | ||
| 45 | + | ||
| 46 | + child.on('close', function(code) { | ||
| 47 | + assert.strictEqual(code, 0); | ||
| 48 | + assert.notStrictEqual(stdout.indexOf('Script execution interrupted.\n'), -1); | ||
| 49 | + assert.notStrictEqual(stdout.indexOf('42042\n'), -1); | ||
| 50 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments