diff --git a/RELEASES.md b/RELEASES.md index 4f85802..65e279e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -58,3 +58,8 @@ - fixed '/' webmethod exclusion was effectively '/*', allowing entire site without authentication - added username to session token - fixed unmounted connect app when https + +2.3.3 2016-04-04 +---------------- + +- Pull fixes from 2.5.16 (#22 & #23) diff --git a/lib/client/base.js b/lib/client/base.js index 5e0e6b5..1b246f7 100644 --- a/lib/client/base.js +++ b/lib/client/base.js @@ -356,6 +356,7 @@ HappnClient.prototype.performRequest = function(path, action, data, parameters, callbackHandler.handleResponse = function(e, response){ clearTimeout(this.timedout); + delete this.client.requestEvents[this.eventId]; return this.handler(e, response); }.bind(callbackHandler); diff --git a/lib/services/pubsub/service.js b/lib/services/pubsub/service.js index 46013dd..21c4384 100644 --- a/lib/services/pubsub/service.js +++ b/lib/services/pubsub/service.js @@ -137,8 +137,7 @@ PubSubService.prototype.createResponse = function(e, message, response, local) { } if (['set', 'remove'].indexOf(message.action) > -1) { - - if (!message.parameters || !message.parameters.options || !message.parameters.options.noPublish) { + if (!message.parameters || !message.parameters.noPublish) { this.publish(message, response); } diff --git a/package.json b/package.json index 6d4975e..b5651e2 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "happn", "description": "pub/sub api as a service using primus and mongo & redis or nedb, can work as cluster, single process or embedded using nedb, use in production at your own risk", - "version": "2.3.2", + "version": "2.3.3", "main": "./lib/index", "scripts": { "test": "mocha silence.js test", - "test-cover": "istanbul cover _mocha -- silence.js test" + "test-cover": "istanbul cover _mocha -- silence.js test", + "test-memory": "mocha --expose-gc test/test-memory" }, "keywords": [ "mongo", @@ -48,7 +49,7 @@ "ws": "0.7.2" }, "devDependencies": { - "chai": "^3.2.0", + "chai": "^3.5.0", "coveralls": "^2.11.6", "expect.js": "*", "istanbul": "^0.4.1", diff --git a/test/1_eventemitter_embedded_sanity.js b/test/1_eventemitter_embedded_sanity.js index 9aecf10..6b913b1 100644 --- a/test/1_eventemitter_embedded_sanity.js +++ b/test/1_eventemitter_embedded_sanity.js @@ -3,11 +3,10 @@ describe('1_eventemitter_embedded_sanity', function () { var expect = require('expect.js'); - var happn = require('../lib/index') + var happn = require('../lib/index'); var service = happn.service; var happn_client = happn.client; var async = require('async'); - var testport = 8000; var test_secret = 'test_secret'; var mode = "embedded"; var default_timeout = 10000; @@ -62,7 +61,7 @@ describe('1_eventemitter_embedded_sanity', function () { } }); - after(function(done) { + after(function (done) { happnInstance.stop(done); }); @@ -70,8 +69,8 @@ describe('1_eventemitter_embedded_sanity', function () { var publisherclient; var listenerclient; - /* - We are initializing 2 clients to test saving data against the database, one client will push data into the + /* + We are initializing 2 clients to test saving data against the database, one client will push data into the database whilst another listens for changes. */ before('should initialize the clients', function (callback) { @@ -107,7 +106,6 @@ describe('1_eventemitter_embedded_sanity', function () { }); - it('the listener should pick up a single wildcard event', function (callback) { this.timeout(default_timeout); @@ -115,7 +113,10 @@ describe('1_eventemitter_embedded_sanity', function () { try { //first listen for the change - listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event/*', {event_type: 'set', count: 1}, function (message) { + listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event/*', { + event_type: 'set', + count: 1 + }, function (message) { expect(listenerclient.events['/SET@/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event/*'].length).to.be(0); callback(); @@ -203,21 +204,21 @@ describe('1_eventemitter_embedded_sanity', function () { try { - async.times(timesCount, - function(n, timesCallback){ + async.times(timesCount, + function (n, timesCallback) { - var test_random_path2 = require('shortid').generate(); + var test_random_path2 = require('shortid').generate(); - publisherclient.set(testBasePath + '/' + test_random_path2, { - property1: 'property1', - property2: 'property2', - property3: 'property3' - }, {noPublish: true}, timesCallback); + publisherclient.set(testBasePath + '/' + test_random_path2, { + property1: 'property1', + property2: 'property2', + property3: 'property3' + }, {noPublish: true}, timesCallback); - }, - function(e){ + }, + function (e) { - if (e) return callback(e); + if (e) return callback(e); listenerclient.get(testBasePath + '/' + '*', null, function (e, results) { @@ -225,18 +226,18 @@ describe('1_eventemitter_embedded_sanity', function () { expect(results.length).to.be(timesCount); - results.every(function(result){ + results.every(function (result) { /* - RESULT SHOULD LOOK LIKE THIS - { property1: 'property1', - property2: 'property2', - property3: 'property3', - _meta: - { modified: 1443606046766, - created: 1443606046766, - path: '/1_eventemitter_embedded_sanity/1443606046555_VkyH6cE1l/set_multiple/E17kSpqE1l' } } - */ + RESULT SHOULD LOOK LIKE THIS + { property1: 'property1', + property2: 'property2', + property3: 'property3', + _meta: + { modified: 1443606046766, + created: 1443606046766, + path: '/1_eventemitter_embedded_sanity/1443606046555_VkyH6cE1l/set_multiple/E17kSpqE1l' } } + */ expect(result._meta.path.indexOf(testBasePath) == 0).to.be(true); @@ -248,16 +249,15 @@ describe('1_eventemitter_embedded_sanity', function () { }); - }); + }); + - } catch (e) { callback(e); } }); - it('should set data, and then merge a new document into the data without overwriting old fields', function (callback) { this.timeout(default_timeout); @@ -307,7 +307,7 @@ describe('1_eventemitter_embedded_sanity', function () { } }); - it('should contain the same payload between 2 non-merging consecutive stores', function (done) { + it('should contain the same payload between 2 non-merging consecutive stores', function (done) { var object = {param1: 10, param2: 20}; var firstTimeNonMergeConsecutive; @@ -321,7 +321,7 @@ describe('1_eventemitter_embedded_sanity', function () { done(); } }, function (err) { - expect(err).to.not.be.ok(); + expect(err).to.not.exist; publisherclient.set('setTest/nonMergeConsecutive', object, {}, function (err) { expect(err).to.not.be.ok(); publisherclient.set('setTest/nonMergeConsecutive', object, {}, function (err) { @@ -572,7 +572,10 @@ describe('1_eventemitter_embedded_sanity', function () { try { //first listen for the change - listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event', {event_type: 'set', count: 1}, function (message) { + listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event', { + event_type: 'set', + count: 1 + }, function (message) { expect(listenerclient.events['/SET@/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event'].length).to.be(0); callback(); @@ -726,7 +729,10 @@ describe('1_eventemitter_embedded_sanity', function () { try { //first listen for the change - listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event', {event_type: 'set', count: 1}, function (message) { + listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event', { + event_type: 'set', + count: 1 + }, function (message) { expect(listenerclient.events['/SET@/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/event'].length).to.be(0); callback(); @@ -884,14 +890,16 @@ describe('1_eventemitter_embedded_sanity', function () { }); - }); it('should unsubscribe from an event', function (callback) { var currentListenerId; - listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/on_off_test', {event_type: 'set', count: 0}, function (message) { + listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/on_off_test', { + event_type: 'set', + count: 0 + }, function (message) { //we detach all listeners from the path here ////console.log('ABOUT OFF PATH'); @@ -900,7 +908,10 @@ describe('1_eventemitter_embedded_sanity', function () { if (e) return callback(new Error(e)); - listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/on_off_test', {event_type: 'set', count: 0}, + listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/on_off_test', { + event_type: 'set', + count: 0 + }, function (message) { ////console.log('ON RAN'); @@ -960,8 +971,8 @@ describe('1_eventemitter_embedded_sanity', function () { listenerclient.onAll(function (eventData, meta) { - if (meta.action == '/REMOVE@/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/catch_all' || - meta.action == '/SET@/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/catch_all') + if (meta.action == '/REMOVE@/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/catch_all' || + meta.action == '/SET@/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/catch_all') caughtCount++; if (caughtCount == 2) @@ -1002,7 +1013,10 @@ describe('1_eventemitter_embedded_sanity', function () { if (e) return callback(e); - listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/off_all_test', {event_type: 'set', count: 0}, + listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testsubscribe/data/off_all_test', { + event_type: 'set', + count: 0 + }, function (message) { onHappened = true; callback(new Error('this wasnt meant to happen')); @@ -1033,5 +1047,35 @@ describe('1_eventemitter_embedded_sanity', function () { }); }); -}) -; \ No newline at end of file + it('should not publish with noPublish set', function (done) { + this.timeout(default_timeout); + + var timeout; + //first listen for the change + listenerclient.on('/1_eventemitter_embedded_sanity/' + test_id + '/testNoPublish', { + event_type: 'set', + count: 1 + }, function (message) { + clearTimeout(timeout); + setImmediate(function() { + expect(message).to.not.be.ok(); + }); + }, function (e) { + expect(e).to.not.be.ok(); + + timeout = setTimeout(function () { + listenerclient.off('/1_eventemitter_embedded_sanity/' + test_id + '/testNoPublish', function () { + done(); + }) + }, 1000); + publisherclient.set('/1_eventemitter_embedded_sanity/' + test_id + '/testNoPublish', { + property1: 'property1', + property2: 'property2', + property3: 'property3' + }, {noPublish: true}, function (e, result) { + expect(e).to.not.be.ok(); + }); + }); + + }); +}); \ No newline at end of file diff --git a/test/test-memory/1_websockets_embedded_memory.js b/test/test-memory/1_websockets_embedded_memory.js new file mode 100644 index 0000000..76c9e8e --- /dev/null +++ b/test/test-memory/1_websockets_embedded_memory.js @@ -0,0 +1,114 @@ +if (!global.gc) { + console.log('Not testing memory leaks, run with argument --expose-gc') + process.kill(); +} + +describe('1-websockets-embedded-memory', function () { + + var spawn = require('child_process').spawn; + var should = require('chai').should(); + var happn = require('../../lib/index'); + var happn_client = happn.client; + + var test_secret = 'test_secret'; + var default_timeout = 50000; + var test_id; + + var sep = require('path').sep; + var libFolder = __dirname + sep + 'lib' + sep; + var remote; + + var publisherclient; + + before(function (callback) { + + this.timeout(20000); + + test_id = Date.now() + '_' + require('shortid').generate(); + + remote = spawn('node', [libFolder + '1-remoteService']); + + remote.stdout.on('data', function (data) { + + //console.log(data.toString()); + + if (data.toString().match(/happn version/)) { + happn_client.create({config: {port: 8001}}, function (e, instance) { + + if (e) return callback(e); + + publisherclient = instance; + + callback(); + }); + } + }); + }); + + after(function (done) { + this.timeout(10000); + publisherclient.disconnect(function() { + remote.kill(); + done(); + }) + + }); + + // set this to a value to leave enough time before and after the test to take a snapshot + var snapshotTimer = 0; + + it('should not leak memory when doing remote calls', function (callback) { + + this.timeout(default_timeout); + var timesCount = 15; + var memUsage = []; + + //do a 1000 sets to fill cache + var empty = {}; + for (var i = 0; i < 1000; i++) { + publisherclient.set('/2_websockets_embedded_sanity/' + test_id + '/set_multiple/empty' + i, empty, {}); + } + global.gc(); + + var count = 0; + if (snapshotTimer) console.log('SNAPSHOT'); + setTimeout(doTest, snapshotTimer); + + function doTest() { + + // take memory usage snapshot after each operation + global.gc(); + memUsage[count] = process.memoryUsage().heapUsed; + + // create a large object that should be freed by the next time this function runs. + var object = {}; + for (var i = 0; i < 10000; i++) { + object[i] = Math.random(); + } + + // do a set that should not hold up this closure + publisherclient.set('/2_websockets_embedded_sanity/' + test_id + '/set_multiple/test' + count, object, {}, function (err) { + // need to reference this in the callback as happner might do + object[0].should.exist; + should.not.exist(err); + if (++count == timesCount) { + if (snapshotTimer)console.log('SNAPSHOT'); + return setTimeout(testComplete, snapshotTimer); + } + setImmediate(doTest); + }); + } + + function testComplete(e) { + + if (e) return callback(e); + + //console.log(memUsage); + // If the memory is not freed, the usage goes up by ~230000 + (memUsage[timesCount - 1] - memUsage[timesCount - 2]).should.be.lt(50000); + callback(); + + } + }); + +}); \ No newline at end of file diff --git a/test/test-memory/lib/1-remoteService.js b/test/test-memory/lib/1-remoteService.js new file mode 100644 index 0000000..c933c58 --- /dev/null +++ b/test/test-memory/lib/1-remoteService.js @@ -0,0 +1,40 @@ +/** + * Created by johan on 3/31/16. + */ + +var happn = require('../../../lib/index'); +var service = happn.service; + +var test_secret = 'test_secret'; +var happnInstance = null; + + +service.create({ + mode: 'embedded', + port: 8001, + services: { + auth: { + path: './services/auth/service.js', + config: { + authTokenSecret: 'a256a2fd43bf441483c5177fc85fd9d3', + systemSecret: test_secret + } + }, + data: { + path: './services/data_embedded/service.js', + config: {} + }, + pubsub: { + path: './services/pubsub/service.js' + } + }, + utils: { + log_level: 'info|error|warning', + log_component: 'prepare' + } +}, function (e, happnInst) { + if (e) + return callback(e); + + happnInstance = happnInst; +}); \ No newline at end of file