child process now use net.Socket
This commit is contained in:
parent
953fa3a5f5
commit
04c06b9149
68
doc/api.txt
68
doc/api.txt
@ -192,23 +192,6 @@ The default is to only recurse twice. To make it recurse indefinitely, pass
|
|||||||
in +null+ for +depth+.
|
in +null+ for +depth+.
|
||||||
|
|
||||||
|
|
||||||
+exec(command, callback)+::
|
|
||||||
Executes the command as a child process, buffers the output and returns it
|
|
||||||
in a callback.
|
|
||||||
+
|
|
||||||
----------------------------------------
|
|
||||||
var sys = require("sys");
|
|
||||||
sys.exec("ls /", function (err, stdout, stderr) {
|
|
||||||
if (err) throw err;
|
|
||||||
sys.puts(stdout);
|
|
||||||
});
|
|
||||||
----------------------------------------
|
|
||||||
+
|
|
||||||
The callback gets the arguments +(err, stdout, stderr)+. On success +err+
|
|
||||||
will be +null+. On error +err+ will be an instance of +Error+ and +err.code+
|
|
||||||
will be the exit code of the child process.
|
|
||||||
|
|
||||||
|
|
||||||
== Events
|
== Events
|
||||||
|
|
||||||
Many objects in Node emit events: a TCP server emits an event each time
|
Many objects in Node emit events: a TCP server emits an event each time
|
||||||
@ -399,25 +382,18 @@ Stops a interval from triggering.
|
|||||||
== Child Processes
|
== Child Processes
|
||||||
|
|
||||||
Node provides a tridirectional +popen(3)+ facility through the class
|
Node provides a tridirectional +popen(3)+ facility through the class
|
||||||
+process.ChildProcess+. It is possible to stream data through the child's +stdin+,
|
+ChildProcess+ class. It is possible to stream data through the child's
|
||||||
+stdout+, and +stderr+ in a fully non-blocking way.
|
+stdin+, +stdout+, and +stderr+ in a fully non-blocking way.
|
||||||
|
|
||||||
|
To create a child process use +require("child_process").spawn()+.
|
||||||
|
|
||||||
|
Child processes always have three streams associated with them.
|
||||||
|
+child.stdin+, +child.stdout+, and +child.stderr+.
|
||||||
|
|
||||||
=== +process.ChildProcess+
|
|
||||||
|
|
||||||
[cols="1,2,10",options="header"]
|
[cols="1,2,10",options="header"]
|
||||||
|=========================================================
|
|=========================================================
|
||||||
| Event | Parameters |Notes
|
| Event | Parameters |Notes
|
||||||
|
|
||||||
| +"output"+ | +data+ | Each time the child process
|
|
||||||
sends data to its +stdout+, this event is
|
|
||||||
emitted. +data+ is a string. If the child
|
|
||||||
process closes its +stdout+ stream (a common
|
|
||||||
thing to do on exit), this event will be emitted
|
|
||||||
with +data === null+.
|
|
||||||
|
|
||||||
| +"error"+ | +data+ | Identical to the +"output"+ event except for
|
|
||||||
+stderr+ instead of +stdout+.
|
|
||||||
|
|
||||||
| +"exit"+ | +code+ | This event is emitted after the child process
|
| +"exit"+ | +code+ | This event is emitted after the child process
|
||||||
ends. +code+ is the final exit code of the
|
ends. +code+ is the final exit code of the
|
||||||
process. One can be assured that after this
|
process. One can be assured that after this
|
||||||
@ -425,19 +401,18 @@ Node provides a tridirectional +popen(3)+ facility through the class
|
|||||||
+"error"+ callbacks will no longer be made.
|
+"error"+ callbacks will no longer be made.
|
||||||
|=========================================================
|
|=========================================================
|
||||||
|
|
||||||
+process.createChildProcess(command, args=[], env=process.env)+::
|
+require("child_process").spawn(command, args=[], env=process.env)+::
|
||||||
Launches a new process with the given +command+, command line arguments, and
|
Launches a new process with the given +command+, command line arguments, and
|
||||||
environmental variables. For example:
|
environmental variables. For example:
|
||||||
+
|
+
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
var ls = process.createChildProcess("ls", ["-lh", "/usr"]);
|
// Pipe a child process output to
|
||||||
ls.addListener("output", function (data) {
|
// parent process output
|
||||||
sys.puts(data);
|
var ls = spawn("ls", ["-lh", "/usr"]);
|
||||||
|
ls.stdout.addListener("data", function (data) {
|
||||||
|
process.stdout.write(data);
|
||||||
});
|
});
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
+
|
|
||||||
Note, if you just want to buffer the output of a command and return it, then
|
|
||||||
+exec()+ in +/sys.js+ might be better.
|
|
||||||
|
|
||||||
|
|
||||||
+child.pid+ ::
|
+child.pid+ ::
|
||||||
@ -459,6 +434,23 @@ Send a signal to the child process. If no argument is given, the process
|
|||||||
will be sent +"SIGTERM"+. See signal(7) for a list of available signals.
|
will be sent +"SIGTERM"+. See signal(7) for a list of available signals.
|
||||||
|
|
||||||
|
|
||||||
|
+require("child_process").exec(command, callback)+::
|
||||||
|
High-level way to executes a command as a child process and buffer the
|
||||||
|
output and return it in a callback.
|
||||||
|
+
|
||||||
|
----------------------------------------
|
||||||
|
var exec = require("child_process").exec;
|
||||||
|
exec("ls /", function (err, stdout, stderr) {
|
||||||
|
if (err) throw err;
|
||||||
|
sys.puts(stdout);
|
||||||
|
});
|
||||||
|
----------------------------------------
|
||||||
|
+
|
||||||
|
The callback gets the arguments +(err, stdout, stderr)+. On success +err+
|
||||||
|
will be +null+. On error +err+ will be an instance of +Error+ and +err.code+
|
||||||
|
will be the exit code of the child process.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
== File System
|
== File System
|
||||||
|
|
||||||
|
100
lib/child_process.js
Normal file
100
lib/child_process.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
var inherits = require('sys').inherits;
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
var Socket = require('net').Socket;
|
||||||
|
var InternalChildProcess = process.binding('child_process').ChildProcess;
|
||||||
|
|
||||||
|
|
||||||
|
var spawn = exports.spawn = function (path, args, env) {
|
||||||
|
var child = new ChildProcess();
|
||||||
|
child.spawn(path, args, env);
|
||||||
|
return child;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.exec = function (command, callback) {
|
||||||
|
var child = spawn("/bin/sh", ["-c", command]);
|
||||||
|
var stdout = "";
|
||||||
|
var stderr = "";
|
||||||
|
|
||||||
|
child.stdout.setEncoding('utf8');
|
||||||
|
child.stdout.addListener("data", function (chunk) { stdout += chunk; });
|
||||||
|
|
||||||
|
child.stderr.setEncoding('utf8');
|
||||||
|
child.stderr.addListener("data", function (chunk) { stderr += chunk; });
|
||||||
|
|
||||||
|
child.addListener("exit", function (code) {
|
||||||
|
if (code == 0) {
|
||||||
|
if (callback) callback(null, stdout, stderr);
|
||||||
|
} else {
|
||||||
|
var e = new Error("Command failed: " + stderr);
|
||||||
|
e.code = code;
|
||||||
|
if (callback) callback(e, stdout, stderr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function ChildProcess () {
|
||||||
|
process.EventEmitter.call(this);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var gotCHLD = false;
|
||||||
|
var exitCode;
|
||||||
|
var internal = this._internal = new InternalChildProcess();
|
||||||
|
|
||||||
|
var stdin = this.stdin = new Socket();
|
||||||
|
var stdout = this.stdout = new Socket();
|
||||||
|
var stderr = this.stderr = new Socket();
|
||||||
|
|
||||||
|
stderr.onend = stdout.onend = function () {
|
||||||
|
if (gotCHLD && !stdout.readable && !stderr.readable) {
|
||||||
|
self.emit('exit', exitCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
internal.onexit = function (code) {
|
||||||
|
gotCHLD = true;
|
||||||
|
exitCode = code;
|
||||||
|
if (!stdout.readable && !stderr.readable) {
|
||||||
|
self.emit('exit', exitCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.__defineGetter__('pid', function () { return internal.pid; });
|
||||||
|
}
|
||||||
|
inherits(ChildProcess, EventEmitter);
|
||||||
|
|
||||||
|
|
||||||
|
ChildProcess.prototype.kill = function (sig) {
|
||||||
|
return this._internal.kill(sig);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ChildProcess.prototype.spawn = function (path, args, env) {
|
||||||
|
args = args || [];
|
||||||
|
env = env || process.env;
|
||||||
|
var envPairs = [];
|
||||||
|
for (var key in env) {
|
||||||
|
if (env.hasOwnProperty(key)) {
|
||||||
|
envPairs.push(key + "=" + env[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fds = this._internal.spawn(path, args, envPairs);
|
||||||
|
|
||||||
|
this.stdin.open(fds[0]);
|
||||||
|
this.stdin.writable = true;
|
||||||
|
this.stdin.readable = false;
|
||||||
|
|
||||||
|
this.stdout.open(fds[1]);
|
||||||
|
this.stdout.writable = false;
|
||||||
|
this.stdout.readable = true;
|
||||||
|
this.stdout.resume();
|
||||||
|
|
||||||
|
this.stderr.open(fds[2]);
|
||||||
|
this.stderr.writable = false;
|
||||||
|
this.stderr.readable = true;
|
||||||
|
this.stderr.resume();
|
||||||
|
};
|
||||||
|
|
22
lib/net.js
22
lib/net.js
@ -367,20 +367,25 @@ function Socket (fd) {
|
|||||||
this.fd = null;
|
this.fd = null;
|
||||||
|
|
||||||
if (parseInt(fd) >= 0) {
|
if (parseInt(fd) >= 0) {
|
||||||
initSocket(this);
|
this.open(fd);
|
||||||
|
|
||||||
this.fd = fd;
|
|
||||||
|
|
||||||
this.readable = true;
|
|
||||||
|
|
||||||
this._writeWatcher.set(this.fd, false, true);
|
|
||||||
this.writable = true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sys.inherits(Socket, events.EventEmitter);
|
sys.inherits(Socket, events.EventEmitter);
|
||||||
exports.Socket = Socket;
|
exports.Socket = Socket;
|
||||||
|
|
||||||
|
|
||||||
|
Socket.prototype.open = function (fd) {
|
||||||
|
initSocket(this);
|
||||||
|
|
||||||
|
this.fd = fd;
|
||||||
|
|
||||||
|
this.readable = true;
|
||||||
|
|
||||||
|
this._writeWatcher.set(this.fd, false, true);
|
||||||
|
this.writable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
exports.createConnection = function (port, host) {
|
exports.createConnection = function (port, host) {
|
||||||
var s = new Socket();
|
var s = new Socket();
|
||||||
s.connect(port, host);
|
s.connect(port, host);
|
||||||
@ -716,6 +721,7 @@ Socket.prototype.forceClose = function (exception) {
|
|||||||
|
|
||||||
timeout.unenroll(this);
|
timeout.unenroll(this);
|
||||||
|
|
||||||
|
// FIXME Bug when this.fd == 0
|
||||||
if (this.fd) {
|
if (this.fd) {
|
||||||
close(this.fd);
|
close(this.fd);
|
||||||
debug('close ' + this.fd);
|
debug('close ' + this.fd);
|
||||||
|
35
lib/sys.js
35
lib/sys.js
@ -16,7 +16,7 @@ exports.debug = function (x) {
|
|||||||
process.binding('stdio').writeError("DEBUG: " + x + "\n");
|
process.binding('stdio').writeError("DEBUG: " + x + "\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.error = function (x) {
|
var error = exports.error = function (x) {
|
||||||
for (var i = 0, len = arguments.length; i < len; ++i) {
|
for (var i = 0, len = arguments.length; i < len; ++i) {
|
||||||
process.binding('stdio').writeError(arguments[i] + '\n');
|
process.binding('stdio').writeError(arguments[i] + '\n');
|
||||||
}
|
}
|
||||||
@ -184,7 +184,7 @@ exports.inspect = function (obj, showHidden, depth) {
|
|||||||
|
|
||||||
exports.p = function () {
|
exports.p = function () {
|
||||||
for (var i = 0, len = arguments.length; i < len; ++i) {
|
for (var i = 0, len = arguments.length; i < len; ++i) {
|
||||||
exports.error(exports.inspect(arguments[i]));
|
error(exports.inspect(arguments[i]));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -207,29 +207,14 @@ exports.log = function (msg) {
|
|||||||
exports.puts(timestamp() + ' - ' + msg.toString());
|
exports.puts(timestamp() + ' - ' + msg.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.exec = function (command, callback) {
|
var execWarning;
|
||||||
var child = process.createChildProcess("/bin/sh", ["-c", command]);
|
exports.exec = function () {
|
||||||
var stdout = "";
|
if (!execWarning) {
|
||||||
var stderr = "";
|
execWarning = 'sys.exec has moved to the "child_process" module. Please update your source code.'
|
||||||
|
error(execWarning);
|
||||||
child.addListener("output", function (chunk) {
|
}
|
||||||
if (chunk) stdout += chunk;
|
return require('child_process').exec.apply(this, arguments);
|
||||||
});
|
}
|
||||||
|
|
||||||
child.addListener("error", function (chunk) {
|
|
||||||
if (chunk) stderr += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
child.addListener("exit", function (code) {
|
|
||||||
if (code == 0) {
|
|
||||||
if (callback) callback(null, stdout, stderr);
|
|
||||||
} else {
|
|
||||||
var e = new Error("Command failed: " + stderr);
|
|
||||||
e.code = code;
|
|
||||||
if (callback) callback(e, stdout, stderr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inherit the prototype methods from one constructor into another.
|
* Inherit the prototype methods from one constructor into another.
|
||||||
|
13
src/node.cc
13
src/node.cc
@ -1073,6 +1073,8 @@ static Handle<Value> Binding(const Arguments& args) {
|
|||||||
|
|
||||||
Local<Object> exports;
|
Local<Object> exports;
|
||||||
|
|
||||||
|
// TODO DRY THIS UP!
|
||||||
|
|
||||||
if (!strcmp(*module_v, "stdio")) {
|
if (!strcmp(*module_v, "stdio")) {
|
||||||
if (binding_cache->Has(module)) {
|
if (binding_cache->Has(module)) {
|
||||||
exports = binding_cache->Get(module)->ToObject();
|
exports = binding_cache->Get(module)->ToObject();
|
||||||
@ -1157,6 +1159,15 @@ static Handle<Value> Binding(const Arguments& args) {
|
|||||||
binding_cache->Set(module, exports);
|
binding_cache->Set(module, exports);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (!strcmp(*module_v, "child_process")) {
|
||||||
|
if (binding_cache->Has(module)) {
|
||||||
|
exports = binding_cache->Get(module)->ToObject();
|
||||||
|
} else {
|
||||||
|
exports = Object::New();
|
||||||
|
ChildProcess::Initialize(exports);
|
||||||
|
binding_cache->Set(module, exports);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (!strcmp(*module_v, "natives")) {
|
} else if (!strcmp(*module_v, "natives")) {
|
||||||
if (binding_cache->Has(module)) {
|
if (binding_cache->Has(module)) {
|
||||||
exports = binding_cache->Get(module)->ToObject();
|
exports = binding_cache->Get(module)->ToObject();
|
||||||
@ -1165,6 +1176,7 @@ static Handle<Value> Binding(const Arguments& args) {
|
|||||||
// Explicitly define native sources.
|
// Explicitly define native sources.
|
||||||
// TODO DRY/automate this?
|
// TODO DRY/automate this?
|
||||||
exports->Set(String::New("assert"), String::New(native_assert));
|
exports->Set(String::New("assert"), String::New(native_assert));
|
||||||
|
exports->Set(String::New("child_process"),String::New(native_child_process));
|
||||||
exports->Set(String::New("dns"), String::New(native_dns));
|
exports->Set(String::New("dns"), String::New(native_dns));
|
||||||
exports->Set(String::New("events"), String::New(native_events));
|
exports->Set(String::New("events"), String::New(native_events));
|
||||||
exports->Set(String::New("file"), String::New(native_file));
|
exports->Set(String::New("file"), String::New(native_file));
|
||||||
@ -1285,7 +1297,6 @@ static void Load(int argc, char *argv[]) {
|
|||||||
IOWatcher::Initialize(process); // io_watcher.cc
|
IOWatcher::Initialize(process); // io_watcher.cc
|
||||||
IdleWatcher::Initialize(process); // idle_watcher.cc
|
IdleWatcher::Initialize(process); // idle_watcher.cc
|
||||||
Timer::Initialize(process); // timer.cc
|
Timer::Initialize(process); // timer.cc
|
||||||
ChildProcess::Initialize(process); // child_process.cc
|
|
||||||
DefineConstants(process); // constants.cc
|
DefineConstants(process); // constants.cc
|
||||||
|
|
||||||
// Compile, execute the src/node.js file. (Which was included as static C
|
// Compile, execute the src/node.js file. (Which was included as static C
|
||||||
|
20
src/node.js
20
src/node.js
@ -25,6 +25,7 @@ process.unwatchFile = removed("process.unwatchFile() has moved to fs.unwatchFile
|
|||||||
GLOBAL.node = {};
|
GLOBAL.node = {};
|
||||||
|
|
||||||
node.createProcess = removed("node.createProcess() has been changed to process.createChildProcess() update your code");
|
node.createProcess = removed("node.createProcess() has been changed to process.createChildProcess() update your code");
|
||||||
|
process.createChildProcess = removed("childProcess API has changed. See doc/api.txt.");
|
||||||
node.exec = removed("process.exec() has moved. Use require('sys') to bring it back.");
|
node.exec = removed("process.exec() has moved. Use require('sys') to bring it back.");
|
||||||
node.inherits = removed("node.inherits() has moved. Use require('sys') to access it.");
|
node.inherits = removed("node.inherits() has moved. Use require('sys') to access it.");
|
||||||
process.inherits = removed("process.inherits() has moved to sys.inherits.");
|
process.inherits = removed("process.inherits() has moved to sys.inherits.");
|
||||||
@ -89,24 +90,6 @@ function requireNative (id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
process.createChildProcess = function (file, args, env) {
|
|
||||||
var child = new process.ChildProcess();
|
|
||||||
args = args || [];
|
|
||||||
env = env || process.env;
|
|
||||||
var envPairs = [];
|
|
||||||
for (var key in env) {
|
|
||||||
if (env.hasOwnProperty(key)) {
|
|
||||||
envPairs.push(key + "=" + env[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO Note envPairs is not currently used in child_process.cc. The PATH
|
|
||||||
// needs to be searched for the 'file' command if 'file' does not contain
|
|
||||||
// a '/' character.
|
|
||||||
child.spawn(file, args, envPairs);
|
|
||||||
return child;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
process.assert = function (x, msg) {
|
process.assert = function (x, msg) {
|
||||||
if (!(x)) throw new Error(msg || "assertion error");
|
if (!(x)) throw new Error(msg || "assertion error");
|
||||||
};
|
};
|
||||||
@ -797,7 +780,6 @@ process.openStdin = function () {
|
|||||||
var net = requireNative('net');
|
var net = requireNative('net');
|
||||||
var fd = process.binding('stdio').openStdin();
|
var fd = process.binding('stdio').openStdin();
|
||||||
stdin = new net.Socket(fd);
|
stdin = new net.Socket(fd);
|
||||||
process.stdout.write(stdin.fd + "\n");
|
|
||||||
stdin.resume();
|
stdin.resume();
|
||||||
stdin.readable = true;
|
stdin.readable = true;
|
||||||
return stdin;
|
return stdin;
|
||||||
|
@ -15,56 +15,55 @@ namespace node {
|
|||||||
|
|
||||||
using namespace v8;
|
using namespace v8;
|
||||||
|
|
||||||
Persistent<FunctionTemplate> ChildProcess::constructor_template;
|
|
||||||
|
|
||||||
static Persistent<String> pid_symbol;
|
static Persistent<String> pid_symbol;
|
||||||
static Persistent<String> exit_symbol;
|
static Persistent<String> onexit_symbol;
|
||||||
static Persistent<String> output_symbol;
|
|
||||||
static Persistent<String> error_symbol;
|
|
||||||
|
// TODO share with other modules
|
||||||
|
static inline int SetNonBlocking(int fd) {
|
||||||
|
int flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
if (r != 0) {
|
||||||
|
perror("SetNonBlocking()");
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void ChildProcess::Initialize(Handle<Object> target) {
|
void ChildProcess::Initialize(Handle<Object> target) {
|
||||||
HandleScope scope;
|
HandleScope scope;
|
||||||
|
|
||||||
Local<FunctionTemplate> t = FunctionTemplate::New(ChildProcess::New);
|
Local<FunctionTemplate> t = FunctionTemplate::New(ChildProcess::New);
|
||||||
constructor_template = Persistent<FunctionTemplate>::New(t);
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
constructor_template->Inherit(EventEmitter::constructor_template);
|
t->SetClassName(String::NewSymbol("ChildProcess"));
|
||||||
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
|
|
||||||
constructor_template->SetClassName(String::NewSymbol("ChildProcess"));
|
|
||||||
|
|
||||||
pid_symbol = NODE_PSYMBOL("pid");
|
pid_symbol = NODE_PSYMBOL("pid");
|
||||||
exit_symbol = NODE_PSYMBOL("exit");
|
onexit_symbol = NODE_PSYMBOL("onexit");
|
||||||
output_symbol = NODE_PSYMBOL("output");
|
|
||||||
error_symbol = NODE_PSYMBOL("error");
|
|
||||||
|
|
||||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "spawn", ChildProcess::Spawn);
|
NODE_SET_PROTOTYPE_METHOD(t, "spawn", ChildProcess::Spawn);
|
||||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "write", ChildProcess::Write);
|
NODE_SET_PROTOTYPE_METHOD(t, "kill", ChildProcess::Kill);
|
||||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", ChildProcess::Close);
|
|
||||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "kill", ChildProcess::Kill);
|
|
||||||
|
|
||||||
target->Set(String::NewSymbol("ChildProcess"),
|
target->Set(String::NewSymbol("ChildProcess"), t->GetFunction());
|
||||||
constructor_template->GetFunction());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Handle<Value> ChildProcess::New(const Arguments& args) {
|
Handle<Value> ChildProcess::New(const Arguments& args) {
|
||||||
HandleScope scope;
|
HandleScope scope;
|
||||||
|
|
||||||
ChildProcess *p = new ChildProcess();
|
ChildProcess *p = new ChildProcess();
|
||||||
p->Wrap(args.Holder());
|
p->Wrap(args.Holder());
|
||||||
|
|
||||||
return args.This();
|
return args.This();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// This is an internal function. The third argument should be an array
|
// This is an internal function. The third argument should be an array
|
||||||
// of key value pairs seperated with '='.
|
// of key value pairs seperated with '='.
|
||||||
Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
||||||
HandleScope scope;
|
HandleScope scope;
|
||||||
|
|
||||||
if ( args.Length() != 3
|
if (args.Length() != 3 ||
|
||||||
|| !args[0]->IsString()
|
!args[0]->IsString() ||
|
||||||
|| !args[1]->IsArray()
|
!args[1]->IsArray() ||
|
||||||
|| !args[2]->IsArray()
|
!args[2]->IsArray()) {
|
||||||
)
|
|
||||||
{
|
|
||||||
return ThrowException(Exception::Error(String::New("Bad argument.")));
|
return ThrowException(Exception::Error(String::New("Bad argument.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +97,9 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
|||||||
env[i] = strdup(*pair);
|
env[i] = strdup(*pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
int r = child->Spawn(argv[0], argv, env);
|
int fds[3];
|
||||||
|
|
||||||
|
int r = child->Spawn(argv[0], argv, env, fds);
|
||||||
|
|
||||||
for (i = 0; i < argv_length; i++) free(argv[i]);
|
for (i = 0; i < argv_length; i++) free(argv[i]);
|
||||||
delete [] argv;
|
delete [] argv;
|
||||||
@ -110,32 +111,18 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
|||||||
return ThrowException(Exception::Error(String::New("Error spawning")));
|
return ThrowException(Exception::Error(String::New("Error spawning")));
|
||||||
}
|
}
|
||||||
|
|
||||||
child->handle_->Set(pid_symbol, Integer::New(child->pid_));
|
Local<Array> a = Array::New(3);
|
||||||
|
|
||||||
return Undefined();
|
assert(fds[0] >= 0);
|
||||||
|
a->Set(0, Integer::New(fds[0])); // stdin
|
||||||
|
assert(fds[1] >= 0);
|
||||||
|
a->Set(1, Integer::New(fds[1])); // stdout
|
||||||
|
assert(fds[2] >= 0);
|
||||||
|
a->Set(2, Integer::New(fds[2])); // stderr
|
||||||
|
|
||||||
|
return scope.Close(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Value> ChildProcess::Write(const Arguments& args) {
|
|
||||||
HandleScope scope;
|
|
||||||
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
|
|
||||||
assert(child);
|
|
||||||
|
|
||||||
enum encoding enc = ParseEncoding(args[1]);
|
|
||||||
ssize_t len = DecodeBytes(args[0], enc);
|
|
||||||
|
|
||||||
if (len < 0) {
|
|
||||||
Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
|
|
||||||
return ThrowException(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
char * buf = new char[len];
|
|
||||||
ssize_t written = DecodeWrite(buf, len, args[0], enc);
|
|
||||||
assert(written == len);
|
|
||||||
int r = child->Write(buf, len);
|
|
||||||
delete [] buf;
|
|
||||||
|
|
||||||
return r == 0 ? True() : False();
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle<Value> ChildProcess::Kill(const Arguments& args) {
|
Handle<Value> ChildProcess::Kill(const Arguments& args) {
|
||||||
HandleScope scope;
|
HandleScope scope;
|
||||||
@ -167,160 +154,59 @@ Handle<Value> ChildProcess::Kill(const Arguments& args) {
|
|||||||
return Undefined();
|
return Undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Value> ChildProcess::Close(const Arguments& args) {
|
|
||||||
HandleScope scope;
|
|
||||||
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
|
|
||||||
assert(child);
|
|
||||||
return child->Close() == 0 ? True() : False();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChildProcess::reader_closed(evcom_reader *r) {
|
void ChildProcess::Stop() {
|
||||||
ChildProcess *child = static_cast<ChildProcess*>(r->data);
|
if (ev_is_active(&child_watcher_)) {
|
||||||
if (r == &child->stdout_reader_) {
|
ev_child_stop(EV_DEFAULT_UC_ &child_watcher_);
|
||||||
child->stdout_fd_ = -1;
|
Unref();
|
||||||
} else {
|
|
||||||
assert(r == &child->stderr_reader_);
|
|
||||||
child->stderr_fd_ = -1;
|
|
||||||
}
|
}
|
||||||
evcom_reader_detach(r);
|
// Don't kill the PID here. We want to allow for killing the parent
|
||||||
child->MaybeShutdown();
|
// process and reparenting to initd. This is perhaps not going the best
|
||||||
|
// technique for daemonizing, but I don't want to rule it out.
|
||||||
|
pid_ = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChildProcess::stdin_closed(evcom_writer *w) {
|
|
||||||
ChildProcess *child = static_cast<ChildProcess*>(w->data);
|
|
||||||
assert(w == &child->stdin_writer_);
|
|
||||||
child->stdin_fd_ = -1;
|
|
||||||
evcom_writer_detach(w);
|
|
||||||
child->MaybeShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChildProcess::on_read(evcom_reader *r, const void *buf, size_t len) {
|
|
||||||
ChildProcess *child = static_cast<ChildProcess*>(r->data);
|
|
||||||
HandleScope scope;
|
|
||||||
|
|
||||||
bool isSTDOUT = (r == &child->stdout_reader_);
|
|
||||||
enum encoding encoding = isSTDOUT ?
|
|
||||||
child->stdout_encoding_ : child->stderr_encoding_;
|
|
||||||
|
|
||||||
// TODO emit 'end' event instead of null.
|
|
||||||
|
|
||||||
Local<Value> data = len ? Encode(buf, len, encoding) : Local<Value>::New(Null());
|
|
||||||
child->Emit(isSTDOUT ? output_symbol : error_symbol, 1, &data);
|
|
||||||
child->MaybeShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
ChildProcess::ChildProcess() : EventEmitter() {
|
|
||||||
evcom_reader_init(&stdout_reader_);
|
|
||||||
stdout_reader_.data = this;
|
|
||||||
stdout_reader_.on_read = on_read;
|
|
||||||
stdout_reader_.on_close = reader_closed;
|
|
||||||
|
|
||||||
evcom_reader_init(&stderr_reader_);
|
|
||||||
stderr_reader_.data = this;
|
|
||||||
stderr_reader_.on_read = on_read;
|
|
||||||
stderr_reader_.on_close = reader_closed;
|
|
||||||
|
|
||||||
evcom_writer_init(&stdin_writer_);
|
|
||||||
stdin_writer_.data = this;
|
|
||||||
stdin_writer_.on_close = stdin_closed;
|
|
||||||
|
|
||||||
ev_init(&child_watcher_, ChildProcess::OnCHLD);
|
|
||||||
child_watcher_.data = this;
|
|
||||||
|
|
||||||
stdout_fd_ = -1;
|
|
||||||
stderr_fd_ = -1;
|
|
||||||
stdin_fd_ = -1;
|
|
||||||
|
|
||||||
stdout_encoding_ = UTF8;
|
|
||||||
stderr_encoding_ = UTF8;
|
|
||||||
|
|
||||||
got_chld_ = false;
|
|
||||||
exit_code_ = 0;
|
|
||||||
|
|
||||||
pid_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChildProcess::~ChildProcess() {
|
|
||||||
Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChildProcess::Shutdown() {
|
|
||||||
if (stdin_fd_ >= 0) {
|
|
||||||
evcom_writer_close(&stdin_writer_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stdin_fd_ >= 0) close(stdin_fd_);
|
|
||||||
if (stdout_fd_ >= 0) close(stdout_fd_);
|
|
||||||
if (stderr_fd_ >= 0) close(stderr_fd_);
|
|
||||||
|
|
||||||
stdin_fd_ = -1;
|
|
||||||
stdout_fd_ = -1;
|
|
||||||
stderr_fd_ = -1;
|
|
||||||
|
|
||||||
evcom_writer_detach(&stdin_writer_);
|
|
||||||
evcom_reader_detach(&stdout_reader_);
|
|
||||||
evcom_reader_detach(&stderr_reader_);
|
|
||||||
|
|
||||||
ev_child_stop(EV_DEFAULT_UC_ &child_watcher_);
|
|
||||||
|
|
||||||
/* XXX Kill the PID? */
|
|
||||||
pid_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int SetNonBlocking(int fd) {
|
|
||||||
int flags = fcntl(fd, F_GETFL, 0);
|
|
||||||
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
||||||
if (r != 0) {
|
|
||||||
perror("SetNonBlocking()");
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that args[0] must be the same as the "file" param. This is an
|
// Note that args[0] must be the same as the "file" param. This is an
|
||||||
// execvp() requirement.
|
// execvp() requirement.
|
||||||
int ChildProcess::Spawn(const char *file, char *const args[], char **env) {
|
//
|
||||||
assert(pid_ == 0);
|
int ChildProcess::Spawn(const char *file,
|
||||||
assert(stdout_fd_ == -1);
|
char *const args[],
|
||||||
assert(stderr_fd_ == -1);
|
char **env,
|
||||||
assert(stdin_fd_ == -1);
|
int stdio_fds[3]) {
|
||||||
|
HandleScope scope;
|
||||||
|
assert(pid_ == -1);
|
||||||
|
assert(!ev_is_active(&child_watcher_));
|
||||||
|
|
||||||
int stdout_pipe[2], stdin_pipe[2], stderr_pipe[2];
|
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
|
||||||
|
|
||||||
/* An implementation of popen(), basically */
|
/* An implementation of popen(), basically */
|
||||||
if (pipe(stdout_pipe) < 0) {
|
if (pipe(stdin_pipe) < 0 ||
|
||||||
|
pipe(stdout_pipe) < 0 ||
|
||||||
|
pipe(stderr_pipe) < 0) {
|
||||||
perror("pipe()");
|
perror("pipe()");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pipe(stderr_pipe) < 0) {
|
|
||||||
perror("pipe()");
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pipe(stdin_pipe) < 0) {
|
|
||||||
perror("pipe()");
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save environ in the case that we get it clobbered
|
// Save environ in the case that we get it clobbered
|
||||||
// by the child process.
|
// by the child process.
|
||||||
char **save_our_env = environ;
|
char **save_our_env = environ;
|
||||||
|
|
||||||
switch (pid_ = vfork()) {
|
switch (pid_ = vfork()) {
|
||||||
case -1: // Error.
|
case -1: // Error.
|
||||||
Shutdown();
|
Stop();
|
||||||
return -4;
|
return -4;
|
||||||
|
|
||||||
case 0: // Child.
|
case 0: // Child.
|
||||||
|
close(stdin_pipe[1]); // close write end
|
||||||
|
dup2(stdin_pipe[0], STDIN_FILENO);
|
||||||
|
|
||||||
close(stdout_pipe[0]); // close read end
|
close(stdout_pipe[0]); // close read end
|
||||||
dup2(stdout_pipe[1], STDOUT_FILENO);
|
dup2(stdout_pipe[1], STDOUT_FILENO);
|
||||||
|
|
||||||
close(stderr_pipe[0]); // close read end
|
close(stderr_pipe[0]); // close read end
|
||||||
dup2(stderr_pipe[1], STDERR_FILENO);
|
dup2(stderr_pipe[1], STDERR_FILENO);
|
||||||
|
|
||||||
close(stdin_pipe[1]); // close write end
|
|
||||||
dup2(stdin_pipe[0], STDIN_FILENO);
|
|
||||||
|
|
||||||
environ = env;
|
environ = env;
|
||||||
|
|
||||||
execvp(file, args);
|
execvp(file, args);
|
||||||
@ -328,81 +214,59 @@ int ChildProcess::Spawn(const char *file, char *const args[], char **env) {
|
|||||||
_exit(127);
|
_exit(127);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parent.
|
||||||
|
|
||||||
// Restore environment.
|
// Restore environment.
|
||||||
environ = save_our_env;
|
environ = save_our_env;
|
||||||
|
|
||||||
// Parent.
|
|
||||||
|
|
||||||
ev_child_set(&child_watcher_, pid_, 0);
|
ev_child_set(&child_watcher_, pid_, 0);
|
||||||
ev_child_start(EV_DEFAULT_UC_ &child_watcher_);
|
ev_child_start(EV_DEFAULT_UC_ &child_watcher_);
|
||||||
|
Ref();
|
||||||
close(stdout_pipe[1]);
|
handle_->Set(pid_symbol, Integer::New(pid_));
|
||||||
stdout_fd_ = stdout_pipe[0];
|
|
||||||
SetNonBlocking(stdout_fd_);
|
|
||||||
|
|
||||||
close(stderr_pipe[1]);
|
|
||||||
stderr_fd_ = stderr_pipe[0];
|
|
||||||
SetNonBlocking(stderr_fd_);
|
|
||||||
|
|
||||||
close(stdin_pipe[0]);
|
close(stdin_pipe[0]);
|
||||||
stdin_fd_ = stdin_pipe[1];
|
stdio_fds[0] = stdin_pipe[1];
|
||||||
SetNonBlocking(stdin_fd_);
|
SetNonBlocking(stdin_pipe[1]);
|
||||||
|
|
||||||
evcom_reader_set(&stdout_reader_, stdout_fd_);
|
close(stdout_pipe[1]);
|
||||||
evcom_reader_attach(EV_DEFAULT_UC_ &stdout_reader_);
|
stdio_fds[1] = stdout_pipe[0];
|
||||||
|
SetNonBlocking(stdout_pipe[0]);
|
||||||
|
|
||||||
evcom_reader_set(&stderr_reader_, stderr_fd_);
|
close(stderr_pipe[1]);
|
||||||
evcom_reader_attach(EV_DEFAULT_UC_ &stderr_reader_);
|
stdio_fds[2] = stderr_pipe[0];
|
||||||
|
SetNonBlocking(stderr_pipe[0]);
|
||||||
evcom_writer_set(&stdin_writer_, stdin_fd_);
|
|
||||||
evcom_writer_attach(EV_DEFAULT_UC_ &stdin_writer_);
|
|
||||||
|
|
||||||
Ref();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChildProcess::OnCHLD(EV_P_ ev_child *watcher, int revents) {
|
|
||||||
ev_child_stop(EV_A_ watcher);
|
|
||||||
ChildProcess *child = static_cast<ChildProcess*>(watcher->data);
|
|
||||||
|
|
||||||
assert(revents == EV_CHILD);
|
void ChildProcess::OnExit(int code) {
|
||||||
assert(child->pid_ == watcher->rpid);
|
HandleScope scope;
|
||||||
assert(&child->child_watcher_ == watcher);
|
|
||||||
|
|
||||||
child->got_chld_ = true;
|
pid_ = -1;
|
||||||
child->exit_code_ = watcher->rstatus;
|
Stop();
|
||||||
|
|
||||||
if (child->stdin_fd_ >= 0) evcom_writer_close(&child->stdin_writer_);
|
handle_->Set(pid_symbol, Null());
|
||||||
|
|
||||||
child->MaybeShutdown();
|
Local<Value> onexit_v = handle_->Get(onexit_symbol);
|
||||||
}
|
assert(onexit_v->IsFunction());
|
||||||
|
Local<Function> onexit = Local<Function>::Cast(onexit_v);
|
||||||
|
|
||||||
int ChildProcess::Write(const char *str, size_t len) {
|
TryCatch try_catch;
|
||||||
if (stdin_fd_ < 0 || got_chld_) return -1;
|
|
||||||
evcom_writer_write(&stdin_writer_, str, len);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChildProcess::Close(void) {
|
Local<Value> argv[1];
|
||||||
if (stdin_fd_ < 0 || got_chld_) return -1;
|
argv[0] = Integer::New(code);
|
||||||
evcom_writer_close(&stdin_writer_);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChildProcess::Kill(int sig) {
|
onexit->Call(handle_, 1, argv);
|
||||||
if (got_chld_ || pid_ == 0) return -1;
|
|
||||||
return kill(pid_, sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChildProcess::MaybeShutdown(void) {
|
if (try_catch.HasCaught()) {
|
||||||
if (stdout_fd_ < 0 && stderr_fd_ < 0 && got_chld_) {
|
FatalException(try_catch);
|
||||||
HandleScope scope;
|
|
||||||
Handle<Value> argv[1] = { Integer::New(exit_code_) };
|
|
||||||
Emit(exit_symbol, 1, argv);
|
|
||||||
Shutdown();
|
|
||||||
Unref();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ChildProcess::Kill(int sig) {
|
||||||
|
return kill(pid_, sig);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
@ -1,65 +1,68 @@
|
|||||||
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
||||||
#ifndef SRC_CHILD_PROCESS_H_
|
#ifndef NODE_CHILD_PROCESS_H_
|
||||||
#define SRC_CHILD_PROCESS_H_
|
#define NODE_CHILD_PROCESS_H_
|
||||||
|
|
||||||
#include <node.h>
|
#include <node.h>
|
||||||
#include <node_events.h>
|
#include <node_object_wrap.h>
|
||||||
|
|
||||||
#include <v8.h>
|
#include <v8.h>
|
||||||
#include <ev.h>
|
#include <ev.h>
|
||||||
#include <evcom.h>
|
|
||||||
|
// ChildProcess is a thin wrapper around ev_child. It has the extra
|
||||||
|
// functionality that it can spawn a child process with pipes connected to
|
||||||
|
// its stdin, stdout, stderr. This class is not meant to be exposed to but
|
||||||
|
// wrapped up in a more friendly EventEmitter with streams for each of the
|
||||||
|
// pipes.
|
||||||
|
//
|
||||||
|
// When the child process exits (when the parent receives SIGCHLD) the
|
||||||
|
// callback child.onexit will be called.
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
class ChildProcess : EventEmitter {
|
class ChildProcess : ObjectWrap {
|
||||||
public:
|
public:
|
||||||
static void Initialize(v8::Handle<v8::Object> target);
|
static void Initialize(v8::Handle<v8::Object> target);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static v8::Persistent<v8::FunctionTemplate> constructor_template;
|
|
||||||
static v8::Handle<v8::Value> New(const v8::Arguments& args);
|
static v8::Handle<v8::Value> New(const v8::Arguments& args);
|
||||||
static v8::Handle<v8::Value> Spawn(const v8::Arguments& args);
|
static v8::Handle<v8::Value> Spawn(const v8::Arguments& args);
|
||||||
static v8::Handle<v8::Value> Write(const v8::Arguments& args);
|
|
||||||
static v8::Handle<v8::Value> Close(const v8::Arguments& args);
|
|
||||||
static v8::Handle<v8::Value> Kill(const v8::Arguments& args);
|
static v8::Handle<v8::Value> Kill(const v8::Arguments& args);
|
||||||
static v8::Handle<v8::Value> PIDGetter(v8::Local<v8::String> _,
|
|
||||||
const v8::AccessorInfo& info);
|
|
||||||
|
|
||||||
ChildProcess();
|
ChildProcess() : ObjectWrap() {
|
||||||
~ChildProcess();
|
ev_init(&child_watcher_, ChildProcess::on_chld);
|
||||||
|
child_watcher_.data = this;
|
||||||
|
pid_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
int Spawn(const char *file, char *const argv[], char **env);
|
~ChildProcess() {
|
||||||
int Write(const char *str, size_t len);
|
Stop();
|
||||||
int Close(void);
|
}
|
||||||
|
|
||||||
|
// Returns 0 on success. stdio_fds will contain file desciptors for stdin,
|
||||||
|
// stdout, and stderr of the subprocess. stdin is writable; the other two
|
||||||
|
// are readable.
|
||||||
|
// The user of this class has responsibility to close these pipes after
|
||||||
|
// the child process exits.
|
||||||
|
int Spawn(const char *file, char *const argv[], char **env, int stdio_fds[3]);
|
||||||
|
|
||||||
|
// Simple syscall wrapper. Does not disable the watcher. onexit will be
|
||||||
|
// called still.
|
||||||
int Kill(int sig);
|
int Kill(int sig);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void on_read(evcom_reader *r, const void *buf, size_t len);
|
void OnExit(int code);
|
||||||
static void reader_closed(evcom_reader *r);
|
void Stop(void);
|
||||||
static void stdin_closed(evcom_writer *w);
|
|
||||||
static void OnCHLD(EV_P_ ev_child *watcher, int revents);
|
|
||||||
|
|
||||||
void MaybeShutdown(void);
|
static void on_chld(EV_P_ ev_child *watcher, int revents) {
|
||||||
void Shutdown(void);
|
ChildProcess *child = static_cast<ChildProcess*>(watcher->data);
|
||||||
|
assert(revents == EV_CHILD);
|
||||||
evcom_reader stdout_reader_;
|
assert(child->pid_ == watcher->rpid);
|
||||||
evcom_reader stderr_reader_;
|
assert(&child->child_watcher_ == watcher);
|
||||||
evcom_writer stdin_writer_;
|
child->OnExit(watcher->rstatus);
|
||||||
|
}
|
||||||
|
|
||||||
ev_child child_watcher_;
|
ev_child child_watcher_;
|
||||||
|
|
||||||
int stdout_fd_;
|
|
||||||
int stderr_fd_;
|
|
||||||
int stdin_fd_;
|
|
||||||
|
|
||||||
enum encoding stdout_encoding_;
|
|
||||||
enum encoding stderr_encoding_;
|
|
||||||
|
|
||||||
pid_t pid_;
|
pid_t pid_;
|
||||||
|
|
||||||
bool got_chld_;
|
|
||||||
int exit_code_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
#endif // SRC_CHILD_PROCESS_H_
|
#endif // NODE_CHILD_PROCESS_H_
|
||||||
|
11
test/fixtures/echo.js
vendored
11
test/fixtures/echo.js
vendored
@ -1,12 +1,13 @@
|
|||||||
require("../common");
|
require("../common");
|
||||||
process.stdio.open();
|
|
||||||
|
|
||||||
print("hello world\r\n");
|
print("hello world\r\n");
|
||||||
|
|
||||||
process.stdio.addListener("data", function (data) {
|
var stdin = process.openStdin();
|
||||||
print(data);
|
|
||||||
|
stdin.addListener("data", function (data) {
|
||||||
|
process.stdout.write(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.stdio.addListener("close", function () {
|
stdin.addListener("end", function () {
|
||||||
process.stdio.close();
|
process.stdout.close();
|
||||||
});
|
});
|
||||||
|
4
test/fixtures/print-chars.js
vendored
4
test/fixtures/print-chars.js
vendored
@ -3,8 +3,8 @@ require("../common");
|
|||||||
var n = parseInt(process.argv[2]);
|
var n = parseInt(process.argv[2]);
|
||||||
|
|
||||||
var s = "";
|
var s = "";
|
||||||
for (var i = 0; i < n-1; i++) {
|
for (var i = 0; i < n; i++) {
|
||||||
s += 'c';
|
s += 'c';
|
||||||
}
|
}
|
||||||
|
|
||||||
puts(s); // \n is the nth char.
|
process.stdout.write(s);
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
require("../common");
|
|
||||||
|
|
||||||
var N = 40;
|
|
||||||
var finished = false;
|
|
||||||
|
|
||||||
function spawn (i) {
|
|
||||||
var child = process.createChildProcess( 'python'
|
|
||||||
, ['-c', 'print 500 * 1024 * "C"']
|
|
||||||
);
|
|
||||||
var output = "";
|
|
||||||
|
|
||||||
child.addListener("output", function(chunk) {
|
|
||||||
if (chunk) output += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
child.addListener("error", function(chunk) {
|
|
||||||
if (chunk) error(chunk)
|
|
||||||
});
|
|
||||||
|
|
||||||
child.addListener("exit", function () {
|
|
||||||
puts(output);
|
|
||||||
if (i < N)
|
|
||||||
spawn(i+1);
|
|
||||||
else
|
|
||||||
finished = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn(0);
|
|
||||||
|
|
||||||
process.addListener("exit", function () {
|
|
||||||
assert.equal(true, finished);
|
|
||||||
});
|
|
@ -1,14 +1,19 @@
|
|||||||
require("../common");
|
require("../common");
|
||||||
|
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
var pwd_called = false;
|
var pwd_called = false;
|
||||||
|
|
||||||
function pwd (callback) {
|
function pwd (callback) {
|
||||||
var output = "";
|
var output = "";
|
||||||
var child = process.createChildProcess("pwd");
|
var child = spawn("pwd");
|
||||||
child.addListener("output", function (s) {
|
|
||||||
|
child.stdout.setEncoding('utf8');
|
||||||
|
child.stdout.addListener("data", function (s) {
|
||||||
puts("stdout: " + JSON.stringify(s));
|
puts("stdout: " + JSON.stringify(s));
|
||||||
if (s) output += s;
|
output += s;
|
||||||
});
|
});
|
||||||
|
|
||||||
child.addListener("exit", function (c) {
|
child.addListener("exit", function (c) {
|
||||||
puts("exit: " + c);
|
puts("exit: " + c);
|
||||||
assert.equal(0, c);
|
assert.equal(0, c);
|
@ -1,10 +1,15 @@
|
|||||||
require("../common");
|
require("../common");
|
||||||
child = process.createChildProcess('/usr/bin/env', [], {'HELLO' : 'WORLD'});
|
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
child = spawn('/usr/bin/env', [], {'HELLO' : 'WORLD'});
|
||||||
|
|
||||||
response = "";
|
response = "";
|
||||||
|
|
||||||
child.addListener("output", function (chunk) {
|
child.stdout.setEncoding('utf8');
|
||||||
puts("stdout: " + JSON.stringify(chunk));
|
|
||||||
if (chunk) response += chunk;
|
child.stdout.addListener("data", function (chunk) {
|
||||||
|
puts("stdout: " + chunk);
|
||||||
|
response += chunk;
|
||||||
});
|
});
|
||||||
|
|
||||||
process.addListener('exit', function () {
|
process.addListener('exit', function () {
|
||||||
|
41
test/simple/test-child-process-ipc.js
Normal file
41
test/simple/test-child-process-ipc.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
require("../common");
|
||||||
|
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var sub = path.join(fixturesDir, 'echo.js');
|
||||||
|
|
||||||
|
var gotHelloWorld = false;
|
||||||
|
var gotEcho = false;
|
||||||
|
|
||||||
|
var child = spawn(process.argv[0], [sub]);
|
||||||
|
|
||||||
|
child.stderr.addListener("data", function (data){
|
||||||
|
puts("parent stderr: " + data);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdout.setEncoding('utf8');
|
||||||
|
|
||||||
|
child.stdout.addListener("data", function (data){
|
||||||
|
puts('child said: ' + JSON.stringify(data));
|
||||||
|
if (!gotHelloWorld) {
|
||||||
|
assert.equal("hello world\r\n", data);
|
||||||
|
gotHelloWorld = true;
|
||||||
|
child.stdin.write('echo me\r\n');
|
||||||
|
} else {
|
||||||
|
assert.equal("echo me\r\n", data);
|
||||||
|
gotEcho = true;
|
||||||
|
child.stdin.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdout.addListener("end", function (data){
|
||||||
|
puts('child end');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
process.addListener('exit', function () {
|
||||||
|
assert.ok(gotHelloWorld);
|
||||||
|
assert.ok(gotEcho);
|
||||||
|
});
|
38
test/simple/test-child-process-kill.js
Normal file
38
test/simple/test-child-process-kill.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
require("../common");
|
||||||
|
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
|
var exitStatus = -1;
|
||||||
|
var gotStdoutEOF = false;
|
||||||
|
var gotStderrEOF = false;
|
||||||
|
|
||||||
|
var cat = spawn("cat");
|
||||||
|
|
||||||
|
|
||||||
|
cat.stdout.addListener("data", function (chunk) {
|
||||||
|
assert.ok(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
cat.stdout.addListener("end", function () {
|
||||||
|
gotStdoutEOF = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
cat.stderr.addListener("data", function (chunk) {
|
||||||
|
assert.ok(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
cat.stderr.addListener("end", function () {
|
||||||
|
gotStderrEOF = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
cat.addListener("exit", function (status) {
|
||||||
|
exitStatus = status;
|
||||||
|
});
|
||||||
|
|
||||||
|
cat.kill();
|
||||||
|
|
||||||
|
process.addListener("exit", function () {
|
||||||
|
assert.ok(exitStatus > 0);
|
||||||
|
assert.ok(gotStdoutEOF);
|
||||||
|
assert.ok(gotStderrEOF);
|
||||||
|
});
|
36
test/simple/test-child-process-spawn-loop.js
Normal file
36
test/simple/test-child-process-spawn-loop.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
require("../common");
|
||||||
|
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
|
var SIZE = 1000 * 1024;
|
||||||
|
var N = 40;
|
||||||
|
var finished = false;
|
||||||
|
|
||||||
|
function doSpawn (i) {
|
||||||
|
var child = spawn( 'python', ['-c', 'print ' + SIZE + ' * "C"']);
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
child.stdout.setEncoding('ascii');
|
||||||
|
child.stdout.addListener("data", function (chunk) {
|
||||||
|
count += chunk.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.addListener("data", function (chunk) {
|
||||||
|
puts('stderr: ' + chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.addListener("exit", function () {
|
||||||
|
assert.equal(SIZE + 1, count); // + 1 for \n
|
||||||
|
if (i < N) {
|
||||||
|
doSpawn(i+1);
|
||||||
|
} else {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doSpawn(0);
|
||||||
|
|
||||||
|
process.addListener("exit", function () {
|
||||||
|
assert.ok(finished);
|
||||||
|
});
|
48
test/simple/test-child-process-stdin.js
Normal file
48
test/simple/test-child-process-stdin.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
require("../common");
|
||||||
|
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
|
var cat = spawn("cat");
|
||||||
|
cat.stdin.write("hello");
|
||||||
|
cat.stdin.write(" ");
|
||||||
|
cat.stdin.write("world");
|
||||||
|
cat.stdin.close();
|
||||||
|
|
||||||
|
var response = "";
|
||||||
|
var exitStatus = -1;
|
||||||
|
|
||||||
|
var gotStdoutEOF = false;
|
||||||
|
|
||||||
|
cat.stdout.setEncoding('utf8');
|
||||||
|
cat.stdout.addListener("data", function (chunk) {
|
||||||
|
puts("stdout: " + chunk);
|
||||||
|
response += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
cat.stdout.addListener('end', function () {
|
||||||
|
gotStdoutEOF = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var gotStderrEOF = false;
|
||||||
|
|
||||||
|
cat.stderr.addListener("data", function (chunk) {
|
||||||
|
// shouldn't get any stderr output
|
||||||
|
assert.ok(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
cat.stderr.addListener("end", function (chunk) {
|
||||||
|
gotStderrEOF = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
cat.addListener("exit", function (status) {
|
||||||
|
puts("exit event");
|
||||||
|
exitStatus = status;
|
||||||
|
assert.equal("hello world", response);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.addListener("exit", function () {
|
||||||
|
assert.equal(0, exitStatus);
|
||||||
|
assert.equal("hello world", response);
|
||||||
|
});
|
27
test/simple/test-child-process-stdout-flush.js
Normal file
27
test/simple/test-child-process-stdout-flush.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
require("../common");
|
||||||
|
var path = require('path');
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
var sub = path.join(fixturesDir, 'print-chars.js');
|
||||||
|
|
||||||
|
n = 500000;
|
||||||
|
|
||||||
|
var child = spawn(process.argv[0], [sub, n]);
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
child.stderr.setEncoding('utf8');
|
||||||
|
child.stderr.addListener("data", function (data) {
|
||||||
|
puts("parent stderr: " + data);
|
||||||
|
assert.ok(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.setEncoding('utf8');
|
||||||
|
child.stdout.addListener("data", function (data) {
|
||||||
|
count += data.length;
|
||||||
|
puts(count);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.addListener("exit", function (data) {
|
||||||
|
assert.equal(n, count);
|
||||||
|
puts("okay");
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
require("../common");
|
require("../common");
|
||||||
|
var exec = require('child_process').exec;
|
||||||
success_count = 0;
|
success_count = 0;
|
||||||
error_count = 0;
|
error_count = 0;
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
process.mixin(require("../common"));
|
require("../common");
|
||||||
|
|
||||||
// The purpose of this test is not to check HTTP compliance but to test the
|
// The purpose of this test is not to check HTTP compliance but to test the
|
||||||
// binding. Tests for pathological http messages should be submitted
|
// binding. Tests for pathological http messages should be submitted
|
||||||
// upstream to http://github.com/ry/http-parser for inclusion into
|
// upstream to http://github.com/ry/http-parser for inclusion into
|
||||||
// deps/http-parser/test.c
|
// deps/http-parser/test.c
|
||||||
|
|
||||||
|
var HTTPParser = process.binding('http_parser').HTTPParser;
|
||||||
|
|
||||||
var parser = new process.HTTPParser("request");
|
var parser = new HTTPParser("request");
|
||||||
|
|
||||||
var buffer = new process.Buffer(1024);
|
var buffer = new process.Buffer(1024);
|
||||||
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
require("../common");
|
|
||||||
|
|
||||||
var exit_status = -1;
|
|
||||||
|
|
||||||
var cat = process.createChildProcess("cat");
|
|
||||||
|
|
||||||
cat.addListener("output", function (chunk) { assert.equal(null, chunk); });
|
|
||||||
cat.addListener("error", function (chunk) { assert.equal(null, chunk); });
|
|
||||||
cat.addListener("exit", function (status) { exit_status = status; });
|
|
||||||
|
|
||||||
cat.kill();
|
|
||||||
|
|
||||||
process.addListener("exit", function () {
|
|
||||||
assert.equal(true, exit_status > 0);
|
|
||||||
});
|
|
@ -1,34 +0,0 @@
|
|||||||
require("../common");
|
|
||||||
|
|
||||||
var cat = process.createChildProcess("cat");
|
|
||||||
|
|
||||||
var response = "";
|
|
||||||
var exit_status = -1;
|
|
||||||
|
|
||||||
cat.addListener("output", function (chunk) {
|
|
||||||
puts("stdout: " + JSON.stringify(chunk));
|
|
||||||
if (chunk) {
|
|
||||||
response += chunk;
|
|
||||||
if (response === "hello world") {
|
|
||||||
puts("closing cat");
|
|
||||||
cat.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cat.addListener("error", function (chunk) {
|
|
||||||
puts("stderr: " + JSON.stringify(chunk));
|
|
||||||
assert.equal(null, chunk);
|
|
||||||
});
|
|
||||||
cat.addListener("exit", function (status) {
|
|
||||||
puts("exit event");
|
|
||||||
exit_status = status;
|
|
||||||
});
|
|
||||||
|
|
||||||
cat.write("hello");
|
|
||||||
cat.write(" ");
|
|
||||||
cat.write("world");
|
|
||||||
|
|
||||||
process.addListener("exit", function () {
|
|
||||||
assert.equal(0, exit_status);
|
|
||||||
assert.equal("hello world", response);
|
|
||||||
});
|
|
@ -1,36 +0,0 @@
|
|||||||
require("../common");
|
|
||||||
var path = require('path');
|
|
||||||
|
|
||||||
var sub = path.join(fixturesDir, 'echo.js');
|
|
||||||
|
|
||||||
var gotHelloWorld = false;
|
|
||||||
var gotEcho = false;
|
|
||||||
|
|
||||||
var child = process.createChildProcess(process.argv[0], [sub]);
|
|
||||||
|
|
||||||
child.addListener("error", function (data){
|
|
||||||
puts("parent stderr: " + data);
|
|
||||||
});
|
|
||||||
|
|
||||||
child.addListener("output", function (data){
|
|
||||||
if (data) {
|
|
||||||
puts('child said: ' + JSON.stringify(data));
|
|
||||||
if (!gotHelloWorld) {
|
|
||||||
assert.equal("hello world\r\n", data);
|
|
||||||
gotHelloWorld = true;
|
|
||||||
child.write('echo me\r\n');
|
|
||||||
} else {
|
|
||||||
assert.equal("echo me\r\n", data);
|
|
||||||
gotEcho = true;
|
|
||||||
child.close();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
puts('child end');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
process.addListener('exit', function () {
|
|
||||||
assert.ok(gotHelloWorld);
|
|
||||||
assert.ok(gotEcho);
|
|
||||||
});
|
|
@ -1,29 +0,0 @@
|
|||||||
require("../common");
|
|
||||||
var path = require('path');
|
|
||||||
|
|
||||||
var sub = path.join(fixturesDir, 'print-chars.js');
|
|
||||||
|
|
||||||
n = 100000;
|
|
||||||
|
|
||||||
var child = process.createChildProcess(process.argv[0], [sub, n]);
|
|
||||||
|
|
||||||
var count = 0;
|
|
||||||
|
|
||||||
child.addListener("error", function (data){
|
|
||||||
if (data) {
|
|
||||||
puts("parent stderr: " + data);
|
|
||||||
assert.ok(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
child.addListener("output", function (data){
|
|
||||||
if (data) {
|
|
||||||
count += data.length;
|
|
||||||
puts(count);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
child.addListener("exit", function (data) {
|
|
||||||
assert.equal(n, count);
|
|
||||||
puts("okay");
|
|
||||||
});
|
|
Loading…
x
Reference in New Issue
Block a user