From 61785afb3d22133fb1968ac7c0274999d4e432eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Tue, 2 Mar 2010 23:12:52 +0100 Subject: [PATCH 01/31] Initial write stream implementation --- lib/fs.js | 87 +++++++++++++++++++++++++++ test/simple/test-file-write-stream.js | 47 +++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 test/simple/test-file-write-stream.js diff --git a/lib/fs.js b/lib/fs.js index 5687424dc2..02701f687f 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -375,3 +375,90 @@ exports.realpath = function(path, callback) { callback(null, normalize(path)); }); } + +exports.fileWriteStream = function(path, options) { + return new FileWriteStream(path, options); +}; + +var FileWriteStream = exports.FileWriteStream = function(path, options) { + this.path = path; + this.fd = null; + this.closed = false; + + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 0666; + + process.mixin(this, options || {}); + + var + self = this, + queue = [], + busy = false; + + queue.push([fs.open, this.path, this.flags, this.mode]); + + function pump() { + if (busy) { + return; + } + + var args = queue.shift(); + if (!args) { + return self.emit('drain'); + } + + busy = true; + + var method = args.shift(); + + args.push(function(err) { + busy = false; + + if (err) { + self.emit('error', err); + return; + } + + // save reference for file pointer + if (method === fs.open) { + self.fd = arguments[1]; + self.emit('open', self.fd); + } + + // stop pumping after close + if (method === fs.close) { + self.emit('close'); + return; + } + + pump(); + }); + + // Inject the file pointer + if (method !== fs.open) { + args.unshift(self.fd); + } + + method.apply(null, args); + }; + + this.write = function(data) { + if (this.closed) { + throw new Error('stream already closed'); + } + + queue.push([fs.write, data, undefined, this.encoding]); + pump(); + return false; + }; + + this.close = function() { + this.closed = true; + queue.push([fs.close,]); + pump(); + }; + + pump(); +}; +FileWriteStream.prototype.__proto__ = process.EventEmitter.prototype; diff --git a/test/simple/test-file-write-stream.js b/test/simple/test-file-write-stream.js new file mode 100644 index 0000000000..669985e178 --- /dev/null +++ b/test/simple/test-file-write-stream.js @@ -0,0 +1,47 @@ +process.mixin(require('../common')); + +var + fn = path.join(fixturesDir, "write.txt"), + file = fs.fileWriteStream(fn), + + EXPECTED = '0123456789', + + callbacks = { + open: -1, + drain: -2, + close: -1 + }; + +file + .addListener('open', function(fd) { + callbacks.open++; + assert.equal('number', typeof fd); + }) + .addListener('drain', function() { + callbacks.drain++; + if (callbacks.drain == -1) { + assert.equal(EXPECTED, fs.readFileSync(fn)); + file.write(EXPECTED); + } else if (callbacks.drain == 0) { + assert.equal(EXPECTED+EXPECTED, fs.readFileSync(fn)); + file.close(); + } + }) + .addListener('close', function() { + callbacks.close++; + assert.throws(function() { + file.write('should not work anymore'); + }); + + fs.unlinkSync(fn); + }); + +for (var i = 0; i < 10; i++) { + assert.strictEqual(false, file.write(i)); +} + +process.addListener('exit', function() { + for (var k in callbacks) { + assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); + } +}); \ No newline at end of file From 18a70ffda13d3692aee34c097ad9330756d6479b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Tue, 2 Mar 2010 23:28:00 +0100 Subject: [PATCH 02/31] Tweaks - Add 'writeable' property - Renamed pump->flush - Use sys.mixin instead of process.mixin --- lib/fs.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 02701f687f..00f6168c15 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1,3 +1,5 @@ +var sys = require('sys'); + exports.Stats = process.Stats; process.Stats.prototype._checkModeProperty = function (property) { @@ -383,13 +385,13 @@ exports.fileWriteStream = function(path, options) { var FileWriteStream = exports.FileWriteStream = function(path, options) { this.path = path; this.fd = null; - this.closed = false; + this.writeable = true; this.flags = 'w'; this.encoding = 'binary'; this.mode = 0666; - process.mixin(this, options || {}); + sys.mixin(this, options || {}); var self = this, @@ -398,7 +400,7 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { queue.push([fs.open, this.path, this.flags, this.mode]); - function pump() { + function flush() { if (busy) { return; } @@ -416,6 +418,7 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { busy = false; if (err) { + self.writeable = false; self.emit('error', err); return; } @@ -426,13 +429,13 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { self.emit('open', self.fd); } - // stop pumping after close + // stop flushing after close if (method === fs.close) { self.emit('close'); return; } - pump(); + flush(); }); // Inject the file pointer @@ -444,21 +447,21 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { }; this.write = function(data) { - if (this.closed) { - throw new Error('stream already closed'); + if (!this.writeable) { + throw new Error('stream not writeable'); } queue.push([fs.write, data, undefined, this.encoding]); - pump(); + flush(); return false; }; this.close = function() { - this.closed = true; + this.writeable = false; queue.push([fs.close,]); - pump(); + flush(); }; - pump(); + flush(); }; -FileWriteStream.prototype.__proto__ = process.EventEmitter.prototype; +FileWriteStream.prototype.__proto__ = process.EventEmitter.prototype; \ No newline at end of file From 9be3df08283af081668c4cdfdd4297c5d15cbd04 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 2 Mar 2010 17:35:01 -0800 Subject: [PATCH 03/31] Add sys.log() --- doc/api.txt | 2 ++ lib/sys.js | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/doc/api.txt b/doc/api.txt index 73f4f54c2e..9466fc6e45 100644 --- a/doc/api.txt +++ b/doc/api.txt @@ -187,6 +187,8 @@ Like +puts()+ but without the trailing new-line. A synchronous output function. Will block the process and output +string+ immediately to +stdout+. ++log(string)+:: +Output with timestamp. +inspect(object, showHidden, depth)+ :: diff --git a/lib/sys.js b/lib/sys.js index 5ad68e4926..a828e5cef5 100644 --- a/lib/sys.js +++ b/lib/sys.js @@ -188,6 +188,25 @@ exports.p = function () { } }; +function pad (n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp () { + var d = new Date(); + return [ d.getDate() + , months[d.getMonth()] + , [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(':') + ].join(' '); +} + +exports.log = function (msg) { + exports.puts(timestamp() + ' - ' + msg.toString()); +} + exports.exec = function (command, callback) { var child = process.createChildProcess("/bin/sh", ["-c", command]); var stdout = ""; From 5c602b750a64136cff86c6ffe8cf06a568c12717 Mon Sep 17 00:00:00 2001 From: Rasmus Andersson Date: Wed, 3 Mar 2010 02:08:53 +0100 Subject: [PATCH 04/31] Rewrote realpath implementation solving all known failing tests (also added a bunch of new test cases) --- lib/fs.js | 190 +++++++++++++-------- test/simple/test-fs-realpath.js | 289 ++++++++++++++++++++++++++------ 2 files changed, 360 insertions(+), 119 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 5687424dc2..3fd95de4d5 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -292,86 +292,132 @@ exports.unwatchFile = function (filename) { // Realpath var path = require('path'); -var dirname = path.dirname, - basename = path.basename, - normalize = path.normalize; - -function readlinkDeepSync(path, stats) { - var seen_links = {}, resolved_link, stats, file_id; - while (true) { - stats = stats || exports.lstatSync(path); - file_id = stats.dev.toString(32)+":"+stats.ino.toString(32); - if (file_id in seen_links) { - throw new Error("cyclic symbolic link at "+path); - } else { - seen_links[file_id] = 1; - if (stats.isSymbolicLink()) { - var newpath = exports.readlinkSync(path); - if (newpath.charAt(0) === '/') { - path = newpath; +var normalize = path.normalize + normalizeArray = path.normalizeArray; + +exports.realpathSync = function (path) { + var seen_links = {}, knownHards = {}, buf, i = 0, part, x, stats; + if (path.charAt(0) !== '/') { + var cwd = process.cwd().split('/'); + path = cwd.concat(path.split('/')); + path = normalizeArray(path); + i = cwd.length; + buf = [].concat(cwd); + } else { + path = normalizeArray(path.split('/')); + buf = ['']; + } + for (; i 0) buf.splice(y, delta); + } else { + i--; + } + } } else { - var dir = dirname(path); - path = (dir !== '') ? dir + '/' + newpath : newpath; + buf.push(path[i]); + knownHards[buf.join('/')] = true; } - } else { - return normalize(path); } } - stats = null; } + return buf.join('/'); } -function readlinkDeep(path, stats, callback) { - var seen_links = {}, resolved_link, file_id; - function next(stats) { - file_id = stats.dev.toString(32)+":"+stats.ino.toString(32); - if (file_id in seen_links) { - callback(new Error("cyclic symbolic link at "+path)); - } else { - seen_links[file_id] = 1; - if (stats.isSymbolicLink()) { - exports.readlink(path, function(err, newpath) { - if (err) callback(err); - if (newpath.charAt(0) === '/') { - path = newpath; - } else { - var dir = dirname(path); - path = (dir !== '') ? dir + '/' + newpath : newpath; - } - _next(); - }); - } else { - callback(null, normalize(path)); - } + +exports.realpath = function (path, callback) { + var seen_links = {}, knownHards = {}, buf = [''], i = 0, part, x; + if (path.charAt(0) !== '/') { + // assumes cwd is canonical + var cwd = process.cwd().split('/'); + path = cwd.concat(path.split('/')); + path = normalizeArray(path); + i = cwd.length-1; + buf = [].concat(cwd); + } else { + path = normalizeArray(path.split('/')); + } + function done(err) { + if (callback) { + if (!err) callback(err, buf.join('/')); + else callback(err); } } - function _next() { - exports.lstat(path, function(err, stats){ - if (err) callback(err); - else next(stats); - }); + function next() { + if (++i === path.length) return done(); + part = path.slice(0, i+1).join('/'); + if (part.length === 0) return next(); + if (part in knownHards) { + buf.push(path[i]); + next(); + } else { + exports.lstat(part, function(err, stats){ + if (err) return done(err); + if (stats.isSymbolicLink()) { + x = stats.dev.toString(32)+":"+stats.ino.toString(32); + if (x in seen_links) + return done(new Error("cyclic link at "+part)); + seen_links[x] = true; + exports.readlink(part, function(err, npart){ + if (err) return done(err); + part = npart; + if (part.charAt(0) === '/') { + // absolute + path = normalizeArray(part.split('/')); + buf = ['']; + i = 0; + } else { + // relative + Array.prototype.splice.apply(path, [i, 1].concat(part.split('/'))); + part = normalizeArray(path); + var y = 0, L = Math.max(path.length, part.length), delta; + for (; y 0) buf.splice(y, delta); + } + else { + i--; // resolve new node if needed + } + } + next(); + }); // fs.readlink + } + else { + buf.push(path[i]); + knownHards[buf.join('/')] = true; + next(); + } + }); // fs.lstat + } } - if (stats) next(stats); - else _next(); -} - -exports.realpathSync = function(path) { - var stats = exports.lstatSync(path); - if (stats.isSymbolicLink()) - return readlinkDeepSync(path, stats); - else - return normalize(path); -} - -exports.realpath = function(path, callback) { - var resolved_path = path; - if (!callback) return; - exports.lstat(path, function(err, stats){ - if (err) - callback(err); - else if (stats.isSymbolicLink()) - readlinkDeep(path, stats, callback); - else - callback(null, normalize(path)); - }); + next(); } diff --git a/test/simple/test-fs-realpath.js b/test/simple/test-fs-realpath.js index 384faebf4f..d6c1c3fc89 100644 --- a/test/simple/test-fs-realpath.js +++ b/test/simple/test-fs-realpath.js @@ -1,56 +1,251 @@ process.mixin(require("../common")); -var async_completed = 0, async_expected = 0; - -// a. deep relative file symlink -var dstPath = path.join(fixturesDir, 'cycles', 'root.js'); -var linkData1 = "../../cycles/root.js"; -var linkPath1 = path.join(fixturesDir, "nested-index", 'one', 'symlink1.js'); -try {fs.unlinkSync(linkPath1);}catch(e){} -fs.symlinkSync(linkData1, linkPath1); - -var linkData2 = "../one/symlink1.js"; -var linkPath2 = path.join(fixturesDir, "nested-index", 'two', 'symlink1-b.js'); -try {fs.unlinkSync(linkPath2);}catch(e){} -fs.symlinkSync(linkData2, linkPath2); - -// b. deep relative directory symlink -var dstPath_b = path.join(fixturesDir, 'cycles', 'folder'); -var linkData1b = "../../cycles/folder"; -var linkPath1b = path.join(fixturesDir, "nested-index", 'one', 'symlink1-dir'); -try {fs.unlinkSync(linkPath1b);}catch(e){} -fs.symlinkSync(linkData1b, linkPath1b); - -var linkData2b = "../one/symlink1-dir"; -var linkPath2b = path.join(fixturesDir, "nested-index", 'two', 'symlink12-dir'); -try {fs.unlinkSync(linkPath2b);}catch(e){} -fs.symlinkSync(linkData2b, linkPath2b); - -assert.equal(fs.realpathSync(linkPath2), dstPath); -assert.equal(fs.realpathSync(linkPath2b), dstPath_b); - -async_expected++; -fs.realpath(linkPath2, function(err, rpath) { - if (err) throw err; - assert.equal(rpath, dstPath); - async_completed++; -}); +var async_completed = 0, async_expected = 0, unlink = []; -async_expected++; -fs.realpath(linkPath2b, function(err, rpath) { - if (err) throw err; - assert.equal(rpath, dstPath_b); - async_completed++; -}); +function asynctest(testBlock, args, callback, assertBlock) { + async_expected++; + testBlock.apply(testBlock, args.concat([function(err){ + var ignoreError = false; + if (assertBlock) { + try { + ignoreError = assertBlock.apply(assertBlock, + Array.prototype.slice.call(arguments)); + } + catch (e) { + err = e; + } + } + async_completed++; + callback(ignoreError ? null : err); + }])); +} -// todo: test shallow symlinks (file & dir) -// todo: test non-symlinks (file & dir) -// todo: test error on cyclic symlinks +function bashRealpath(path, callback) { + exec("cd '"+path.replace("'","\\'")+"' && pwd -P",function (err, o) { + callback(err, o.trim()); + }); +} -process.addListener("exit", function () { +// sub-tests: + +function test_simple_relative_symlink(callback) { + var entry = fixturesDir+'/cycles/symlink', + expected = fixturesDir+'/cycles/root.js'; + [ + [entry, 'root.js'], + ].forEach(function(t) { + try {fs.unlinkSync(t[0]);}catch(e){} + fs.symlinkSync(t[1], t[0]); + unlink.push(t[0]); + }); + var result = fs.realpathSync(entry); + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + asynctest(fs.realpath, [entry], callback, function(err, result){ + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + }); +} + +function test_simple_absolute_symlink(callback) { + bashRealpath(fixturesDir, function(err, fixturesAbsDir) { + if (err) return callback(err); + var entry = fixturesAbsDir+'/cycles/symlink', + expected = fixturesAbsDir+'/nested-index/one/index.js'; + [ + [entry, expected], + ].forEach(function(t) { + try {fs.unlinkSync(t[0]);}catch(e){} + fs.symlinkSync(t[1], t[0]); + unlink.push(t[0]); + }); + var result = fs.realpathSync(entry); + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + asynctest(fs.realpath, [entry], callback, function(err, result){ + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + }); + }); +} + +function test_deep_relative_file_symlink(callback) { + var expected = path.join(fixturesDir, 'cycles', 'root.js'); + var linkData1 = "../../cycles/root.js"; + var linkPath1 = path.join(fixturesDir, "nested-index", 'one', 'symlink1.js'); try {fs.unlinkSync(linkPath1);}catch(e){} - try {fs.unlinkSync(linkPath2);}catch(e){} + fs.symlinkSync(linkData1, linkPath1); + + var linkData2 = "../one/symlink1.js"; + var entry = path.join(fixturesDir, "nested-index", 'two', 'symlink1-b.js'); + try {fs.unlinkSync(entry);}catch(e){} + fs.symlinkSync(linkData2, entry); + unlink.push(linkPath1); + unlink.push(entry); + + assert.equal(fs.realpathSync(entry), expected); + asynctest(fs.realpath, [entry], callback, function(err, result){ + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + }); +} + +function test_deep_relative_dir_symlink(callback) { + var expected = path.join(fixturesDir, 'cycles', 'folder'); + var linkData1b = "../../cycles/folder"; + var linkPath1b = path.join(fixturesDir, "nested-index", 'one', 'symlink1-dir'); try {fs.unlinkSync(linkPath1b);}catch(e){} - try {fs.unlinkSync(linkPath2b);}catch(e){} + fs.symlinkSync(linkData1b, linkPath1b); + + var linkData2b = "../one/symlink1-dir"; + var entry = path.join(fixturesDir, "nested-index", 'two', 'symlink12-dir'); + try {fs.unlinkSync(entry);}catch(e){} + fs.symlinkSync(linkData2b, entry); + unlink.push(linkPath1b); + unlink.push(entry); + + assert.equal(fs.realpathSync(entry), expected); + + asynctest(fs.realpath, [entry], callback, function(err, result){ + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + }); +} + +function test_cyclic_link_protection(callback) { + var entry = fixturesDir+'/cycles/realpath-3a'; + [ + [entry, '../cycles/realpath-3b'], + [fixturesDir+'/cycles/realpath-3b', '../cycles/realpath-3c'], + [fixturesDir+'/cycles/realpath-3c', '../cycles/realpath-3a'], + ].forEach(function(t) { + try {fs.unlinkSync(t[0]);}catch(e){} + fs.symlinkSync(t[1], t[0]); + unlink.push(t[0]); + }); + assert.throws(function(){ fs.realpathSync(entry); }); + asynctest(fs.realpath, [entry], callback, function(err, result){ + assert.ok(err && true); + return true; + }); +} + +function test_relative_input_cwd(callback) { + var p = fixturesDir.lastIndexOf('/'); + var entrydir = fixturesDir.substr(0, p); + var entry = fixturesDir.substr(p+1)+'/cycles/realpath-3a'; + var expected = fixturesDir+'/cycles/root.js'; + [ + [entry, '../cycles/realpath-3b'], + [fixturesDir+'/cycles/realpath-3b', '../cycles/realpath-3c'], + [fixturesDir+'/cycles/realpath-3c', 'root.js'], + ].forEach(function(t) { + var fn = t[0]; + if (fn.charAt(0) !== '/') fn = entrydir + '/' + fn; + try {fs.unlinkSync(fn);}catch(e){} + fs.symlinkSync(t[1], fn); + unlink.push(fn); + }); + var origcwd = process.cwd(); + process.chdir(entrydir); + assert.equal(fs.realpathSync(entry), expected); + asynctest(fs.realpath, [entry], callback, function(err, result){ + process.chdir(origcwd); + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + return true; + }); +} + +function test_deep_symlink_mix(callback) { + // todo: check to see that fixturesDir is not rooted in the + // same directory as our test symlink. + // obtain our current realpath using bash (so we can test ourselves) + bashRealpath(fixturesDir, function(err, fixturesAbsDir) { + if (err) return callback(err); + /* + /tmp/node-test-realpath-f1 -> ../tmp/node-test-realpath-d1/foo + /tmp/node-test-realpath-d1 -> ../node-test-realpath-d2 + /tmp/node-test-realpath-d2/foo -> ../node-test-realpath-f2 + /tmp/node-test-realpath-f2 + -> /node/test/fixtures/nested-index/one/realpath-c + /node/test/fixtures/nested-index/one/realpath-c + -> /node/test/fixtures/nested-index/two/realpath-c + /node/test/fixtures/nested-index/two/realpath-c -> ../../cycles/root.js + /node/test/fixtures/cycles/root.js (hard) + */ + var entry = '/tmp/node-test-realpath-f1'; + try {fs.unlinkSync('/tmp/node-test-realpath-d2/foo');}catch(e){} + try {fs.rmdirSync('/tmp/node-test-realpath-d2');}catch(e){} + fs.mkdirSync('/tmp/node-test-realpath-d2', 0700); + try { + [ + [entry, '../tmp/node-test-realpath-d1/foo'], + ['/tmp/node-test-realpath-d1', '../tmp/node-test-realpath-d2'], + ['/tmp/node-test-realpath-d2/foo', '../node-test-realpath-f2'], + ['/tmp/node-test-realpath-f2', fixturesAbsDir+'/nested-index/one/realpath-c'], + [fixturesAbsDir+'/nested-index/one/realpath-c', fixturesAbsDir+'/nested-index/two/realpath-c'], + [fixturesAbsDir+'/nested-index/two/realpath-c', '../../cycles/root.js'], + ].forEach(function(t) { + //debug('setting up '+t[0]+' -> '+t[1]); + try {fs.unlinkSync(t[0]);}catch(e){} + fs.symlinkSync(t[1], t[0]); + unlink.push(t[0]); + }); + } finally { + unlink.push('/tmp/node-test-realpath-d2'); + } + var expected = fixturesAbsDir+'/cycles/root.js'; + assert.equal(fs.realpathSync(entry), expected); + asynctest(fs.realpath, [entry], callback, function(err, result){ + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + return true; + }); + }); +} + +function test_non_symlinks(callback) { + bashRealpath(fixturesDir, function(err, fixturesAbsDir) { + if (err) return callback(err); + var p = fixturesAbsDir.lastIndexOf('/'); + var entrydir = fixturesAbsDir.substr(0, p); + var entry = fixturesAbsDir.substr(p+1)+'/cycles/root.js'; + var expected = fixturesAbsDir+'/cycles/root.js'; + var origcwd = process.cwd(); + process.chdir(entrydir); + assert.equal(fs.realpathSync(entry), expected); + asynctest(fs.realpath, [entry], callback, function(err, result){ + process.chdir(origcwd); + assert.equal(result, expected, + 'got '+inspect(result)+' expected '+inspect(expected)); + return true; + }); + }); +} + +// ---------------------------------------------------------------------------- + +var tests = [ + test_simple_relative_symlink, + test_simple_absolute_symlink, + test_deep_relative_file_symlink, + test_deep_relative_dir_symlink, + test_cyclic_link_protection, + test_relative_input_cwd, + test_deep_symlink_mix, + test_non_symlinks, +]; +var numtests = tests.length; +function runNextTest(err) { + if (err) throw err; + var test = tests.shift() + if (!test) puts(numtests+' subtests completed OK for fs.realpath'); + else test(runNextTest); +} +runNextTest(); + +process.addListener("exit", function () { + unlink.forEach(function(path){ try {fs.unlinkSync(path);}catch(e){} }); assert.equal(async_completed, async_expected); }); From 0e844d3bcbcded9792774e38e8032ac493482e63 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 2 Mar 2010 21:14:23 -0800 Subject: [PATCH 05/31] tcp.Connection.prototype.write should return boolean --- deps/evcom/evcom.c | 10 ++- deps/evcom/evcom.h | 2 +- doc/api.txt | 4 + src/node_net.cc | 8 +- src/node_net.h | 4 +- test/pummel/test-tcp-pause.js | 67 ++++++++++++++++ .../pummel/test-tcp-throttle-kernel-buffer.js | 55 ------------- test/pummel/test-tcp-throttle.js | 78 ++++++++----------- 8 files changed, 117 insertions(+), 111 deletions(-) create mode 100644 test/pummel/test-tcp-pause.js delete mode 100644 test/pummel/test-tcp-throttle-kernel-buffer.js diff --git a/deps/evcom/evcom.c b/deps/evcom/evcom.c index 1ee02a1f0a..1088181cef 100644 --- a/deps/evcom/evcom.c +++ b/deps/evcom/evcom.c @@ -1139,12 +1139,13 @@ void evcom_stream_force_close (evcom_stream *stream) evcom_stream_detach(stream); } -void +/* Returns the number of bytes flushed to the buffer */ +ssize_t evcom_stream_write (evcom_stream *stream, const char *str, size_t len) { if (!WRITABLE(stream) || GOT_CLOSE(stream)) { assert(0 && "Do not write to a closed stream"); - return; + return -1; } ssize_t sent = 0; @@ -1188,7 +1189,7 @@ evcom_stream_write (evcom_stream *stream, const char *str, size_t len) } /* TODO else { memcpy to last buffer on head } */ assert(sent >= 0); - if ((size_t)sent == len) return; /* sent the whole buffer */ + if ((size_t)sent == len) return sent; /* sent the whole buffer */ len -= sent; str += sent; @@ -1202,7 +1203,7 @@ evcom_stream_write (evcom_stream *stream, const char *str, size_t len) if (ATTACHED(stream)) { ev_io_start(D_LOOP_(stream) &stream->write_watcher); } - return; + return sent; close: stream->send_action = stream_send__close; @@ -1210,6 +1211,7 @@ close: if (ATTACHED(stream)) { ev_io_start(D_LOOP_(stream) &stream->write_watcher); } + return -1; } void diff --git a/deps/evcom/evcom.h b/deps/evcom/evcom.h index fd03a5bf05..3b07289fb5 100644 --- a/deps/evcom/evcom.h +++ b/deps/evcom/evcom.h @@ -194,7 +194,7 @@ void evcom_stream_read_resume (evcom_stream *); void evcom_stream_read_pause (evcom_stream *); void evcom_stream_reset_timeout (evcom_stream *, float timeout); void evcom_stream_set_no_delay (evcom_stream *, int no_delay); -void evcom_stream_write (evcom_stream *, const char *str, size_t len); +ssize_t evcom_stream_write (evcom_stream *, const char *str, size_t len); /* Once the write buffer is drained, evcom_stream_close will shutdown the * writing end of the stream and will close the read end once the server * replies with an EOF. diff --git a/doc/api.txt b/doc/api.txt index 9466fc6e45..1e7abcfb20 100644 --- a/doc/api.txt +++ b/doc/api.txt @@ -1493,6 +1493,10 @@ Sets the encoding (either +"ascii"+, +"utf8"+, or +"binary"+) for data that is r Sends data on the connection. The second parameter specifies the encoding in the case of a string--it defaults to ASCII because encoding to UTF8 is rather slow. ++ +Returns +true+ if the entire data was flushed successfully to the kernel +buffer. Returns +false+ if all or part of the data was queued in user memory. ++'drain'+ will be emitted when the buffer is again free. +connection.close()+:: diff --git a/src/node_net.cc b/src/node_net.cc index b9f739c0e5..bc0f67315b 100644 --- a/src/node_net.cc +++ b/src/node_net.cc @@ -633,12 +633,12 @@ Handle Connection::Write(const Arguments& args) { } char * buf = new char[len]; - ssize_t written = DecodeWrite(buf, len, args[0], enc); - assert(written == len); - connection->Write(buf, written); + ssize_t bufsize = DecodeWrite(buf, len, args[0], enc); + assert(bufsize == len); + ssize_t sent = connection->Write(buf, bufsize); delete [] buf; - return scope.Close(Integer::New(written)); + return sent == bufsize ? True() : False(); } void Connection::OnReceive(const void *buf, size_t len) { diff --git a/src/node_net.h b/src/node_net.h index 12e9e1dbd6..8ad1471a6a 100644 --- a/src/node_net.h +++ b/src/node_net.h @@ -62,8 +62,8 @@ class Connection : public EventEmitter { return evcom_stream_connect(&stream_, address); } - void Write(const char *buf, size_t len) { - evcom_stream_write(&stream_, buf, len); + ssize_t Write(const char *buf, size_t len) { + return evcom_stream_write(&stream_, buf, len); } void Close() { diff --git a/test/pummel/test-tcp-pause.js b/test/pummel/test-tcp-pause.js new file mode 100644 index 0000000000..0316e7797d --- /dev/null +++ b/test/pummel/test-tcp-pause.js @@ -0,0 +1,67 @@ +process.mixin(require("../common")); +tcp = require("tcp"); +N = 200; + +server = tcp.createServer(function (connection) { + function write (j) { + if (j >= N) { + connection.close(); + return; + } + setTimeout(function () { + connection.write("C"); + write(j+1); + }, 10); + } + write(0); +}); +server.listen(PORT); + + +recv = ""; +chars_recved = 0; + +client = tcp.createConnection(PORT); +client.setEncoding("ascii"); +client.addListener("data", function (d) { + print(d); + recv += d; +}); + +setTimeout(function () { + chars_recved = recv.length; + puts("pause at: " + chars_recved); + assert.equal(true, chars_recved > 1); + client.pause(); + setTimeout(function () { + puts("resume at: " + chars_recved); + assert.equal(chars_recved, recv.length); + client.resume(); + + setTimeout(function () { + chars_recved = recv.length; + puts("pause at: " + chars_recved); + client.pause(); + + setTimeout(function () { + puts("resume at: " + chars_recved); + assert.equal(chars_recved, recv.length); + client.resume(); + + }, 500); + + }, 500); + + }, 500); + +}, 500); + +client.addListener("end", function () { + server.close(); + client.close(); +}); + +process.addListener("exit", function () { + assert.equal(N, recv.length); + debug("Exit"); +}); diff --git a/test/pummel/test-tcp-throttle-kernel-buffer.js b/test/pummel/test-tcp-throttle-kernel-buffer.js deleted file mode 100644 index 8914e3556d..0000000000 --- a/test/pummel/test-tcp-throttle-kernel-buffer.js +++ /dev/null @@ -1,55 +0,0 @@ -process.mixin(require("../common")); -tcp = require("tcp"); -N = 30*1024; // 500kb - -puts("build big string"); -var body = ""; -for (var i = 0; i < N; i++) { - body += "C"; -} - -puts("start server on port " + PORT); - -server = tcp.createServer(function (connection) { - connection.addListener("connect", function () { - connection.write(body); - connection.close(); - }); -}); -server.listen(PORT); - - -chars_recved = 0; -npauses = 0; - - -var paused = false; -client = tcp.createConnection(PORT); -client.setEncoding("ascii"); -client.addListener("data", function (d) { - chars_recved += d.length; - puts("got " + chars_recved); - if (!paused) { - client.pause(); - npauses += 1; - paused = true; - puts("pause"); - x = chars_recved; - setTimeout(function () { - assert.equal(chars_recved, x); - client.resume(); - puts("resume"); - paused = false; - }, 100); - } -}); - -client.addListener("end", function () { - server.close(); - client.close(); -}); - -process.addListener("exit", function () { - assert.equal(N, chars_recved); - assert.equal(true, npauses > 2); -}); diff --git a/test/pummel/test-tcp-throttle.js b/test/pummel/test-tcp-throttle.js index 0316e7797d..32a7363722 100644 --- a/test/pummel/test-tcp-throttle.js +++ b/test/pummel/test-tcp-throttle.js @@ -1,60 +1,48 @@ process.mixin(require("../common")); tcp = require("tcp"); -N = 200; +N = 60*1024; // 30kb + +puts("build big string"); +var body = ""; +for (var i = 0; i < N; i++) { + body += "C"; +} + +puts("start server on port " + PORT); server = tcp.createServer(function (connection) { - function write (j) { - if (j >= N) { - connection.close(); - return; - } - setTimeout(function () { - connection.write("C"); - write(j+1); - }, 10); - } - write(0); + connection.addListener("connect", function () { + assert.equal(false, connection.write(body)); + connection.close(); + }); }); server.listen(PORT); -recv = ""; chars_recved = 0; +npauses = 0; + +var paused = false; client = tcp.createConnection(PORT); client.setEncoding("ascii"); client.addListener("data", function (d) { - print(d); - recv += d; -}); - -setTimeout(function () { - chars_recved = recv.length; - puts("pause at: " + chars_recved); - assert.equal(true, chars_recved > 1); - client.pause(); - setTimeout(function () { - puts("resume at: " + chars_recved); - assert.equal(chars_recved, recv.length); - client.resume(); - + chars_recved += d.length; + puts("got " + chars_recved); + if (!paused) { + client.pause(); + npauses += 1; + paused = true; + puts("pause"); + x = chars_recved; setTimeout(function () { - chars_recved = recv.length; - puts("pause at: " + chars_recved); - client.pause(); - - setTimeout(function () { - puts("resume at: " + chars_recved); - assert.equal(chars_recved, recv.length); - client.resume(); - - }, 500); - - }, 500); - - }, 500); - -}, 500); + assert.equal(chars_recved, x); + client.resume(); + puts("resume"); + paused = false; + }, 100); + } +}); client.addListener("end", function () { server.close(); @@ -62,6 +50,6 @@ client.addListener("end", function () { }); process.addListener("exit", function () { - assert.equal(N, recv.length); - debug("Exit"); + assert.equal(N, chars_recved); + assert.equal(true, npauses > 2); }); From 548d59d07e21a54fb869345ff71ed08cbe2ffad1 Mon Sep 17 00:00:00 2001 From: Jacek Becela Date: Wed, 3 Mar 2010 11:17:45 -0700 Subject: [PATCH 06/31] Fix fs.readFile handling encoding. Should close issue #72 --- src/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node.js b/src/node.js index 221a5a2657..12686e674c 100644 --- a/src/node.js +++ b/src/node.js @@ -310,7 +310,7 @@ function readAll (fd, pos, content, encoding, callback) { } process.fs.readFile = function (path, encoding_, callback) { - var encoding = typeof(encoding_) == 'string' ? encoding : 'utf8'; + var encoding = typeof(encoding_) == 'string' ? encoding_ : 'utf8'; var callback_ = arguments[arguments.length - 1]; var callback = (typeof(callback_) == 'function' ? callback_ : null); process.fs.open(path, process.O_RDONLY, 0666, function (err, fd) { From 769a35024f399e051f16dd6c8e663f82ed37cbf2 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 3 Mar 2010 10:45:58 -0800 Subject: [PATCH 07/31] Allow passing env to child process --- src/node_child_process.cc | 8 +++++--- src/node_child_process.h | 2 +- test/simple/test-child-process-env.js | 12 ++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 test/simple/test-child-process-env.js diff --git a/src/node_child_process.cc b/src/node_child_process.cc index 4dc3d437af..6902455dd0 100644 --- a/src/node_child_process.cc +++ b/src/node_child_process.cc @@ -9,6 +9,8 @@ #include #include +extern char **environ; + namespace node { using namespace v8; @@ -276,7 +278,7 @@ static inline int SetNonBlocking(int fd) { // Note that args[0] must be the same as the "file" param. This is an // execvp() requirement. -int ChildProcess::Spawn(const char *file, char *const args[], char *const env[]) { +int ChildProcess::Spawn(const char *file, char *const args[], char **env) { assert(pid_ == 0); assert(stdout_fd_ == -1); assert(stderr_fd_ == -1); @@ -315,11 +317,11 @@ int ChildProcess::Spawn(const char *file, char *const args[], char *const env[]) close(stdin_pipe[1]); // close write end dup2(stdin_pipe[0], STDIN_FILENO); + environ = env; + execvp(file, args); perror("execvp()"); _exit(127); - - // TODO search PATH and use: execve(file, argv, env); } // Parent. diff --git a/src/node_child_process.h b/src/node_child_process.h index 1cdd974d09..b37db9f36a 100644 --- a/src/node_child_process.h +++ b/src/node_child_process.h @@ -28,7 +28,7 @@ class ChildProcess : EventEmitter { ChildProcess(); ~ChildProcess(); - int Spawn(const char *file, char *const argv[], char *const env[]); + int Spawn(const char *file, char *const argv[], char **env); int Write(const char *str, size_t len); int Close(void); int Kill(int sig); diff --git a/test/simple/test-child-process-env.js b/test/simple/test-child-process-env.js new file mode 100644 index 0000000000..4b5d902030 --- /dev/null +++ b/test/simple/test-child-process-env.js @@ -0,0 +1,12 @@ +process.mixin(require("../common")); +child = process.createChildProcess('/usr/bin/env', [], {'HELLO' : 'WORLD'}); +response = ""; + +child.addListener("output", function (chunk) { + puts("stdout: " + JSON.stringify(chunk)); + if (chunk) response += chunk; +}); + +process.addListener('exit', function () { + assert.ok(response.indexOf('HELLO=WORLD') >= 0); +}); From 0dba38eef0f026df3f232a4f5cf1cbbcaa57fc1b Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 3 Mar 2010 01:11:47 -0800 Subject: [PATCH 08/31] Fix a bug that was suppressing the error in setgid, allowing it to fail silently. --- src/node.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node.cc b/src/node.cc index d760c72976..11c6a7de8e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -506,7 +506,7 @@ static Handle SetGid(const Arguments& args) { Local given_gid = args[0]->ToInteger(); int gid = given_gid->Int32Value(); int result; - if ((result == setgid(gid)) != 0) { + if ((result = setgid(gid)) != 0) { return ThrowException(Exception::Error(String::New(strerror(errno)))); } return Undefined(); From 64d0e328e8a03251223dcbba8c4cc92319036d16 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 3 Mar 2010 12:41:31 -0800 Subject: [PATCH 09/31] Remove unused EventEmitter object --- lib/http.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/http.js b/lib/http.js index d9eab022ee..109351371d 100644 --- a/lib/http.js +++ b/lib/http.js @@ -317,10 +317,6 @@ ClientRequest.prototype.close = function () { function createIncomingMessageStream (connection, incoming_listener) { - var stream = new events.EventEmitter(); - - stream.addListener("incoming", incoming_listener); - var incoming, field, value; connection.addListener("messageBegin", function () { @@ -372,7 +368,7 @@ function createIncomingMessageStream (connection, incoming_listener) { incoming.statusCode = info.statusCode; } - stream.emit("incoming", incoming, info.should_keep_alive); + incoming_listener(incoming, info.should_keep_alive); }); connection.addListener("body", function (chunk) { @@ -382,8 +378,6 @@ function createIncomingMessageStream (connection, incoming_listener) { connection.addListener("messageComplete", function () { incoming.emit('end'); }); - - return stream; } /* Returns true if the message queue is finished and the connection From 9d4d232eaaa1ae0403eb4727ee03e8749fa1c2c3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 3 Mar 2010 12:49:06 -0800 Subject: [PATCH 10/31] Factor out a http.Client._reconnect() function --- lib/http.js | 55 ++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/http.js b/lib/http.js index 109351371d..33cab5143e 100644 --- a/lib/http.js +++ b/lib/http.js @@ -457,18 +457,35 @@ exports.createClient = function (port, host) { var requests = []; var currentRequest; + client.tcpSetSecure = client.setSecure; + client.setSecure = function(format_type, ca_certs, crl_list, private_key, certificate) { + secure_credentials.secure = true; + secure_credentials.format_type = format_type; + secure_credentials.ca_certs = ca_certs; + secure_credentials.crl_list = crl_list; + secure_credentials.private_key = private_key; + secure_credentials.certificate = certificate; + } + + client._reconnect = function () { + if (client.readyState != "opening") { + //sys.debug("HTTP CLIENT: reconnecting readyState = " + client.readyState); + client.connect(port, host); + if (secure_credentials.secure) { + client.tcpSetSecure(secure_credentials.format_type, + secure_credentials.ca_certs, + secure_credentials.crl_list, + secure_credentials.private_key, + secure_credentials.certificate); + } + } + }; + client._pushRequest = function (req) { req.addListener("flush", function () { if (client.readyState == "closed") { //sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + client.readyState); - client.connect(port, host); // reconnect - if (secure_credentials.secure) { - client.tcpSetSecure(secure_credentials.format_type, - secure_credentials.ca_certs, - secure_credentials.crl_list, - secure_credentials.private_key, - secure_credentials.certificate); - } + client._reconnect(); return; } //sys.debug("client flush readyState = " + client.readyState); @@ -477,16 +494,6 @@ exports.createClient = function (port, host) { requests.push(req); }; - client.tcpSetSecure = client.setSecure; - client.setSecure = function(format_type, ca_certs, crl_list, private_key, certificate) { - secure_credentials.secure = true; - secure_credentials.format_type = format_type; - secure_credentials.ca_certs = ca_certs; - secure_credentials.crl_list = crl_list; - secure_credentials.private_key = private_key; - secure_credentials.certificate = certificate; - } - client.addListener("connect", function () { client.resetParser(); currentRequest = requests.shift(); @@ -507,16 +514,8 @@ exports.createClient = function (port, host) { //sys.debug("HTTP CLIENT onClose. readyState = " + client.readyState); // If there are more requests to handle, reconnect. - if (requests.length > 0 && client.readyState != "opening") { - //sys.debug("HTTP CLIENT: reconnecting readyState = " + client.readyState); - client.connect(port, host); // reconnect - if (secure_credentials.secure) { - client.tcpSetSecure(secure_credentials.format_type, - secure_credentials.ca_certs, - secure_credentials.crl_list, - secure_credentials.private_key, - secure_credentials.certificate); - } + if (requests.length > 0) { + client._reconnect(); } }); From d1500cee6e2750bb57f73f8e8bfd8ca034f96110 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 3 Mar 2010 13:06:19 -0800 Subject: [PATCH 11/31] Store connection in OutgoingMessage --- lib/http.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/http.js b/lib/http.js index 33cab5143e..ca196bc176 100644 --- a/lib/http.js +++ b/lib/http.js @@ -96,8 +96,10 @@ IncomingMessage.prototype._addHeaderLine = function (field, value) { } }; -function OutgoingMessage () { - events.EventEmitter.call(this); +function OutgoingMessage (connection) { + events.EventEmitter.call(this, connection); + + this.connection = connection; this.output = []; this.outputEncodings = []; @@ -246,7 +248,7 @@ OutgoingMessage.prototype.close = function () { function ServerResponse (req) { - OutgoingMessage.call(this); + OutgoingMessage.call(this, req.connection); if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { this.use_chunked_encoding_by_default = false; @@ -283,8 +285,8 @@ ServerResponse.prototype.writeHead = function (statusCode) { ServerResponse.prototype.sendHeader = ServerResponse.prototype.writeHead; ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; -function ClientRequest (method, url, headers) { - OutgoingMessage.call(this); +function ClientRequest (connection, method, url, headers) { + OutgoingMessage.call(this, connection); this.should_keep_alive = false; if (method === "GET" || method === "HEAD") { @@ -559,7 +561,7 @@ process.http.Client.prototype.request = function (method, url, headers) { url = method; method = null; } - var req = new ClientRequest(method || "GET", url, headers); + var req = new ClientRequest(this, method || "GET", url, headers); this._pushRequest(req); return req; }; From d5ee777af2b6a89ab866c74171f6059c85079f88 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 3 Mar 2010 15:34:57 -0800 Subject: [PATCH 12/31] Don't allow child process to clobber environ --- src/node_child_process.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/node_child_process.cc b/src/node_child_process.cc index 6902455dd0..6c09ee0f00 100644 --- a/src/node_child_process.cc +++ b/src/node_child_process.cc @@ -302,6 +302,10 @@ int ChildProcess::Spawn(const char *file, char *const args[], char **env) { return -3; } + // Save environ in the case that we get it clobbered + // by the child process. + char **save_our_env = environ; + switch (pid_ = vfork()) { case -1: // Error. Shutdown(); @@ -324,6 +328,9 @@ int ChildProcess::Spawn(const char *file, char *const args[], char **env) { _exit(127); } + // Restore environment. + environ = save_our_env; + // Parent. ev_child_set(&child_watcher_, pid_, 0); From 409020a67d3388e4eda90af546e0fbe25b0adec3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 4 Mar 2010 09:58:31 -0800 Subject: [PATCH 13/31] Use kqueue on macintosh --- benchmark/http_simple.rb | 8 +++++--- src/node.cc | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/benchmark/http_simple.rb b/benchmark/http_simple.rb index ee33f57f1d..d1176e5abe 100644 --- a/benchmark/http_simple.rb +++ b/benchmark/http_simple.rb @@ -86,10 +86,12 @@ end if $0 == __FILE__ #require DIR + '/../lib/ebb' require 'rubygems' - require 'rack' - require 'thin' - require 'ebb' +# require 'rack' # Rack::Handler::Mongrel.run(SimpleApp.new, :Port => 8000) + + require 'thin' Thin::Server.start("0.0.0.0", 8000, SimpleApp.new) + +# require 'ebb' # Ebb::start_server(SimpleApp.new, :port => 8000) end diff --git a/src/node.cc b/src/node.cc index 11c6a7de8e..205c8aeab5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1240,10 +1240,12 @@ int main(int argc, char *argv[]) { evcom_ignore_sigpipe(); // Initialize the default ev loop. -#ifdef __sun +#if defined(__sun) // TODO(Ryan) I'm experiencing abnormally high load using Solaris's // EVBACKEND_PORT. Temporarally forcing select() until I debug. ev_default_loop(EVBACKEND_SELECT); +#elif defined(__APPLE__) + ev_default_loop(EVBACKEND_KQUEUE); #else ev_default_loop(EVFLAG_AUTO); #endif From 1e710cafa704cf451782e8c96e1b57418561600e Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 4 Mar 2010 11:51:39 -0800 Subject: [PATCH 14/31] Remove process.unloop() --- src/node.cc | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/node.cc b/src/node.cc index 205c8aeab5..e6904a1e2f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -424,19 +424,6 @@ static Handle Loop(const Arguments& args) { return Undefined(); } -static Handle Unloop(const Arguments& args) { - HandleScope scope; - int how = EVUNLOOP_ONE; - if (args[0]->IsString()) { - String::Utf8Value how_s(args[0]->ToString()); - if (0 == strcmp(*how_s, "all")) { - how = EVUNLOOP_ALL; - } - } - ev_unloop(EV_DEFAULT_ how); - return Undefined(); -} - static Handle Chdir(const Arguments& args) { HandleScope scope; @@ -1049,7 +1036,6 @@ static void Load(int argc, char *argv[]) { // define various internal methods NODE_SET_METHOD(process, "loop", Loop); - NODE_SET_METHOD(process, "unloop", Unloop); NODE_SET_METHOD(process, "compile", Compile); NODE_SET_METHOD(process, "_byteLength", ByteLength); NODE_SET_METHOD(process, "reallyExit", Exit); From e6dbf8d632b9fd6797db00e59b02d2179f221fb5 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 4 Mar 2010 13:00:37 -0800 Subject: [PATCH 15/31] Revert "Remove process.unloop()" People need this for backwards compatibility. Will be removed soon though! This reverts commit 1e710cafa704cf451782e8c96e1b57418561600e. --- src/node.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/node.cc b/src/node.cc index e6904a1e2f..205c8aeab5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -424,6 +424,19 @@ static Handle Loop(const Arguments& args) { return Undefined(); } +static Handle Unloop(const Arguments& args) { + HandleScope scope; + int how = EVUNLOOP_ONE; + if (args[0]->IsString()) { + String::Utf8Value how_s(args[0]->ToString()); + if (0 == strcmp(*how_s, "all")) { + how = EVUNLOOP_ALL; + } + } + ev_unloop(EV_DEFAULT_ how); + return Undefined(); +} + static Handle Chdir(const Arguments& args) { HandleScope scope; @@ -1036,6 +1049,7 @@ static void Load(int argc, char *argv[]) { // define various internal methods NODE_SET_METHOD(process, "loop", Loop); + NODE_SET_METHOD(process, "unloop", Unloop); NODE_SET_METHOD(process, "compile", Compile); NODE_SET_METHOD(process, "_byteLength", ByteLength); NODE_SET_METHOD(process, "reallyExit", Exit); From 9415ca909ea0e5e5c0b390fa60759952143beed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Wed, 3 Mar 2010 12:39:17 +0100 Subject: [PATCH 16/31] Use process.mixin instead of sys.mixin The process namespace has not been cleaned up yet, so mixin is still attached to process. --- lib/fs.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 00f6168c15..24542ae0c4 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1,5 +1,3 @@ -var sys = require('sys'); - exports.Stats = process.Stats; process.Stats.prototype._checkModeProperty = function (property) { @@ -391,7 +389,7 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { this.encoding = 'binary'; this.mode = 0666; - sys.mixin(this, options || {}); + process.mixin(this, options || {}); var self = this, From f6e00759effd1d550657aa6ce0208ee04eda87bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Wed, 3 Mar 2010 12:39:41 +0100 Subject: [PATCH 17/31] Initial read stream implementation --- lib/fs.js | 90 ++++++++++++++++++++++++++++ test/simple/test-file-read-stream.js | 54 +++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 test/simple/test-file-read-stream.js diff --git a/lib/fs.js b/lib/fs.js index 24542ae0c4..ac13b2670f 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -376,6 +376,96 @@ exports.realpath = function(path, callback) { }); } +exports.fileReadStream = function(path, options) { + return new FileReadStream(path, options); +}; + +var FileReadStream = exports.FileReadStream = function(path, options) { + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; + + this.flags = 'r'; + this.encoding = 'binary'; + this.mode = 0666; + this.bufferSize = 4 * 1024; + + process.mixin(this, options || {}); + + var + self = this, + buffer = []; + + function read() { + if (!self.readable || self.paused) { + return; + } + + fs.read(self.fd, self.bufferSize, undefined, self.encoding, function(err, data, bytesRead) { + if (bytesRead === 0) { + self.emit('end'); + self.close(); + return; + } + + // do not emit events if the stream is paused + if (self.paused) { + buffer.push(data); + return; + } + + self.emit('data', data); + read(); + }); + } + + fs.open(this.path, this.flags, this.mode, function(err, fd) { + if (err) { + self.emit('error', err); + return; + } + + self.fd = fd; + self.emit('open', fd); + read(); + }); + + this.close = function() { + this.readable = false; + fs.close(this.fd, function(err) { + if (err) { + self.emit('error', err); + return; + } + + self.emit('close'); + }); + }; + + this.pause = function() { + this.paused = true; + }; + + this.resume = function() { + this.paused = false; + + // emit any buffered read events before continuing + var data; + while (!this.paused) { + data = buffer.shift(); + if (data === undefined) { + break; + } + + self.emit('data', data); + } + + read(); + }; +}; +FileReadStream.prototype.__proto__ = process.EventEmitter.prototype; + exports.fileWriteStream = function(path, options) { return new FileWriteStream(path, options); }; diff --git a/test/simple/test-file-read-stream.js b/test/simple/test-file-read-stream.js new file mode 100644 index 0000000000..3b358b5c5c --- /dev/null +++ b/test/simple/test-file-read-stream.js @@ -0,0 +1,54 @@ +process.mixin(require('../common')); + +var + fn = path.join(fixturesDir, 'multipart.js'), + file = fs.fileReadStream(fn), + + callbacks = { + open: -1, + end: -1, + close: -1 + }, + + paused = false, + + fileContent = ''; + +file + .addListener('open', function(fd) { + callbacks.open++; + assert.equal('number', typeof fd); + assert.ok(file.readable); + }) + .addListener('error', function(err) { + throw err; + }) + .addListener('data', function(data) { + assert.ok(!paused); + fileContent += data; + + paused = true; + file.pause(); + assert.ok(file.paused); + + setTimeout(function() { + paused = false; + file.resume(); + assert.ok(!file.paused); + }, 10); + }) + .addListener('end', function(chunk) { + callbacks.end++; + }) + .addListener('close', function() { + callbacks.close++; + assert.ok(!file.readable); + + assert.equal(fs.readFileSync(fn), fileContent); + }); + +process.addListener('exit', function() { + for (var k in callbacks) { + assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); + } +}); \ No newline at end of file From b4fba5fe8ee5fe3a43405fbd8838feab5f259bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 4 Mar 2010 14:25:59 +0100 Subject: [PATCH 18/31] Simplify buffering There is no way more than one read event would be buffered. --- lib/fs.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index ac13b2670f..de1bf94ce3 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -395,7 +395,7 @@ var FileReadStream = exports.FileReadStream = function(path, options) { var self = this, - buffer = []; + buffer = null; function read() { if (!self.readable || self.paused) { @@ -411,7 +411,7 @@ var FileReadStream = exports.FileReadStream = function(path, options) { // do not emit events if the stream is paused if (self.paused) { - buffer.push(data); + buffer = data; return; } @@ -450,15 +450,9 @@ var FileReadStream = exports.FileReadStream = function(path, options) { this.resume = function() { this.paused = false; - // emit any buffered read events before continuing - var data; - while (!this.paused) { - data = buffer.shift(); - if (data === undefined) { - break; - } - - self.emit('data', data); + if (buffer !== null) { + self.emit('data', buffer); + buffer = null; } read(); From 48562fa9389946ae7ac5b977004fc0d47e5af947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Thu, 4 Mar 2010 22:06:06 +0100 Subject: [PATCH 19/31] Updated file streams Read streams now only support forceClose() Write streams support close() and forceClose() --- lib/fs.js | 22 ++++++++++++++++++++-- test/simple/test-file-write-stream.js | 3 +++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index de1bf94ce3..cf133158e8 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -405,7 +405,7 @@ var FileReadStream = exports.FileReadStream = function(path, options) { fs.read(self.fd, self.bufferSize, undefined, self.encoding, function(err, data, bytesRead) { if (bytesRead === 0) { self.emit('end'); - self.close(); + self.forceClose(); return; } @@ -415,6 +415,11 @@ var FileReadStream = exports.FileReadStream = function(path, options) { return; } + // do not emit events anymore after we declared the stream unreadable + if (!self.readable) { + return; + } + self.emit('data', data); read(); }); @@ -431,7 +436,7 @@ var FileReadStream = exports.FileReadStream = function(path, options) { read(); }); - this.close = function() { + this.forceClose = function() { this.readable = false; fs.close(this.fd, function(err) { if (err) { @@ -544,6 +549,19 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { flush(); }; + this.forceClose = function() { + this.writeable = false; + fs.close(self.fd, function(err) { + if (err) { + self.emit('error', err); + return; + } + + self.emit('close'); + }); + }; + + flush(); }; FileWriteStream.prototype.__proto__ = process.EventEmitter.prototype; \ No newline at end of file diff --git a/test/simple/test-file-write-stream.js b/test/simple/test-file-write-stream.js index 669985e178..5f15bfe1c3 100644 --- a/test/simple/test-file-write-stream.js +++ b/test/simple/test-file-write-stream.js @@ -17,6 +17,9 @@ file callbacks.open++; assert.equal('number', typeof fd); }) + .addListener('error', function(err) { + throw err; + }) .addListener('drain', function() { callbacks.drain++; if (callbacks.drain == -1) { From 0fcc94525a59df4ae1a90a7c3d4c62df0e19b36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Fri, 5 Mar 2010 18:56:25 +0100 Subject: [PATCH 20/31] Renamed fileReadStream -> createReadStream Did the same for fileWriteStream as well. --- lib/fs.js | 4 ++-- test/simple/test-file-read-stream.js | 2 +- test/simple/test-file-write-stream.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index cf133158e8..579199d93e 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -376,7 +376,7 @@ exports.realpath = function(path, callback) { }); } -exports.fileReadStream = function(path, options) { +exports.createReadStream = function(path, options) { return new FileReadStream(path, options); }; @@ -465,7 +465,7 @@ var FileReadStream = exports.FileReadStream = function(path, options) { }; FileReadStream.prototype.__proto__ = process.EventEmitter.prototype; -exports.fileWriteStream = function(path, options) { +exports.createWriteStream = function(path, options) { return new FileWriteStream(path, options); }; diff --git a/test/simple/test-file-read-stream.js b/test/simple/test-file-read-stream.js index 3b358b5c5c..447629e41b 100644 --- a/test/simple/test-file-read-stream.js +++ b/test/simple/test-file-read-stream.js @@ -2,7 +2,7 @@ process.mixin(require('../common')); var fn = path.join(fixturesDir, 'multipart.js'), - file = fs.fileReadStream(fn), + file = fs.createReadStream(fn), callbacks = { open: -1, diff --git a/test/simple/test-file-write-stream.js b/test/simple/test-file-write-stream.js index 5f15bfe1c3..89059033ab 100644 --- a/test/simple/test-file-write-stream.js +++ b/test/simple/test-file-write-stream.js @@ -2,7 +2,7 @@ process.mixin(require('../common')); var fn = path.join(fixturesDir, "write.txt"), - file = fs.fileWriteStream(fn), + file = fs.createWriteStream(fn), EXPECTED = '0123456789', From 145fac2b56a29270c798996db508a9fc6338c1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Fri, 5 Mar 2010 19:24:20 +0100 Subject: [PATCH 21/31] Use sys inherits Also use events.EventEmitter instead of process.EventEmitter. --- lib/fs.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 579199d93e..165ece8155 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1,3 +1,7 @@ +var + sys = require('sys'), + events = require('events'); + exports.Stats = process.Stats; process.Stats.prototype._checkModeProperty = function (property) { @@ -381,6 +385,8 @@ exports.createReadStream = function(path, options) { }; var FileReadStream = exports.FileReadStream = function(path, options) { + events.EventEmitter.call(this); + this.path = path; this.fd = null; this.readable = true; @@ -463,13 +469,15 @@ var FileReadStream = exports.FileReadStream = function(path, options) { read(); }; }; -FileReadStream.prototype.__proto__ = process.EventEmitter.prototype; +sys.inherits(FileReadStream, events.EventEmitter); exports.createWriteStream = function(path, options) { return new FileWriteStream(path, options); }; var FileWriteStream = exports.FileWriteStream = function(path, options) { + events.EventEmitter.call(this); + this.path = path; this.fd = null; this.writeable = true; @@ -564,4 +572,4 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { flush(); }; -FileWriteStream.prototype.__proto__ = process.EventEmitter.prototype; \ No newline at end of file +sys.inherits(FileWriteStream, events.EventEmitter); \ No newline at end of file From 78c61000c20e0456a3d887bc397fe29815f2f9e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Fri, 5 Mar 2010 19:53:59 +0100 Subject: [PATCH 22/31] Properly handle read errors Also set readable to false if the initial fs.open call failed. --- lib/fs.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/fs.js b/lib/fs.js index 165ece8155..56c739389b 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -409,6 +409,12 @@ var FileReadStream = exports.FileReadStream = function(path, options) { } fs.read(self.fd, self.bufferSize, undefined, self.encoding, function(err, data, bytesRead) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } + if (bytesRead === 0) { self.emit('end'); self.forceClose(); @@ -434,6 +440,7 @@ var FileReadStream = exports.FileReadStream = function(path, options) { fs.open(this.path, this.flags, this.mode, function(err, fd) { if (err) { self.emit('error', err); + self.readable = false; return; } From a96b5c792e49c0e2e425ce14746276d1c59c45ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Fri, 5 Mar 2010 19:54:28 +0100 Subject: [PATCH 23/31] Documentation for FileReadStream --- doc/api.txt | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/doc/api.txt b/doc/api.txt index 73f4f54c2e..c4a0ec355c 100644 --- a/doc/api.txt +++ b/doc/api.txt @@ -783,6 +783,47 @@ Objects returned from +fs.stat()+ and +fs.lstat()+ are of this type. +stats.isSocket()+:: ... +=== +fs.FileReadStream+ + +[cols="1,2,10",options="header"] +|========================================================= +|Event | Parameters | Notes + +|+"open"+ | +fd+ | The file descriptor was opened. +|+"data"+ | +chunk+ | A chunk of data was read. +|+"error"+ | +err+ | An error occured. This stops the stream. +|+"end"+ | | The end of the file was reached. +|+"close"+ | | The file descriptor was closed. +|========================================================= + ++fs.createReadStream(path, [options]);+ :: +Returns a new FileReadStream object. ++ ++options+ is an object with the following defaults: ++ +---------------------------------------- +{ "flags": "r" +, "encoding": "binary" +, "mode": 0666 +, "bufferSize": 4 * 1024 +} +---------------------------------------- + ++readStream.readable+ :: +A boolean that is +true+ by default, but turns +false+ after an +"error"+ +occured, the stream came to an "end", or +forceClose()+ was called. + ++readStream.pause()+ :: +Stops the stream from reading further data. No +"data"+ event will be fired +until the stream is resumed. + ++readStream.resume()+ :: +Resumes the stream. Together with +pause()+ this useful to throttle reading. + ++readStream.forceClose()+ :: +Allows to close the stream before the +"end"+ is reached. No more events other +than +"close"+ will be fired after this method has been called. + == HTTP To use the HTTP server and client one must +require("http")+. From dbf9e466bc263cc08b217403cdf4a14a66f82935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Fri, 5 Mar 2010 20:04:19 +0100 Subject: [PATCH 24/31] Documentation for FileWriteStream --- doc/api.txt | 39 +++++++++++++++++++++++++++++++++++++++ lib/fs.js | 1 - 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/doc/api.txt b/doc/api.txt index c4a0ec355c..c8d62c2b1a 100644 --- a/doc/api.txt +++ b/doc/api.txt @@ -824,6 +824,45 @@ Resumes the stream. Together with +pause()+ this useful to throttle reading. Allows to close the stream before the +"end"+ is reached. No more events other than +"close"+ will be fired after this method has been called. +=== +fs.FileWriteStream+ + +[cols="1,2,10",options="header"] +|========================================================= +|Event | Parameters | Notes + +|+"open"+ | +fd+ | The file descriptor was opened. +|+"drain"+ | | No more data needs to be written. +|+"error"+ | +err+ | An error occured. This stops the stream. +|+"close"+ | | The file descriptor was closed. +|========================================================= + ++fs.createWriteStream(path, [options]);+ :: +Returns a new FileWriteStream object. ++ ++options+ is an object with the following defaults: ++ +---------------------------------------- +{ "flags": "r" +, "encoding": "binary" +, "mode": 0666 +} +---------------------------------------- + ++writeStream.writeable+ :: +A boolean that is +true+ by default, but turns +false+ after an +"error"+ +occured or +close()+ / +forceClose()+ was called. + ++writeStream.write(data)+ :: +Returns +true+ if the data was flushed to the kernel, and +false+ if it was +queued up for being written later. A +"drain"+ will fire after all queued data +has been written. + ++writeStream.close()+ :: +Closes the stream right after all queued +write()+ calls have finished. + ++writeStream.forceClose()+ :: +Allows to close the stream regardless of its current state. + == HTTP To use the HTTP server and client one must +require("http")+. diff --git a/lib/fs.js b/lib/fs.js index 56c739389b..fa8ba89bda 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -576,7 +576,6 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { }); }; - flush(); }; sys.inherits(FileWriteStream, events.EventEmitter); \ No newline at end of file From 6d60d2db00d25b385bd978ea074486651ae10c50 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 5 Mar 2010 14:36:13 -0800 Subject: [PATCH 25/31] Revert "Use kqueue on macintosh" Experiencing bugs http://github.com/ry/node/issues/#issue/74 This reverts commit 409020a67d3388e4eda90af546e0fbe25b0adec3. --- benchmark/http_simple.rb | 8 +++----- src/node.cc | 4 +--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/benchmark/http_simple.rb b/benchmark/http_simple.rb index d1176e5abe..ee33f57f1d 100644 --- a/benchmark/http_simple.rb +++ b/benchmark/http_simple.rb @@ -86,12 +86,10 @@ end if $0 == __FILE__ #require DIR + '/../lib/ebb' require 'rubygems' -# require 'rack' -# Rack::Handler::Mongrel.run(SimpleApp.new, :Port => 8000) - + require 'rack' require 'thin' + require 'ebb' +# Rack::Handler::Mongrel.run(SimpleApp.new, :Port => 8000) Thin::Server.start("0.0.0.0", 8000, SimpleApp.new) - -# require 'ebb' # Ebb::start_server(SimpleApp.new, :port => 8000) end diff --git a/src/node.cc b/src/node.cc index 205c8aeab5..11c6a7de8e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1240,12 +1240,10 @@ int main(int argc, char *argv[]) { evcom_ignore_sigpipe(); // Initialize the default ev loop. -#if defined(__sun) +#ifdef __sun // TODO(Ryan) I'm experiencing abnormally high load using Solaris's // EVBACKEND_PORT. Temporarally forcing select() until I debug. ev_default_loop(EVBACKEND_SELECT); -#elif defined(__APPLE__) - ev_default_loop(EVBACKEND_KQUEUE); #else ev_default_loop(EVFLAG_AUTO); #endif From e72b072d5333c87e2ca972f0d88dabfa0cedbec2 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 5 Mar 2010 15:31:21 -0800 Subject: [PATCH 26/31] Decouple timer from EventEmitter --- src/node.js | 14 ++++++-------- src/node_timer.cc | 42 ++++++++++++++++++++++++++++++++---------- src/node_timer.h | 7 ++++--- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/node.js b/src/node.js index 12686e674c..afc70bf88f 100644 --- a/src/node.js +++ b/src/node.js @@ -246,35 +246,33 @@ function addTimerListener (callback) { // Special case the no param case to avoid the extra object creation. if (arguments.length > 2) { var args = Array.prototype.slice.call(arguments, 2); - timer.addListener("timeout", function(){ - callback.apply(timer, args); - }); + timer.callback = function () { callback.apply(timer, args); }; } else { - timer.addListener("timeout", callback); + timer.callback = callback; } } -GLOBAL.setTimeout = function (callback, after) { +global.setTimeout = function (callback, after) { var timer = new process.Timer(); addTimerListener.apply(timer, arguments); timer.start(after, 0); return timer; }; -GLOBAL.setInterval = function (callback, repeat) { +global.setInterval = function (callback, repeat) { var timer = new process.Timer(); addTimerListener.apply(timer, arguments); timer.start(repeat, repeat); return timer; }; -GLOBAL.clearTimeout = function (timer) { +global.clearTimeout = function (timer) { if (timer instanceof process.Timer) { timer.stop(); } }; -GLOBAL.clearInterval = GLOBAL.clearTimeout; +global.clearInterval = global.clearTimeout; diff --git a/src/node_timer.cc b/src/node_timer.cc index 5d60126c8a..1fa470aad3 100644 --- a/src/node_timer.cc +++ b/src/node_timer.cc @@ -9,6 +9,7 @@ Persistent Timer::constructor_template; static Persistent timeout_symbol; static Persistent repeat_symbol; +static Persistent callback_symbol; void Timer::Initialize (Handle target) @@ -17,12 +18,12 @@ Timer::Initialize (Handle target) Local t = FunctionTemplate::New(Timer::New); constructor_template = Persistent::New(t); - constructor_template->Inherit(EventEmitter::constructor_template); constructor_template->InstanceTemplate()->SetInternalFieldCount(1); constructor_template->SetClassName(String::NewSymbol("Timer")); timeout_symbol = NODE_PSYMBOL("timeout"); repeat_symbol = NODE_PSYMBOL("repeat"); + callback_symbol = NODE_PSYMBOL("callback"); NODE_SET_PROTOTYPE_METHOD(constructor_template, "start", Timer::Start); NODE_SET_PROTOTYPE_METHOD(constructor_template, "stop", Timer::Stop); @@ -66,7 +67,23 @@ Timer::OnTimeout (EV_P_ ev_timer *watcher, int revents) assert(revents == EV_TIMEOUT); - timer->Emit(timeout_symbol, 0, NULL); + HandleScope scope; + + Local callback_v = timer->handle_->Get(callback_symbol); + if (!callback_v->IsFunction()) { + timer->Stop(); + return; + } + + Local callback = Local::Cast(callback_v); + + TryCatch try_catch; + + callback->Call(timer->handle_, 0, NULL); + + if (try_catch.HasCaught()) { + FatalException(try_catch); + } if (timer->watcher_.repeat == 0) timer->Unref(); } @@ -90,8 +107,8 @@ Timer::New (const Arguments& args) Handle Timer::Start (const Arguments& args) { - Timer *timer = ObjectWrap::Unwrap(args.Holder()); HandleScope scope; + Timer *timer = ObjectWrap::Unwrap(args.Holder()); if (args.Length() != 2) return ThrowException(String::New("Bad arguments")); @@ -108,13 +125,18 @@ Timer::Start (const Arguments& args) return Undefined(); } -Handle -Timer::Stop (const Arguments& args) -{ + +Handle Timer::Stop(const Arguments& args) { + HandleScope scope; Timer *timer = ObjectWrap::Unwrap(args.Holder()); - if (ev_is_active(&timer->watcher_)) { - ev_timer_stop(EV_DEFAULT_UC_ &timer->watcher_); - timer->Unref(); - } + timer->Stop(); return Undefined(); } + + +void Timer::Stop () { + if (watcher_.active) { + ev_timer_stop(EV_DEFAULT_UC_ &watcher_); + Unref(); + } +} diff --git a/src/node_timer.h b/src/node_timer.h index d662f9a0fa..0472fdb0d6 100644 --- a/src/node_timer.h +++ b/src/node_timer.h @@ -2,20 +2,20 @@ #define node_timer_h #include -#include +#include #include #include namespace node { -class Timer : EventEmitter { +class Timer : ObjectWrap { public: static void Initialize (v8::Handle target); protected: static v8::Persistent constructor_template; - Timer () : EventEmitter () { } + Timer () : ObjectWrap () { } ~Timer(); static v8::Handle New (const v8::Arguments& args); @@ -26,6 +26,7 @@ class Timer : EventEmitter { private: static void OnTimeout (EV_P_ ev_timer *watcher, int revents); + void Stop (); ev_timer watcher_; }; From 939a6c7484b31557878ae54faf0a6e5a31d6454e Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 5 Mar 2010 15:59:31 -0800 Subject: [PATCH 27/31] Clean up homepage --- doc/index.html | 53 +++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/doc/index.html b/doc/index.html index e28ae2e8d4..e18522b1a2 100644 --- a/doc/index.html +++ b/doc/index.html @@ -21,10 +21,8 @@
  • ChangeLog
  • Build
  • About
  • -
  • Demo
  • -
  • Community
  • -
  • Contribute
  • -
  • Benchmarks
  • +
  • Links
  • +
  • Contributing
  • Documentation
  • @@ -208,32 +206,42 @@ make install

    -

    Demo

    -

    - A chat room demo is running at Links + +

    + +

    Contributing

    - Patches are always welcome. The process is simple: + Patches are welcome. The process is simple:

    @@ -253,13 +261,10 @@ git format-patch HEAD^
           

    - Feature patches should usually be discussed before putting in the work. + You should ask the mailing list if a new feature is wanted before + working on a patch.

    -

    Benchmarks

    -

    - 2009.09.06 narwhal, node, v8cgi, thin/eventmachine -