Restructure src/node.js startup code
authorFelix Geisendörfer <felix@debuggable.com>
Mon, 24 Jan 2011 22:15:10 +0000 (23:15 +0100)
committerRyan Dahl <ry@tinyclouds.org>
Mon, 24 Jan 2011 22:52:25 +0000 (14:52 -0800)
This patch introduces a logical structure and sequence for the
bootstrap code found src/node.js.

src/node.js
test/message/undefined_reference_in_new_context.out

index 5e0f12b..a2a5f3f 100644 (file)
+// 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) {
-
   global = this;
-  global.process = process;
-  global.global = global;
-  global.GLOBAL = global;
-  global.root = global;
-
-  /** deprecation errors ************************************************/
-
-  function removed(reason) {
-    return function() {
-      throw new Error(reason);
-    };
-  }
-
-  process.debug =
-      removed('process.debug() use console.error() instead');
-  process.error =
-      removed('process.error() use console.error() instead');
-  process.watchFile =
-      removed('process.watchFile() has moved to fs.watchFile()');
-  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;
-  var runInThisContext = Script.runInThisContext;
+  function startup() {
+    startup.globalVariables();
+    startup.globalTimeouts();
+    startup.globalConsole();
 
-  // lazy loaded.
-  var constants;
-  function lazyConstants() {
-    if (!constants) constants = process.binding('constants');
-    return constants;
-  }
+    startup.processAssert();
+    startup.processNextTick();
+    startup.processStdio();
+    startup.processKillAndExit();
+    startup.processSignalHandlers();
 
+    startup.removedMethods();
 
-  // nextTick()
+    startup.resolveArgv0();
 
-  var nextTickQueue = [];
+    if (startup.runThirdPartyMain()) {
+      return;
+    }
 
-  process._tickCallback = function() {
-    var l = nextTickQueue.length;
-    if (l === 0) return;
+    if (startup.runDebugger()) {
+      return;
+    }
 
-    try {
-      for (var i = 0; i < l; i++) {
-        nextTickQueue[i]();
-      }
+    if (startup.runScript()) {
+      return;
     }
-    catch (e) {
-      nextTickQueue.splice(0, i + 1);
-      if (i + 1 < l) {
-        process._needTickCallback();
-      }
-      throw e; // process.nextTick error, or 'error' event on first tick
+
+    if (startup.runEval()) {
+      return;
     }
 
-    nextTickQueue.splice(0, l);
-  };
+    startup.runRepl();
+  }
 
-  process.nextTick = function(callback) {
-    nextTickQueue.push(callback);
-    process._needTickCallback();
+  startup.globalVariables = function() {
+    global.process = process;
+    global.global = global;
+    global.GLOBAL = global;
+    global.root = global;
+    global.Buffer = NativeModule.require('buffer').Buffer;
   };
 
-  // Native modules don't need a full require function. So we can bootstrap
-  // most of the system with this mini module system.
-  var NativeModule = (function() {
-    function NativeModule(id) {
-      this.filename = id + '.js';
-      this.id = id;
-      this.exports = {};
-      this.loaded = false;
-    }
+  startup.globalTimeouts = function() {
+    global.setTimeout = function() {
+      var t = NativeModule.require('timers');
+      return t.setTimeout.apply(this, arguments);
+    };
 
-    NativeModule._source = process.binding('natives');
-    NativeModule._cache = {};
+    global.setInterval = function() {
+      var t = NativeModule.require('timers');
+      return t.setInterval.apply(this, arguments);
+    };
 
-    NativeModule.require = function(id) {
-      if (id == 'native_module') {
-        return NativeModule;
-      }
+    global.clearTimeout = function() {
+      var t = NativeModule.require('timers');
+      return t.clearTimeout.apply(this, arguments);
+    };
 
-      var cached = NativeModule.getCached(id);
-      if (cached) {
-        return cached.exports;
-      }
+    global.clearInterval = function() {
+      var t = NativeModule.require('timers');
+      return t.clearInterval.apply(this, arguments);
+    };
+  };
 
-      if (!NativeModule.exists(id)) {
-        throw new Error('No such native module ' + id);
-      }
+  startup.globalConsole = function() {
+    global.__defineGetter__('console', function() {
+      return NativeModule.require('console');
+    });
+  };
 
-      var nativeModule = new NativeModule(id);
 
-      nativeModule.compile();
-      nativeModule.cache();
+  startup._lazyConstants = null;
+
+  startup.lazyConstants = function() {
+    if (!startup._lazyConstants) {
+      startup._lazyConstants = process.binding('constants');
+    }
+    return startup._lazyConstants;
+  };
 
-      return nativeModule.exports;
+  startup.processAssert = function() {
+    process.assert = function(x, msg) {
+      if (!x) {
+        throw new Error(msg || 'assertion error');
+      }
     };
+  };
 
-    NativeModule.getCached = function(id) {
-      return NativeModule._cache[id];
-    }
+  startup.processNextTick = function() {
+    var nextTickQueue = [];
 
-    NativeModule.exists = function(id) {
-      return (id in NativeModule._source);
-    }
+    process._tickCallback = function() {
+      var l = nextTickQueue.length;
+      if (l === 0) return;
 
-    NativeModule.getSource = function(id) {
-      return NativeModule._source[id];
-    }
+      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
+      }
 
-    NativeModule.wrap = function(script) {
-      return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
+      nextTickQueue.splice(0, l);
     };
 
-    NativeModule.wrapper = [
-      '(function (exports, require, module, __filename, __dirname) { ',
-      '\n});'
-    ];
+    process.nextTick = function(callback) {
+      nextTickQueue.push(callback);
+      process._needTickCallback();
+    };
+  };
 
-    NativeModule.prototype.compile = function() {
-      var source = NativeModule.getSource(this.id);
-      source = NativeModule.wrap(source);
+  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;
+      }
+
+      return stdout;
+    });
+
+    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 fn = runInThisContext(source, this.filename, true);
-      fn(this.exports, NativeModule.require, this, this.filename);
+      return stdin;
+    });
 
-      this.loaded = true;
+    process.openStdin = function() {
+      process.stdin.resume();
+      return process.stdin;
     };
+  };
 
-    NativeModule.prototype.cache = function() {
-      NativeModule._cache[this.id] = this;
+  startup.processKillAndExit = function() {
+    process.exit = function(code) {
+      process.emit('exit', code || 0);
+      process.reallyExit(code || 0);
     };
 
-    return NativeModule;
-  })();
+    process.kill = function(pid, sig) {
+      sig = sig || 'SIGTERM';
 
-  var Module = NativeModule.require('module').Module;
+      if (!startup.lazyConstants()[sig]) {
+        throw new Error('Unknown signal: ' + sig);
+      }
 
-  // Load events module in order to access prototype elements on process like
-  // process.addListener.
-  var events = NativeModule.require('events');
+      process._kill(pid, startup.lazyConstants()[sig]);
+    };
+  };
 
-  // Signal Handlers
-  (function() {
+  startup.processSignalHandlers = function() {
+    // Load events module in order to access prototype elements on process like
+    // process.addListener.
+    var events = NativeModule.require('events');
     var signalWatchers = {};
     var addListener = process.addListener;
     var removeListener = process.removeListener;
 
     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
       if (isSignal(type)) {
         if (!signalWatchers.hasOwnProperty(type)) {
           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); };
           signalWatchers[type] = w;
           w.start();
 
       return ret;
     };
-  })();
-
-
-  global.setTimeout = function() {
-    var t = NativeModule.require('timers');
-    return t.setTimeout.apply(this, arguments);
   };
 
-  global.setInterval = function() {
-    var t = NativeModule.require('timers');
-    return t.setInterval.apply(this, arguments);
+  startup._removedProcessMethods = {
+    'debug': 'process.debug() use console.error() instead',
+    '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() {
-    var t = NativeModule.require('timers');
-    return t.clearTimeout.apply(this, arguments);
+  startup.removedMethods = function() {
+    for (var method in startup._removedProcessMethods) {
+      var reason = startup._removedProcessMethods[method];
+      process[method] = startup._removedMethod(reason);
+    }
   };
 
-  global.clearInterval = function() {
-    var t = NativeModule.require('timers');
-    return t.clearInterval.apply(this, arguments);
+  startup._removedMethod = function(reason) {
+    return function() {
+      throw new Error(reason);
+    };
   };
 
+  startup.resolveArgv0 = function() {
+    var cwd = process.cwd();
+    var isWindows = process.platform === 'win32';
+
+    // Make process.argv[0] into a full path, but only touch argv[0] if it's
+    // not a system $PATH lookup.
+    // 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) !== '/') {
+      var path = NativeModule.require('path');
+      process.argv[0] = path.join(cwd, process.argv[0]);
+    }
+  };
 
-  var stdout, stdin;
+  startup.runThirdPartyMain = function() {
+    // 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')) {
+      return;
+    }
 
+    process.nextTick(function() {
+      NativeModule.require('_third_party_main');
+    });
+    return true;
+  };
 
-  process.__defineGetter__('stdout', function() {
-    if (stdout) return stdout;
+  startup.runDebugger = function() {
+    if (!(process.argv[1] == 'debug')) {
+      return;
+    }
 
-    var binding = process.binding('stdio'),
-        net = NativeModule.require('net'),
-        fs = NativeModule.require('fs'),
-        tty = NativeModule.require('tty'),
-        fd = binding.stdoutFD;
+    // Start the debugger agent
+    var d = NativeModule.require('_debugger');
+    d.start();
+    return true;
+  };
 
-    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;
+  startup.runScript = function() {
+    if (!process.argv[1]) {
+      return;
     }
 
-    return stdout;
-  });
+    // make process.argv[1] into a full path
+    if (!(/^http:\/\//).exec(process.argv[1])) {
+      var path = NativeModule.require('path');
+      process.argv[1] = path.resolve(process.argv[1]);
+    }
 
+    var Module = NativeModule.require('module');
 
-  process.__defineGetter__('stdin', function() {
-    if (stdin) return stdin;
+    // REMOVEME: nextTick should not be necessary. This hack to get
+    // test/simple/test-exception-handler2.js working.
+    process.nextTick(Module.runMain);
 
-    var binding = process.binding('stdio'),
-        net = NativeModule.require('net'),
-        fs = NativeModule.require('fs'),
-        tty = NativeModule.require('tty'),
-        fd = binding.openStdin();
+    return true;
+  };
 
-    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;
+  startup.runEval = function() {
+    // -e, --eval
+    if (!process._eval) {
+      return;
     }
 
-    return stdin;
-  });
+    var Module = NativeModule.require('module');
 
+    var rv = new Module()._compile('return eval(process._eval)', 'eval');
+    console.log(rv);
+    return true;
+  };
 
-  process.openStdin = function() {
-    process.stdin.resume();
-    return process.stdin;
+  startup.runRepl = function() {
+    var Module = NativeModule.require('module');
+    // REPL
+    Module.requireRepl().start();
   };
 
 
-  // Lazy load console object
-  global.__defineGetter__('console', function() {
-    return NativeModule.require('console');
-  });
+  // 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;
 
-  global.Buffer = NativeModule.require('buffer').Buffer;
+  function NativeModule(id) {
+    this.filename = id + '.js';
+    this.id = id;
+    this.exports = {};
+    this.loaded = false;
+  }
 
-  process.exit = function(code) {
-    process.emit('exit', code || 0);
-    process.reallyExit(code || 0);
-  };
+  NativeModule._source = process.binding('natives');
+  NativeModule._cache = {};
 
-  process.kill = function(pid, sig) {
-    sig = sig || 'SIGTERM';
-    if (!lazyConstants()[sig]) throw new Error('Unknown signal: ' + sig);
-    process._kill(pid, lazyConstants()[sig]);
-  };
+  NativeModule.require = function(id) {
+    if (id == 'native_module') {
+      return NativeModule;
+    }
 
+    var cached = NativeModule.getCached(id);
+    if (cached) {
+      return cached.exports;
+    }
 
-  var cwd = process.cwd();
-  var path = NativeModule.require('path');
-  var isWindows = process.platform === 'win32';
+    if (!NativeModule.exists(id)) {
+      throw new Error('No such native module ' + id);
+    }
 
-  // Make process.argv[0] and process.argv[1] into full paths, but only
-  // touch argv[0] if it's not a system $PATH lookup.
-  // 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]);
-  }
+    var nativeModule = new NativeModule(id);
 
-  // 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;
-  }
+    nativeModule.compile();
+    nativeModule.cache();
 
-  if (process.argv[1]) {
-    if (process.argv[1] == 'debug') {
-      // Start the debugger agent
-      var d = NativeModule.require('_debugger');
-      d.start();
-      return;
-    }
+    return nativeModule.exports;
+  };
 
-    // Load Module
-    // make process.argv[1] into a full path
-    if (!(/^http:\/\//).exec(process.argv[1])) {
-      process.argv[1] = path.resolve(process.argv[1]);
-    }
-    // REMOVEME: nextTick should not be necessary. This hack to get
-    // test/simple/test-exception-handler2.js working.
-    process.nextTick(Module.runMain);
-    return;
+  NativeModule.getCached = function(id) {
+    return NativeModule._cache[id];
   }
 
-  if (process._eval) {
-    // -e, --eval
-    var rv = new Module()._compile('return eval(process._eval)', 'eval');
-    console.log(rv);
-    return;
+  NativeModule.exists = function(id) {
+    return (id in NativeModule._source);
   }
 
-  // REPL
-  Module.requireRepl().start();
+  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();
 });
index 0c6cdf5..7237047 100644 (file)
@@ -1,8 +1,8 @@
 before
 
 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
     at evalmachine.<anonymous>:*
     at Object.<anonymous> (*test*message*undefined_reference_in_new_context.js:*)