summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy J Fontaine <tjfontaine@gmail.com>2015-03-22 12:33:12 -0700
committerTimothy J Fontaine <tjfontaine@gmail.com>2015-03-24 14:09:16 -0700
commitfa79367e9873930fc69c52f8f52a67e6c4428d06 (patch)
treed657b005e199d1dd0409e9309059e3ca9336d62d
parenta93087007fce8466ef58e37af76521468295ccec (diff)
downloadnode-fa79367e9873930fc69c52f8f52a67e6c4428d06.tar.gz
events: add StrictEE
-rw-r--r--lib/_http_client.js23
-rw-r--r--lib/_http_incoming.js9
-rw-r--r--lib/_http_outgoing.js15
-rw-r--r--lib/_http_server.js30
-rw-r--r--lib/_stream_readable.js20
-rw-r--r--lib/_stream_writable.js21
-rw-r--r--lib/_tls_wrap.js18
-rw-r--r--lib/events.js162
-rw-r--r--lib/fs.js22
-rw-r--r--lib/https.js20
-rw-r--r--lib/net.js63
-rw-r--r--lib/stream.js14
12 files changed, 407 insertions, 10 deletions
diff --git a/lib/_http_client.js b/lib/_http_client.js
index 5f873d65d..5e004ce8c 100644
--- a/lib/_http_client.js
+++ b/lib/_http_client.js
@@ -42,6 +42,29 @@ var Agent = require('_http_agent');
function ClientRequest(options, cb) {
var self = this;
OutgoingMessage.call(self);
+ this.strictEERegister({
+ connect: {
+ maxCount: 1,
+ after: ['socket'],
+ },
+ close: {
+ maxCount: 1,
+ after: ['error', 'upgrade', 'aborted', 'finish', 'end'],
+ },
+ error: {
+ maxCount: 1,
+ notAfter: ['close'],
+ },
+ continue: {
+ maxCount: 1,
+ notAfter: ['error', 'close'],
+ after: ['socket'],
+ },
+ response: {
+ notAfter: ['error', 'close'],
+ maxCount: 1,
+ },
+ }, true);
if (util.isString(options)) {
options = url.parse(options);
diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js
index 69d3d86ec..30297ebe1 100644
--- a/lib/_http_incoming.js
+++ b/lib/_http_incoming.js
@@ -38,6 +38,15 @@ exports.readStop = readStop;
/* Abstract base class for ServerRequest and ClientResponse. */
function IncomingMessage(socket) {
Stream.Readable.call(this);
+ this.strictEERegister({
+ aborted: {
+ maxCount: 1,
+ notAfter: ['error', 'close', 'end'],
+ },
+ timeout: {
+ notAfter: ['error', 'close'],
+ },
+ });
// XXX This implementation is kind of all over the place
// When the parser emits body chunks, they go in this list.
diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js
index d02bdf7f2..39cc2ef48 100644
--- a/lib/_http_outgoing.js
+++ b/lib/_http_outgoing.js
@@ -62,7 +62,18 @@ utcDate._onTimeout = function() {
function OutgoingMessage() {
- Stream.call(this);
+ Stream.Writable.call(this);
+ this.strictEERegister({
+ socket: {
+ notAfter: ['close', 'error', 'finish'],
+ },
+ upgrade: {
+ maxCount: 1,
+ },
+ timeout: {
+ notAfter: ['error', 'close'],
+ },
+ });
this.output = [];
this.outputEncodings = [];
@@ -90,7 +101,7 @@ function OutgoingMessage() {
this._headers = null;
this._headerNames = {};
}
-util.inherits(OutgoingMessage, Stream);
+util.inherits(OutgoingMessage, Stream.Writable);
exports.OutgoingMessage = OutgoingMessage;
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 787fc27bb..7ca307ca2 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -100,6 +100,15 @@ var STATUS_CODES = exports.STATUS_CODES = {
function ServerResponse(req) {
OutgoingMessage.call(this);
+ this.strictEERegister({
+ close: {
+ maxCount: 1,
+ after: ['abort', 'error', '_socketClose'],
+ },
+ _socketClose: {
+ maxCount: 1,
+ },
+ }, true);
if (req.method === 'HEAD') this._hasBody = false;
@@ -238,6 +247,27 @@ function Server(requestListener) {
if (!(this instanceof Server)) return new Server(requestListener);
net.Server.call(this, { allowHalfOpen: true });
+ this.strictEERegister({
+ checkContinue: {
+ notAfter: ['close', 'error'],
+ after: ['listening', 'connection'],
+ },
+ request: {
+ notAfter: ['close', 'error'],
+ },
+ socket: {
+ notAfter: ['close', 'error'],
+ },
+ timeout: {
+ after: ['listening', 'connection'],
+ notAfter: ['error', 'close'],
+ },
+ upgrade: {
+ after: ['listening', 'connection'],
+ notAfter: ['close', 'error'],
+ },
+ });
+
if (requestListener) {
this.addListener('request', requestListener);
}
diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js
index 5f280b773..e87c9f293 100644
--- a/lib/_stream_readable.js
+++ b/lib/_stream_readable.js
@@ -22,7 +22,7 @@
module.exports = Readable;
Readable.ReadableState = ReadableState;
-var EE = require('events').EventEmitter;
+var EE = require('events').StrictEE;
var Stream = require('stream');
var Buffer = require('buffer').Buffer;
var util = require('util');
@@ -106,6 +106,24 @@ function Readable(options) {
this.readable = true;
Stream.call(this);
+ this.strictEERegister({
+ data: {
+ notAfter: ['close', 'end', 'error'],
+ },
+ end: {
+ maxCount: 1,
+ notAfter: ['error', 'close'],
+ },
+ pause: {
+ notAfter: ['end', 'error', 'close'],
+ },
+ readable: {
+ notAfter: ['end', 'error', 'close'],
+ },
+ resume: {
+ notAfter: ['end', 'error', 'close'],
+ },
+ }, true);
}
// Manually shove something into the read() buffer.
diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js
index f39393b06..8f5730706 100644
--- a/lib/_stream_writable.js
+++ b/lib/_stream_writable.js
@@ -155,6 +155,27 @@ function Writable(options) {
this.writable = true;
Stream.call(this);
+ this.strictEERegister({
+ drain: {
+ notAfter: ['finish', 'close', 'error'],
+ },
+ finish: {
+ maxCount: 1,
+ after: ['prefinish'],
+ notAfter: ['close', 'error'],
+ },
+ pipe: {
+ notAfter: ['close', 'error', 'finish'],
+ },
+ prefinish: {
+ maxCount: 1,
+ notAfter: ['close', 'error', 'finish'],
+ },
+ unpipe: {
+ after: ['pipe'],
+ notAfter: ['close', 'error'],
+ },
+ }, true);
}
// Otherwise people can pipe Writable streams, which is just wrong.
diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js
index 6e2a43084..ac4873fa3 100644
--- a/lib/_tls_wrap.js
+++ b/lib/_tls_wrap.js
@@ -235,6 +235,18 @@ function TLSSocket(socket, options) {
readable: false,
writable: false
});
+ this.strictEERegister({
+ secure: {
+ maxCount: 1,
+ },
+ secureConnect: {
+ maxCount: 1,
+ after: ['connect', 'secure'],
+ },
+ _tlsError: {
+ maxCount: 1,
+ },
+ });
if (socket) {
this._parent = socket;
@@ -674,6 +686,12 @@ function Server(/* [options], listener */) {
}
});
});
+ this.strictEERegister({
+ secureConnection: {
+ after: ['listening', 'connection'],
+ notAfter: ['close', 'error'],
+ },
+ });
if (listener) {
this.on('secureConnection', listener);
diff --git a/lib/events.js b/lib/events.js
index 0cd841b84..b6bc9a866 100644
--- a/lib/events.js
+++ b/lib/events.js
@@ -19,7 +19,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
-var domain;
+var assert, domain;
var util = require('util');
function EventEmitter() {
@@ -315,3 +315,163 @@ EventEmitter.listenerCount = function(emitter, type) {
ret = emitter._events[type].length;
return ret;
};
+
+// StrictEE
+var strictID = 0;
+var expectedEvents = {};
+
+function StrictEE(rules, override) {
+ if (EventEmitter.TrackMinCount === undefined) {
+ EventEmitter.TrackMinCount = process.NODE_STRICT_EVENTS || false;
+ process.on('exit', function StrictEEOnExit() {
+ if (EventEmitter.TrackMinCount && Object.keys(expectedEvents).length > 0) {
+ (function MinCountAssertionFailure(ee) {
+ process.abort();
+ })(expectedEvents);
+ }
+ });
+ }
+
+ if (EventEmitter.RequireEventDef === undefined) {
+ EventEmitter.RequireEventDef = process.NODE_REQUIRE_EVENTS || true;
+ }
+
+ if (assert === undefined)
+ assert = require('assert');
+
+ if (!(this instanceof StrictEE))
+ return new StrictEE(rules);
+
+ this._strictID = undefined;
+ this.strictEERegister(rules, override);
+
+ EventEmitter.call(this);
+}
+util.inherits(StrictEE, EventEmitter);
+
+
+EventEmitter.StrictEE = StrictEE;
+EventEmitter.TrackMinCount = undefined;
+EventEmitter.RequireEventDef = undefined;
+
+StrictEE.prototype.strictEERegister = function strictRegister(rules, override) {
+ var oldRules = this._strictEERules = this._strictEERules || {};
+ this._strictEvents = this._strictEvents || {};
+
+ var i, e;
+
+ for (i in rules) {
+ if (!override) {
+ var r = oldRules[i];
+ assert.strictEqual(r, undefined,
+ util.format('Event "%s" already registered, %j', i, r));
+ }
+
+ e = rules[i];
+
+ if (util.isArray(e.notAfter) && e.notAfter.indexOf('*') > -1) {
+ assert.strictEqual(e.notAfter.length, 1,
+ util.format(
+ 'Event "%s" notAfter wild card must only have one entry, not %j',
+ i, e.notAfter));
+ }
+
+ if (EventEmitter.TrackMinCount && util.isNumber(e.minCount)) {
+ if (!this._strictID)
+ this._strictID = ++strictID;
+
+ var ee = expectedEvents[strictID] = expectedEvents[strictID] || { __: this };
+
+ ee[i] = e.minCount;
+ }
+ }
+
+ this._strictEERules = util._extend(oldRules, rules);
+};
+
+StrictEE.prototype.emit = function strictEmit(type) {
+ var strictRule = this._strictEERules[type];
+
+ if (!strictRule) {
+ if (EventEmitter.RequireEventDef)
+ assert.ok(false, util.format('Event "%s" was not defined, fired %j',
+ type, Object.keys(this._strictEvents)));
+ else
+ return EventEmitter.prototype.emit.apply(this, arguments);
+ }
+
+ var keys;
+
+ if (util.isArray(strictRule.notAfter) && strictRule.notAfter.length === 1 &&
+ strictRule.notAfter[0] === '*') {
+ keys = Object.keys(this._strictEvents);
+ assert.strictEqual(keys.length, 0,
+ util.format('Event "%s" must come first but came after %j', type, keys));
+ }
+
+ var strictEvent = this._strictEvents[type] || {};
+
+ strictEvent.count = 1 + (strictEvent.count || 0);
+
+ if (util.isNumber(strictRule.maxCount)) {
+ assert.ok(strictEvent.count <= strictRule.maxCount,
+ util.format('Event "%s" fired %d times, more than %d',
+ type, strictEvent.count, strictRule.maxCount));
+ }
+
+ if (util.isNumber(strictRule.minCount)) {
+ assert.ok(this._strictID, 'Object must have a strictID');
+ var oee = expectedEvents[this._strictID];
+ if (oee) {
+ var ee = oee[type];
+ if (ee) {
+ if (--ee === 0)
+ delete oee[type];
+ }
+
+ if (Object.keys(oee) === 1)
+ delete expectedEvents[this._strictID];
+ }
+ }
+
+ var found, i, e;
+
+ if (util.isArray(strictRule.notAfter)) {
+ found = false;
+
+ for (i in strictRule.notAfter) {
+ if (i === '*') continue;
+
+ e = strictRule.notAfter[i];
+ if (this._strictEvents[e]) {
+ found = e;
+ break;
+ }
+ }
+
+ assert.strictEqual(found, false,
+ util.format('Event "%s" must **not** fire after "%s", fired %j',
+ type, found, Object.keys(this._strictEvents)));
+ }
+
+ if (util.isArray(strictRule.after)) {
+ found = false;
+
+ for (i in strictRule.after) {
+ e = strictRule.after[i];
+ if (this._strictEvents[e]) {
+ found = true;
+ break;
+ }
+ }
+
+ console.error(this);
+ assert.strictEqual(found, true,
+ util.format('Event "%s" must fire after at least one of %j, fired %j',
+ type, strictRule.after, Object.keys(this._strictEvents)));
+ }
+
+ this._strictEvents[type] = strictEvent;
+
+ return EventEmitter.prototype.emit.apply(this, arguments);
+};
diff --git a/lib/fs.js b/lib/fs.js
index bd9fde680..8c7efe117 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1567,6 +1567,12 @@ function ReadStream(path, options) {
}, options || {});
Readable.call(this, options);
+ this.strictEERegister({
+ open: {
+ maxCount: 1,
+ notAfter: ['close', 'error'],
+ },
+ });
this.path = path;
this.fd = options.hasOwnProperty('fd') ? options.fd : null;
@@ -1733,6 +1739,12 @@ function WriteStream(path, options) {
options = options || {};
Writable.call(this, options);
+ this.strictEERegister({
+ open: {
+ maxCount: 1,
+ notAfter: ['error', 'close'],
+ },
+ });
this.path = path;
this.fd = null;
@@ -1814,7 +1826,13 @@ WriteStream.prototype.destroySoon = WriteStream.prototype.end;
// SyncWriteStream is internal. DO NOT USE.
// Temporary hack for process.stdout and process.stderr when piped to files.
function SyncWriteStream(fd, options) {
- Stream.call(this);
+ Stream.Writable.call(this);
+ this.strictEERegister({
+ open: {
+ maxOpen: 1,
+ notAfter: ['error', 'close'],
+ },
+ });
options = options || {};
@@ -1825,7 +1843,7 @@ function SyncWriteStream(fd, options) {
options.autoClose : true;
}
-util.inherits(SyncWriteStream, Stream);
+util.inherits(SyncWriteStream, Stream.Writable);
// Export
diff --git a/lib/https.js b/lib/https.js
index f9011648f..f723278f5 100644
--- a/lib/https.js
+++ b/lib/https.js
@@ -34,6 +34,26 @@ function Server(opts, requestListener) {
}
tls.Server.call(this, opts, http._connectionListener);
+ this.strictEERegister({
+ checkContinue: {
+ notAfter: ['close', 'error'],
+ after: ['listening', 'connection'],
+ },
+ request: {
+ notAfter: ['close', 'error'],
+ },
+ socket: {
+ notAfter: ['close', 'error'],
+ },
+ timeout: {
+ after: ['listening', 'connection'],
+ notAfter: ['error', 'close'],
+ },
+ upgrade: {
+ after: ['listening', 'connection'],
+ notAfter: ['close', 'error'],
+ },
+ });
this.httpAllowHalfOpen = false;
diff --git a/lib/net.js b/lib/net.js
index 70baab3bd..d16bba141 100644
--- a/lib/net.js
+++ b/lib/net.js
@@ -197,6 +197,38 @@ function Socket(options) {
this.read(0);
}
}
+
+ this.strictEERegister({
+ agentRemove: {
+ notAfter: ['close', 'error'],
+ },
+ // aborted comes from http
+ connect: {
+ maxCount: 1,
+ notAfter: ['data', 'error', 'end', 'finish', 'close'],
+ },
+ destroy: {
+ //maxCount: 1,
+ notAfter: ['close'],
+ },
+ // XXX
+ // used by httpAgent, and inexplicably we can't actually say it doesn't
+ // happen after any of the events you might consider being final.
+ free: {
+ //notAfter: ['error', 'finish', 'end', 'close'],
+ },
+ lookup: {
+ maxCount: 1,
+ notAfter: ['connect', 'data', 'error', 'end', 'finish', 'close'],
+ },
+ timeout: {
+ notAfter: ['error', 'end', 'close'],
+ },
+ _socketEnd: {
+ maxCount: 1,
+ notAfter: ['error', 'end', 'close'],
+ },
+ });
}
util.inherits(Socket, stream.Duplex);
@@ -856,6 +888,7 @@ Socket.prototype.connect = function(options, cb) {
return Socket.prototype.connect.apply(this, args);
}
+ // XXX this logic is suspect
if (this.destroyed) {
this._readableState.reading = false;
this._readableState.ended = false;
@@ -866,6 +899,7 @@ Socket.prototype.connect = function(options, cb) {
this._writableState.errorEmitted = false;
this.destroyed = false;
this._handle = null;
+ this._strictEvents = {};
}
var self = this;
@@ -1004,7 +1038,32 @@ function afterConnect(status, handle, req, readable, writable) {
function Server(/* [ options, ] listener */) {
if (!(this instanceof Server)) return new Server(arguments[0], arguments[1]);
- events.EventEmitter.call(this);
+ events.StrictEE.call(this, {
+ clientError: {
+ after: ['listening'],
+ notAfter: ['error', 'close'],
+ },
+ close: {
+ after: ['listening', 'error'],
+ maxCount: 1,
+ },
+ connect: {
+ after: ['listening'],
+ notAfter: ['error', 'close'],
+ },
+ connection: {
+ after: ['listening'],
+ notAfter: ['close', 'error'],
+ },
+ error: {
+ notAfter: ['close'],
+ maxCount: 1,
+ },
+ listening: {
+ notAfter: ['close', 'error'],
+ maxCount: 1,
+ },
+ });
var self = this;
@@ -1044,7 +1103,7 @@ function Server(/* [ options, ] listener */) {
this.allowHalfOpen = options.allowHalfOpen || false;
this.pauseOnConnect = !!options.pauseOnConnect;
}
-util.inherits(Server, events.EventEmitter);
+util.inherits(Server, events.StrictEE);
exports.Server = Server;
diff --git a/lib/stream.js b/lib/stream.js
index 073c6ed33..298994305 100644
--- a/lib/stream.js
+++ b/lib/stream.js
@@ -21,7 +21,7 @@
module.exports = Stream;
-var EE = require('events').EventEmitter;
+var EE = require('events').StrictEE;
var util = require('util');
util.inherits(Stream, EE);
@@ -40,7 +40,17 @@ Stream.Stream = Stream;
// part of this class) is overridden in the Readable class.
function Stream() {
- EE.call(this);
+ EE.call(this, {
+ close: {
+ maxCount: 1,
+ //aborted comes from http semantics
+ after: ['error', 'end', 'finish', 'destroy', 'aborted'],
+ },
+ error: {
+ maxCount: 1,
+ notAfter: ['close', 'end', 'finish'],
+ },
+ }, true);
}
Stream.prototype.pipe = function(dest, options) {