Restructure src/node.js startup code

This patch introduces a logical structure and sequence for the
bootstrap code found src/node.js.
This commit is contained in:
Felix Geisendörfer 2011-01-24 23:15:10 +01:00 committed by Ryan Dahl
parent cc5e9130bb
commit 91cc2d8c4b
2 changed files with 328 additions and 256 deletions

View File

@ -1,167 +1,210 @@
// Hello, and welcome to hacking node.js!
//
// This file is invoked by node::Load in src/node.cc, and responsible for
// bootstrapping the node.js core. Special caution is given to the performance
// of the startup process, so many dependencies are invoked lazily.
(function(process) { (function(process) {
global = this; global = this;
global.process = process;
global.global = global;
global.GLOBAL = global;
global.root = global;
/** deprecation errors ************************************************/ function startup() {
startup.globalVariables();
startup.globalTimeouts();
startup.globalConsole();
function removed(reason) { startup.processAssert();
return function() { startup.processNextTick();
throw new Error(reason); startup.processStdio();
}; startup.processKillAndExit();
startup.processSignalHandlers();
startup.removedMethods();
startup.resolveArgv0();
if (startup.runThirdPartyMain()) {
return;
}
if (startup.runDebugger()) {
return;
}
if (startup.runScript()) {
return;
}
if (startup.runEval()) {
return;
}
startup.runRepl();
} }
process.debug = startup.globalVariables = function() {
removed('process.debug() use console.error() instead'); global.process = process;
process.error = global.global = global;
removed('process.error() use console.error() instead'); global.GLOBAL = global;
process.watchFile = global.root = global;
removed('process.watchFile() has moved to fs.watchFile()'); global.Buffer = NativeModule.require('buffer').Buffer;
process.unwatchFile =
removed('process.unwatchFile() has moved to fs.unwatchFile()');
process.mixin =
removed('process.mixin() has been removed.');
process.createChildProcess =
removed('childProcess API has changed. See doc/api.txt.');
process.inherits =
removed('process.inherits() has moved to sys.inherits.');
process._byteLength =
removed('process._byteLength() has moved to Buffer.byteLength');
process.assert = function(x, msg) {
if (!x) throw new Error(msg || 'assertion error');
}; };
var Script = process.binding('evals').Script; startup.globalTimeouts = function() {
var runInThisContext = Script.runInThisContext; global.setTimeout = function() {
var t = NativeModule.require('timers');
return t.setTimeout.apply(this, arguments);
};
// lazy loaded. global.setInterval = function() {
var constants; var t = NativeModule.require('timers');
function lazyConstants() { return t.setInterval.apply(this, arguments);
if (!constants) constants = process.binding('constants'); };
return constants;
}
global.clearTimeout = function() {
var t = NativeModule.require('timers');
return t.clearTimeout.apply(this, arguments);
};
// nextTick() global.clearInterval = function() {
var t = NativeModule.require('timers');
var nextTickQueue = []; return t.clearInterval.apply(this, arguments);
};
process._tickCallback = function() {
var l = nextTickQueue.length;
if (l === 0) return;
try {
for (var i = 0; i < l; i++) {
nextTickQueue[i]();
}
}
catch (e) {
nextTickQueue.splice(0, i + 1);
if (i + 1 < l) {
process._needTickCallback();
}
throw e; // process.nextTick error, or 'error' event on first tick
}
nextTickQueue.splice(0, l);
}; };
process.nextTick = function(callback) { startup.globalConsole = function() {
nextTickQueue.push(callback); global.__defineGetter__('console', function() {
process._needTickCallback(); return NativeModule.require('console');
});
}; };
// Native modules don't need a full require function. So we can bootstrap
// most of the system with this mini module system. startup._lazyConstants = null;
var NativeModule = (function() {
function NativeModule(id) { startup.lazyConstants = function() {
this.filename = id + '.js'; if (!startup._lazyConstants) {
this.id = id; startup._lazyConstants = process.binding('constants');
this.exports = {};
this.loaded = false;
} }
return startup._lazyConstants;
};
NativeModule._source = process.binding('natives'); startup.processAssert = function() {
NativeModule._cache = {}; process.assert = function(x, msg) {
if (!x) {
throw new Error(msg || 'assertion error');
}
};
};
NativeModule.require = function(id) { startup.processNextTick = function() {
if (id == 'native_module') { var nextTickQueue = [];
return NativeModule;
process._tickCallback = function() {
var l = nextTickQueue.length;
if (l === 0) return;
try {
for (var i = 0; i < l; i++) {
nextTickQueue[i]();
}
}
catch (e) {
nextTickQueue.splice(0, i + 1);
if (i + 1 < l) {
process._needTickCallback();
}
throw e; // process.nextTick error, or 'error' event on first tick
} }
var cached = NativeModule.getCached(id); nextTickQueue.splice(0, l);
if (cached) { };
return cached.exports;
process.nextTick = function(callback) {
nextTickQueue.push(callback);
process._needTickCallback();
};
};
startup.processStdio = function() {
var stdout, stdin;
process.__defineGetter__('stdout', function() {
if (stdout) return stdout;
var binding = process.binding('stdio'),
net = NativeModule.require('net'),
fs = NativeModule.require('fs'),
tty = NativeModule.require('tty'),
fd = binding.stdoutFD;
if (binding.isatty(fd)) {
stdout = new tty.WriteStream(fd);
} else if (binding.isStdoutBlocking()) {
stdout = new fs.WriteStream(null, {fd: fd});
} else {
stdout = new net.Stream(fd);
// FIXME Should probably have an option in net.Stream to create a
// stream from an existing fd which is writable only. But for now
// we'll just add this hack and set the `readable` member to false.
// Test: ./node test/fixtures/echo.js < /etc/passwd
stdout.readable = false;
} }
if (!NativeModule.exists(id)) { return stdout;
throw new Error('No such native module ' + id); });
process.__defineGetter__('stdin', function() {
if (stdin) return stdin;
var binding = process.binding('stdio'),
net = NativeModule.require('net'),
fs = NativeModule.require('fs'),
tty = NativeModule.require('tty'),
fd = binding.openStdin();
if (binding.isatty(fd)) {
stdin = new tty.ReadStream(fd);
} else if (binding.isStdinBlocking()) {
stdin = new fs.ReadStream(null, {fd: fd});
} else {
stdin = new net.Stream(fd);
stdin.readable = true;
} }
var nativeModule = new NativeModule(id); return stdin;
});
nativeModule.compile(); process.openStdin = function() {
nativeModule.cache(); process.stdin.resume();
return process.stdin;
};
};
return nativeModule.exports; startup.processKillAndExit = function() {
process.exit = function(code) {
process.emit('exit', code || 0);
process.reallyExit(code || 0);
}; };
NativeModule.getCached = function(id) { process.kill = function(pid, sig) {
return NativeModule._cache[id]; sig = sig || 'SIGTERM';
}
NativeModule.exists = function(id) { if (!startup.lazyConstants()[sig]) {
return (id in NativeModule._source); throw new Error('Unknown signal: ' + sig);
} }
NativeModule.getSource = function(id) { process._kill(pid, startup.lazyConstants()[sig]);
return NativeModule._source[id];
}
NativeModule.wrap = function(script) {
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
}; };
};
NativeModule.wrapper = [ startup.processSignalHandlers = function() {
'(function (exports, require, module, __filename, __dirname) { ', // Load events module in order to access prototype elements on process like
'\n});' // process.addListener.
]; var events = NativeModule.require('events');
NativeModule.prototype.compile = function() {
var source = NativeModule.getSource(this.id);
source = NativeModule.wrap(source);
var fn = runInThisContext(source, this.filename, true);
fn(this.exports, NativeModule.require, this, this.filename);
this.loaded = true;
};
NativeModule.prototype.cache = function() {
NativeModule._cache[this.id] = this;
};
return NativeModule;
})();
var Module = NativeModule.require('module').Module;
// Load events module in order to access prototype elements on process like
// process.addListener.
var events = NativeModule.require('events');
// Signal Handlers
(function() {
var signalWatchers = {}; var signalWatchers = {};
var addListener = process.addListener; var addListener = process.addListener;
var removeListener = process.removeListener; var removeListener = process.removeListener;
function isSignal(event) { function isSignal(event) {
return event.slice(0, 3) === 'SIG' && lazyConstants()[event]; return event.slice(0, 3) === 'SIG' && startup.lazyConstants()[event];
} }
// Wrap addListener for the special signal types // Wrap addListener for the special signal types
@ -170,7 +213,7 @@
if (isSignal(type)) { if (isSignal(type)) {
if (!signalWatchers.hasOwnProperty(type)) { if (!signalWatchers.hasOwnProperty(type)) {
var b = process.binding('signal_watcher'); var b = process.binding('signal_watcher');
var w = new b.SignalWatcher(lazyConstants()[type]); var w = new b.SignalWatcher(startup.lazyConstants()[type]);
w.callback = function() { process.emit(type); }; w.callback = function() { process.emit(type); };
signalWatchers[type] = w; signalWatchers[type] = w;
w.start(); w.start();
@ -195,157 +238,186 @@
return ret; return ret;
}; };
})();
global.setTimeout = function() {
var t = NativeModule.require('timers');
return t.setTimeout.apply(this, arguments);
}; };
global.setInterval = function() { startup._removedProcessMethods = {
var t = NativeModule.require('timers'); 'debug': 'process.debug() use console.error() instead',
return t.setInterval.apply(this, arguments); 'error': 'process.error() use console.error() instead',
'watchFile': 'process.watchFile() has moved to fs.watchFile()',
'unwatchFile': 'process.unwatchFile() has moved to fs.unwatchFile()',
'mixin': 'process.mixin() has been removed.',
'createChildProcess': 'childProcess API has changed. See doc/api.txt.',
'inherits': 'process.inherits() has moved to sys.inherits.',
'_byteLength': 'process._byteLength() has moved to Buffer.byteLength',
}; };
global.clearTimeout = function() { startup.removedMethods = function() {
var t = NativeModule.require('timers'); for (var method in startup._removedProcessMethods) {
return t.clearTimeout.apply(this, arguments); var reason = startup._removedProcessMethods[method];
}; process[method] = startup._removedMethod(reason);
global.clearInterval = function() {
var t = NativeModule.require('timers');
return t.clearInterval.apply(this, arguments);
};
var stdout, stdin;
process.__defineGetter__('stdout', function() {
if (stdout) return stdout;
var binding = process.binding('stdio'),
net = NativeModule.require('net'),
fs = NativeModule.require('fs'),
tty = NativeModule.require('tty'),
fd = binding.stdoutFD;
if (binding.isatty(fd)) {
stdout = new tty.WriteStream(fd);
} else if (binding.isStdoutBlocking()) {
stdout = new fs.WriteStream(null, {fd: fd});
} else {
stdout = new net.Stream(fd);
// FIXME Should probably have an option in net.Stream to create a
// stream from an existing fd which is writable only. But for now
// we'll just add this hack and set the `readable` member to false.
// Test: ./node test/fixtures/echo.js < /etc/passwd
stdout.readable = false;
} }
};
return stdout; startup._removedMethod = function(reason) {
}); return function() {
throw new Error(reason);
};
};
startup.resolveArgv0 = function() {
var cwd = process.cwd();
var isWindows = process.platform === 'win32';
process.__defineGetter__('stdin', function() { // Make process.argv[0] into a full path, but only touch argv[0] if it's
if (stdin) return stdin; // not a system $PATH lookup.
// TODO: Make this work on Windows as well. Note that "node" might
var binding = process.binding('stdio'), // execute cwd\node.exe, or some %PATH%\node.exe on Windows,
net = NativeModule.require('net'), // and that every directory has its own cwd, so d:node.exe is valid.
fs = NativeModule.require('fs'), var argv0 = process.argv[0];
tty = NativeModule.require('tty'), if (!isWindows && argv0.indexOf('/') !== -1 && argv0.charAt(0) !== '/') {
fd = binding.openStdin(); var path = NativeModule.require('path');
process.argv[0] = path.join(cwd, process.argv[0]);
if (binding.isatty(fd)) {
stdin = new tty.ReadStream(fd);
} else if (binding.isStdinBlocking()) {
stdin = new fs.ReadStream(null, {fd: fd});
} else {
stdin = new net.Stream(fd);
stdin.readable = true;
} }
return stdin;
});
process.openStdin = function() {
process.stdin.resume();
return process.stdin;
}; };
startup.runThirdPartyMain = function() {
// Lazy load console object // To allow people to extend Node in different ways, this hook allows
global.__defineGetter__('console', function() { // one to drop a file lib/_third_party_main.js into the build directory
return NativeModule.require('console'); // which will be executed instead of Node's normal loading.
}); if (!NativeModule.exists('_third_party_main')) {
return;
}
global.Buffer = NativeModule.require('buffer').Buffer;
process.nextTick(function() {
process.exit = function(code) { NativeModule.require('_third_party_main');
process.emit('exit', code || 0); });
process.reallyExit(code || 0); return true;
}; };
process.kill = function(pid, sig) { startup.runDebugger = function() {
sig = sig || 'SIGTERM'; if (!(process.argv[1] == 'debug')) {
if (!lazyConstants()[sig]) throw new Error('Unknown signal: ' + sig); return;
process._kill(pid, lazyConstants()[sig]); }
};
// Start the debugger agent
var d = NativeModule.require('_debugger');
var cwd = process.cwd(); d.start();
var path = NativeModule.require('path'); return true;
var isWindows = process.platform === 'win32'; };
// Make process.argv[0] and process.argv[1] into full paths, but only startup.runScript = function() {
// touch argv[0] if it's not a system $PATH lookup. if (!process.argv[1]) {
// TODO: Make this work on Windows as well. Note that "node" might
// execute cwd\node.exe, or some %PATH%\node.exe on Windows,
// and that every directory has its own cwd, so d:node.exe is valid.
var argv0 = process.argv[0];
if (!isWindows && argv0.indexOf('/') !== -1 && argv0.charAt(0) !== '/') {
process.argv[0] = path.join(cwd, process.argv[0]);
}
// To allow people to extend Node in different ways, this hook allows
// one to drop a file lib/_third_party_main.js into the build directory
// which will be executed instead of Node's normal loading.
if (NativeModule.exists('_third_party_main')) {
process.nextTick(function() {
NativeModule.require('_third_party_main');
});
return;
}
if (process.argv[1]) {
if (process.argv[1] == 'debug') {
// Start the debugger agent
var d = NativeModule.require('_debugger');
d.start();
return; return;
} }
// Load Module
// make process.argv[1] into a full path // make process.argv[1] into a full path
if (!(/^http:\/\//).exec(process.argv[1])) { if (!(/^http:\/\//).exec(process.argv[1])) {
var path = NativeModule.require('path');
process.argv[1] = path.resolve(process.argv[1]); process.argv[1] = path.resolve(process.argv[1]);
} }
var Module = NativeModule.require('module');
// REMOVEME: nextTick should not be necessary. This hack to get // REMOVEME: nextTick should not be necessary. This hack to get
// test/simple/test-exception-handler2.js working. // test/simple/test-exception-handler2.js working.
process.nextTick(Module.runMain); process.nextTick(Module.runMain);
return;
}
if (process._eval) { return true;
};
startup.runEval = function() {
// -e, --eval // -e, --eval
if (!process._eval) {
return;
}
var Module = NativeModule.require('module');
var rv = new Module()._compile('return eval(process._eval)', 'eval'); var rv = new Module()._compile('return eval(process._eval)', 'eval');
console.log(rv); console.log(rv);
return; return true;
};
startup.runRepl = function() {
var Module = NativeModule.require('module');
// REPL
Module.requireRepl().start();
};
// Below you find a minimal module system, which is used to load the node
// core modules found in lib/*.js. All core modules are compiled into the
// node binary, so they can be loaded faster.
var Script = process.binding('evals').Script;
var runInThisContext = Script.runInThisContext;
function NativeModule(id) {
this.filename = id + '.js';
this.id = id;
this.exports = {};
this.loaded = false;
} }
// REPL NativeModule._source = process.binding('natives');
Module.requireRepl().start(); NativeModule._cache = {};
NativeModule.require = function(id) {
if (id == 'native_module') {
return NativeModule;
}
var cached = NativeModule.getCached(id);
if (cached) {
return cached.exports;
}
if (!NativeModule.exists(id)) {
throw new Error('No such native module ' + id);
}
var nativeModule = new NativeModule(id);
nativeModule.compile();
nativeModule.cache();
return nativeModule.exports;
};
NativeModule.getCached = function(id) {
return NativeModule._cache[id];
}
NativeModule.exists = function(id) {
return (id in NativeModule._source);
}
NativeModule.getSource = function(id) {
return NativeModule._source[id];
}
NativeModule.wrap = function(script) {
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};
NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
NativeModule.prototype.compile = function() {
var source = NativeModule.getSource(this.id);
source = NativeModule.wrap(source);
var fn = runInThisContext(source, this.filename, true);
fn(this.exports, NativeModule.require, this, this.filename);
this.loaded = true;
};
NativeModule.prototype.cache = function() {
NativeModule._cache[this.id] = this;
};
startup();
}); });

View File

@ -1,8 +1,8 @@
before before
node.js:* node.js:*
throw e; // process.nextTick error, or 'error' event on first tick throw e; // process.nextTick error, or 'error' event on first tick
^ ^
ReferenceError: foo is not defined ReferenceError: foo is not defined
at evalmachine.<anonymous>:* at evalmachine.<anonymous>:*
at Object.<anonymous> (*test*message*undefined_reference_in_new_context.js:*) at Object.<anonymous> (*test*message*undefined_reference_in_new_context.js:*)