From 8505392de43f4f198aa96ce302bab789a23cdbfc Mon Sep 17 00:00:00 2001 From: Bruce Jones Date: Wed, 5 Apr 2017 15:07:56 -0400 Subject: [PATCH 1/6] Implemented a terminate function Terminates the child process and calls the end callback --- index.js | 11 +++++++++++ test/python/infinite_loop.py | 3 +++ test/test-python-shell.js | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 test/python/infinite_loop.py diff --git a/index.js b/index.js index 1c2bc59..5271a75 100644 --- a/index.js +++ b/index.js @@ -245,4 +245,15 @@ 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 () { + this.childProcess.kill(); + this.terminated = true; + this._endCallback && this._endCallback(); + 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..38f39bd 100644 --- a/test/test-python-shell.js +++ b/test/test-python-shell.js @@ -287,4 +287,23 @@ 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(); + endCalled.should.be.true; + done(); + }); + }); }); From 54a3f36bdb586c8dd8c68b3fc7d15bac1b4f475a Mon Sep 17 00:00:00 2001 From: Bruce Jones Date: Wed, 5 Apr 2017 15:11:27 -0400 Subject: [PATCH 2/6] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index db96034..33c541c 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,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()` + +Terminates the python script, the optional end callback is invoked if specified. + #### 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. From db1cd116728d451721b29b420e4b5c32247f40b5 Mon Sep 17 00:00:00 2001 From: Bruce Jones Date: Fri, 26 Jan 2018 11:13:19 -0500 Subject: [PATCH 3/6] Added ability to send kill signal to terminate and address #94 --- index.js | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/index.js b/index.js index 5271a75..03f5d9e 100644 --- a/index.js +++ b/index.js @@ -73,48 +73,31 @@ var PythonShell = function (script, options) { errorData += ''+data; }); - this.stderr.on('end', function(){ - self.stderrHasEnded = true - terminateIfNeeded(); - }) - - this.stdout.on('end', function(){ - self.stdoutHasEnded = true - terminateIfNeeded(); - }) - - this.childProcess.on('exit', function (code) { - self.exitCode = code; - terminateIfNeeded(); - }); - - function terminateIfNeeded() { - if (!self.stderrHasEnded || !self.stdoutHasEnded || self.exitCode == null) { - return; - } + this.childProcess.on('exit', function (code, signal) { var err; - if (errorData || self.exitCode !== 0) { + if (errorData || (code && code !== 0)) { if (errorData) { err = self.parseError(errorData); } else { - err = new Error('process exited with code ' + self.exitCode); + err = new Error('process exited with code ' + code); } err = extend(err, { executable: pythonPath, options: pythonOptions.length ? pythonOptions : null, script: self.script, args: scriptArgs.length ? scriptArgs : null, - exitCode: self.exitCode + exitCode: code }); // do not emit error if only a callback is used if (self.listeners('error').length || !self._endCallback) { self.emit('error', err); } } + self.terminated = true; self.emit('close'); - self._endCallback && self._endCallback(err); - } + self._endCallback && self._endCallback(err,self.exitCode,signal); + }); }; util.inherits(PythonShell, EventEmitter); @@ -249,10 +232,9 @@ PythonShell.prototype.end = function (callback) { * 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 () { - this.childProcess.kill(); +PythonShell.prototype.terminate = function (signal) { + this.childProcess.kill(signal); this.terminated = true; - this._endCallback && this._endCallback(); return this; }; From 0ed5f5482de7668acc6444755bee941fdc86eb17 Mon Sep 17 00:00:00 2001 From: Bruce Jones Date: Fri, 26 Jan 2018 11:18:37 -0500 Subject: [PATCH 4/6] Updated readme --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33c541c..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,9 +212,9 @@ 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()` +#### `.terminate(signal)` -Terminates the python script, the optional end callback is invoked if specified. +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` From 2c2b7e454f764d9af2ca59a883fd8b6d10972376 Mon Sep 17 00:00:00 2001 From: Bruce Jones Date: Fri, 26 Jan 2018 13:18:06 -0500 Subject: [PATCH 5/6] Fixed bugs and updated tests. All tests should now pass. --- index.js | 30 ++++++++++++++++++++++++------ test/test-python-shell.js | 19 +++++++++++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 03f5d9e..ec97180 100644 --- a/index.js +++ b/index.js @@ -73,20 +73,38 @@ var PythonShell = function (script, options) { errorData += ''+data; }); - this.childProcess.on('exit', function (code, signal) { + this.stderr.on('end', function(){ + self.stderrHasEnded = true + terminateIfNeeded(); + }) + + this.stdout.on('end', function(){ + self.stdoutHasEnded = true + terminateIfNeeded(); + }) + + this.childProcess.on('exit', function (code,signal) { + self.exitCode = code; + self.exitSignal = signal; + terminateIfNeeded(); + }); + + function terminateIfNeeded() { + if(!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null)) return; + var err; - if (errorData || (code && code !== 0)) { + if (errorData || (self.exitCode && self.exitCode !== 0)) { if (errorData) { err = self.parseError(errorData); } else { - err = new Error('process exited with code ' + code); + err = new Error('process exited with code ' + self.exitCode); } err = extend(err, { executable: pythonPath, options: pythonOptions.length ? pythonOptions : null, script: self.script, args: scriptArgs.length ? scriptArgs : null, - exitCode: code + exitCode: self.exitCode }); // do not emit error if only a callback is used if (self.listeners('error').length || !self._endCallback) { @@ -96,8 +114,8 @@ var PythonShell = function (script, options) { self.terminated = true; self.emit('close'); - self._endCallback && self._endCallback(err,self.exitCode,signal); - }); + self._endCallback && self._endCallback(err,self.exitCode,self.exitSignal); + }; }; util.inherits(PythonShell, EventEmitter); diff --git a/test/test-python-shell.js b/test/test-python-shell.js index 38f39bd..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(); }); }); @@ -292,7 +292,7 @@ describe('PythonShell', function () { it('set terminated to true', function (done) { var pyshell = new PythonShell('infinite_loop.py'); pyshell.terminate(); - pyshell.terminated.should.be.true; + pyshell.terminated.should.be.true done(); }); it('run the end callback if specified', function (done) { @@ -302,7 +302,18 @@ describe('PythonShell', function () { endCalled = true; }) pyshell.terminate(); - endCalled.should.be.true; + 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(); }); }); From 80e4b1c54e47700a9062b2833478d8773831dc92 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 19:33:56 -0800 Subject: [PATCH 6/6] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4306866..8878e74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "python-shell", - "version": "0.4.0", + "version": "0.5.0", "description": "Run Python scripts from Node.js with simple (but efficient) inter-process communication through stdio", "keywords": [ "python"