diff --git a/README.md b/README.md index db96034..66d690c 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,11 @@ pyshell.on('message', function (message) { }); // end the input stream and allow the process to exit -pyshell.end(function (err) { +pyshell.end(function (err,code,signal) { if (err) throw err; + console.log('The exit code was: ' + code); + console.log('The exit signal was: ' + signal); + console.log('finished'); console.log('finished'); }); ``` @@ -209,6 +212,10 @@ Parses incoming data from the Python script written via stdout and emits `messag Closes the stdin stream, allowing the Python script to finish and exit. The optional callback is invoked when the process is terminated. +#### `.terminate(signal)` + +Terminates the python script, the optional end callback is invoked if specified. A kill signal may be provided by `signal`, if `signal` is not specified SIGTERM is sent. + #### event: `message` Fires when a chunk of data is parsed from the stdout stream via the `receive` method. If a `parser` method is specified, the result of this function will be the message value. This event is not emitted in binary mode. diff --git a/index.js b/index.js index 1c2bc59..ec97180 100644 --- a/index.js +++ b/index.js @@ -83,17 +83,17 @@ var PythonShell = function (script, options) { terminateIfNeeded(); }) - this.childProcess.on('exit', function (code) { + this.childProcess.on('exit', function (code,signal) { self.exitCode = code; + self.exitSignal = signal; terminateIfNeeded(); }); function terminateIfNeeded() { - if (!self.stderrHasEnded || !self.stdoutHasEnded || self.exitCode == null) { - return; - } + if(!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null)) return; + var err; - if (errorData || self.exitCode !== 0) { + if (errorData || (self.exitCode && self.exitCode !== 0)) { if (errorData) { err = self.parseError(errorData); } else { @@ -111,10 +111,11 @@ var PythonShell = function (script, options) { self.emit('error', err); } } + self.terminated = true; self.emit('close'); - self._endCallback && self._endCallback(err); - } + self._endCallback && self._endCallback(err,self.exitCode,self.exitSignal); + }; }; util.inherits(PythonShell, EventEmitter); @@ -245,4 +246,14 @@ PythonShell.prototype.end = function (callback) { return this; }; +/** + * Closes the stdin stream, which should cause the process to finish its work and close + * @returns {PythonShell} The same instance for chaining calls + */ +PythonShell.prototype.terminate = function (signal) { + this.childProcess.kill(signal); + this.terminated = true; + return this; +}; + module.exports = PythonShell; diff --git a/test/python/infinite_loop.py b/test/python/infinite_loop.py new file mode 100644 index 0000000..4e03907 --- /dev/null +++ b/test/python/infinite_loop.py @@ -0,0 +1,3 @@ +a = 0 +while(True): + a += 1 diff --git a/test/test-python-shell.js b/test/test-python-shell.js index 7ff6ba3..1722fac 100644 --- a/test/test-python-shell.js +++ b/test/test-python-shell.js @@ -238,9 +238,9 @@ describe('PythonShell', function () { describe('.end(callback)', function () { it('should end normally when exit code is zero', function (done) { var pyshell = new PythonShell('exit-code.py'); - pyshell.end(function (err) { + pyshell.end(function (err,code,signal) { if (err) return done(err); - pyshell.exitCode.should.be.exactly(0); + code.should.be.exactly(0); done(); }); }); @@ -287,4 +287,34 @@ describe('PythonShell', function () { }); }); }); + + describe('.terminate()', function () { + it('set terminated to true', function (done) { + var pyshell = new PythonShell('infinite_loop.py'); + pyshell.terminate(); + pyshell.terminated.should.be.true + done(); + }); + it('run the end callback if specified', function (done) { + var pyshell = new PythonShell('infinite_loop.py'); + var endCalled = false; + pyshell.end(()=>{ + endCalled = true; + }) + pyshell.terminate(); + pyshell.terminated.should.be.true + done(); + }); + it('terminate with correct kill signal', function (done) { + var pyshell = new PythonShell('infinite_loop.py'); + var endCalled = false; + pyshell.end(()=>{ + endCalled = true; + }) + pyshell.terminate('SIGKILL'); + pyshell.terminated.should.be.true; + setTimeout(()=>{pyshell.exitSignal.should.be.exactly('SIGKILL');},500); + done(); + }); + }); });