From 860d008d5418cecd48c25369f2f8709a1f7ffb5c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Feb 2010 09:37:20 -0800 Subject: [PATCH] Truly synchronous require() This is to reduce our dependency on wait(). For some reason this patch affects the timer test: % ./node test/mjsunit/test-timers.js diff: 989 diff: 989 diff: 1989 diff: 2989 Previously it showed: % ./node test/mjsunit/test-timers.js diff: 1000 diff: 1000 diff: 2000 diff: 3000 I'm not sure what caused this change, and it's rather disturbing. However I want to remove wait() as soon as possible and so am pushing this patch through. The module loading code is becoming increasingly ugly - this patch has not helped. A refactor needs to be done soon. --- src/node.js | 199 ++++++++++++++++----- test/mjsunit/test-remote-module-loading.js | 10 +- 2 files changed, 156 insertions(+), 53 deletions(-) diff --git a/src/node.js b/src/node.js index 0d3da4c8e0..17a994020f 100644 --- a/src/node.js +++ b/src/node.js @@ -857,6 +857,16 @@ var pathModule = createInternalModule("path", function (exports) { var path = pathModule.exports; +function existsSync (path) { + try { + fs.statSync(path); + return true; + } catch (e) { + return false; + } +} + + process.paths = [ path.join(process.installPrefix, "lib/node/libraries") ]; @@ -870,11 +880,16 @@ if (process.env["NODE_PATH"]) { } +/* Sync unless callback given */ function findModulePath (id, dirs, callback) { process.assert(dirs.constructor == Array); if (/^https?:\/\//.exec(id)) { - callback(id); + if (callback) { + callback(id); + } else { + throw new Error("Sync http require not allowed."); + } return; } @@ -883,8 +898,11 @@ function findModulePath (id, dirs, callback) { } if (dirs.length == 0) { - callback(); - return; + if (callback) { + callback(); + } else { + return; // sync returns null + } } var dir = dirs[0]; @@ -902,26 +920,37 @@ function findModulePath (id, dirs, callback) { path.join(dir, id, "index.addon") ]; - var searchLocations = function() { + function searchLocations () { var location = locations.shift(); - if (location === undefined) { - findModulePath(id, rest, callback); - return; + if (!location) { + return findModulePath(id, rest, callback); } - path.exists(location, function (found) { - if (found) { - callback(location); - return; + // if async + if (callback) { + path.exists(location, function (found) { + if (found) { + callback(location); + } else { + return searchLocations(); + } + }) + + // if sync + } else { + if (existsSync(location)) { + return location; + } else { + return searchLocations(); } - searchLocations(); - }); - }; - searchLocations(); + } + } + return searchLocations(); } -function resolveModulePath(request, parent) { +// sync - no i/o performed +function resolveModulePath(request, parent) { var id, paths; if (request.charAt(0) == "." && (request.charAt(1) == "/" || request.charAt(1) == ".")) { // Relative request @@ -939,6 +968,33 @@ function resolveModulePath(request, parent) { return [id, paths]; } + +function loadModuleSync (request, parent) { + var resolvedModule = resolveModulePath(request, parent); + var id = resolvedModule[0]; + var paths = resolvedModule[1]; + + debug("loadModuleSync REQUEST " + (request) + " parent: " + parent.id); + + var cachedModule = internalModuleCache[id] || parent.moduleCache[id]; + + if (cachedModule) { + debug("found " + JSON.stringify(id) + " in cache"); + return cachedModule.exports; + } else { + debug("looking for " + JSON.stringify(id) + " in " + JSON.stringify(paths)); + var filename = findModulePath(request, paths); + if (!filename) { + throw new Error("Cannot find module '" + request + "'"); + } else { + var module = new Module(id, parent); + module.loadSync(filename); + return module.exports; + } + } +} + + function loadModule (request, parent) { var // The promise returned from require.async() @@ -947,7 +1003,7 @@ function loadModule (request, parent) { id = resolvedModule[0], paths = resolvedModule[1]; - // debug("loadModule REQUEST " + (request) + " parent: " + JSON.stringify(parent)); + debug("loadModule REQUEST " + (request) + " parent: " + parent.id); var cachedModule = internalModuleCache[id] || parent.moduleCache[id]; if (cachedModule) { @@ -971,6 +1027,21 @@ function loadModule (request, parent) { return loadPromise; }; + +Module.prototype.loadSync = function (filename) { + debug("loadSync " + JSON.stringify(filename) + " for module " + JSON.stringify(this.id)); + + process.assert(!this.loaded); + this.filename = filename; + + if (filename.match(/\.node$/)) { + this._loadObjectSync(filename); + } else { + this._loadScriptSync(filename); + } +}; + + Module.prototype.load = function (filename, loadPromise) { debug("load " + JSON.stringify(filename) + " for module " + JSON.stringify(this.id)); @@ -981,13 +1052,20 @@ Module.prototype.load = function (filename, loadPromise) { this.filename = filename; if (filename.match(/\.node$/)) { - this.loadObject(filename, loadPromise); + this._loadObject(filename, loadPromise); } else { - this.loadScript(filename, loadPromise); + this._loadScript(filename, loadPromise); } }; -Module.prototype.loadObject = function (filename, loadPromise) { + +Module.prototype._loadObjectSync = function (filename) { + this.loaded = true; + process.dlopen(filename, this.exports); +}; + + +Module.prototype._loadObject = function (filename, loadPromise) { var self = this; // XXX Not yet supporting loading from HTTP. would need to download the // file, store it to tmp then run dlopen on it. @@ -998,11 +1076,10 @@ Module.prototype.loadObject = function (filename, loadPromise) { }); }; + function cat (id, loadPromise) { var promise; - debug(id); - if (id.match(/^http:\/\//)) { promise = new events.Promise(); loadModule('http', process.mainModule) @@ -1025,7 +1102,52 @@ function cat (id, loadPromise) { return promise; } -Module.prototype.loadScript = function (filename, loadPromise) { + +Module.prototype._loadContent = function (content, filename) { + var self = this; + // remove shebang + content = content.replace(/^\#\!.*/, ''); + + function requireAsync (url) { + return loadModule(url, self); // new child + } + + function require (path) { + return loadModuleSync(path, self); + } + + require.paths = process.paths; + require.async = requireAsync; + require.main = process.mainModule; + // create wrapper function + var wrapper = "(function (exports, require, module, __filename, __dirname) { " + + content + + "\n});"; + + try { + var compiledWrapper = process.compile(wrapper, filename); + compiledWrapper.apply(self.exports, [self.exports, require, self, filename, path.dirname(filename)]); + } catch (e) { + return e; + } +}; + + +Module.prototype._loadScriptSync = function (filename) { + var content = fs.readFileSync(filename); + // remove shebang + content = content.replace(/^\#\!.*/, ''); + + var e = this._loadContent(content, filename); + if (e) { + throw e; + } else { + this.loaded = true; + } +}; + + +Module.prototype._loadScript = function (filename, loadPromise) { var self = this; var catPromise = cat(filename, loadPromise); @@ -1034,41 +1156,20 @@ Module.prototype.loadScript = function (filename, loadPromise) { }); catPromise.addCallback(function (content) { - // remove shebang - content = content.replace(/^\#\!.*/, ''); - - function requireAsync (url) { - return loadModule(url, self); // new child - } - - function require (url) { - return requireAsync(url).wait(); - } - - require.paths = process.paths; - require.async = requireAsync; - require.main = process.mainModule; - // create wrapper function - var wrapper = "(function (exports, require, module, __filename, __dirname) { " - + content - + "\n});"; - - try { - var compiledWrapper = process.compile(wrapper, filename); - compiledWrapper.apply(self.exports, [self.exports, require, self, filename, path.dirname(filename)]); - } catch (e) { + var e = self._loadContent(content, filename); + if (e) { loadPromise.emitError(e); return; } - - self.waitChildrenLoad(function () { + self._waitChildrenLoad(function () { self.loaded = true; loadPromise.emitSuccess(self.exports); }); }); }; -Module.prototype.waitChildrenLoad = function (callback) { + +Module.prototype._waitChildrenLoad = function (callback) { var nloaded = 0; var children = this.children; for (var i = 0; i < children.length; i++) { diff --git a/test/mjsunit/test-remote-module-loading.js b/test/mjsunit/test-remote-module-loading.js index 2978f72add..6f6f82e080 100644 --- a/test/mjsunit/test-remote-module-loading.js +++ b/test/mjsunit/test-remote-module-loading.js @@ -17,9 +17,11 @@ var server = http.createServer(function(req, res) { }); server.listen(PORT); -var httpModule = require('http://localhost:'+PORT+'/moduleA.js'); -assert.equal('/moduleA.js', httpModule.httpPath()); -modulesLoaded++; +assert.throws(function () { + var httpModule = require('http://localhost:'+PORT+'/moduleA.js'); + assert.equal('/moduleA.js', httpModule.httpPath()); + modulesLoaded++; +}); var nodeBinary = process.ARGV[0]; var cmd = 'NODE_PATH='+libDir+' '+nodeBinary+' http://localhost:'+PORT+'/moduleB.js'; @@ -35,5 +37,5 @@ sys }); process.addListener('exit', function() { - assert.equal(2, modulesLoaded); + assert.equal(1, modulesLoaded); });