@jayesol/jayeson.lib.delivery 2.0.6 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -96
- package/lib/CoreMessages.js +248 -1
- package/lib/DeliveryStack.js +1212 -1
- package/lib/Subscriber.js +1083 -1
- package/lib/index.js +7 -1
- package/lib/util.js +64 -1
- package/package.json +40 -41
package/lib/Subscriber.js
CHANGED
|
@@ -1 +1,1083 @@
|
|
|
1
|
-
var coreMsges=require("./CoreMessages"),deliveryStack=require("./DeliveryStack"),_=require("underscore"),EventEmitter=require("eventemitter3"),streamFinder=require("@jayesol/jayeson.lib.streamfinder"),Discoverer=streamFinder.Discoverer,util=require("./util"),_c=util._c,DefaultClient=deliveryStack.DefaultClient,EPEvent=deliveryStack.EPEvent,EPConnectedEvent=deliveryStack.EPConnectedEvent,EPConnectionError=deliveryStack.EPConnectionError,EPDisconnectedEvent=deliveryStack.EPDisconnectedEvent,IEndPointEventSource=deliveryStack.IEndPointEventSource,generateUUID=deliveryStack.generateUUID,Util=deliveryStack.Util,MessageWrapper=deliveryStack.MessageWrapper,StreamRegistryMessageGroup=coreMsges.StreamRegistryMessageGroup,EmptyMessageClass=coreMsges.EmptyMessageClass,StringMessageClass=coreMsges.StringMessageClass,JSonMessageClass=coreMsges.JSonMessageClass,StreamRegistryRequest=coreMsges.StreamRegistryRequest,StreamRegistryResponse=coreMsges.StreamRegistryResponse,AuthGroup=coreMsges.AuthGroup,GenericMessageGroup=coreMsges.GenericMessageGroup,KeepAliveMessageGroup=coreMsges.KeepAliveMessageGroup,TicketRenew=coreMsges.TicketRenew;function SubscriberConfig(){this.autoDiscover=!0,this.streamFinder="",this.username="",this.password="",this.discoveryPattern={},this.processors={},this.schemes=["ws"],this.ticketRenewalInterval=84e4,this.reconnectionInterval=3e3,this.exclusionThreshold=3,this.feedScope="",this.discoverByScope=!0}var DATA_NODE_COUNTER=0;function DataNode(e,t,s,o,i,n){this.client=null,this.streams={},this.id=e,this.counter=DATA_NODE_COUNTER++,console.log("[DataNode] Node ",e," counter ",this.counter," is created!"),this.state=DataNode.State.CREATED,this.connection=o.connection,this.toBeConsumed=[],this.consumeStream(o),this.clientId=t,this.ticket=s,this.consumeTaskTimeoutID=0,this.eventBus=i,this.sConf=n,this.removalPromises={},this.consumptionPromises={}}function Subscriber(e){IEndPointEventSource.call(this),this.conf=new SubscriberConfig,_.extend(this.conf,e),this.consuming={},this.nodes={},this.currentAvailableSources=null,this.hasUsedCurrentSources=!1,this.clientId=this.uuid(),this.ticket=null,this.eventBus=new EventEmitter,this.eventBus.on(DataNode.Event.RECONNECTION_ERROR,_.bind(this.onNodeReconnectionError,this)),this.eventBus.on(DataNode.Event.STATE,_.bind(this.onNodeStateChanged,this)),this.eventBus.on(DataNode.Event.CONSUMPTION_START,_.bind(this.onStreamConsumption,this)),this.eventBus.on(DataNode.Event.CONSUMPTION_ERROR,_.bind(this.onStreamError,this)),this.eventBus.on(DataNode.Event.CONSUMPTION_STOP,_.bind(this.onStreamStop,this))}function StreamConsumptionWrapper(e,t){this.state=e,this.node=t}function ConsumptionStart(e,t){EPEvent.call(this,e._ep),this.client=e,this.streams=t}function ConsumptionStop(e,t){EPEvent.call(this,e._ep),this.client=e,this.streams=t}function ConsumptionError(e,t){EPEvent.call(this,e._ep),this.client=e,this.streams=t}DataNode.State={CREATED:1,CONNECTING:2,CONNECTED:3,RECONNECTING:4,DISCONNECTED:5,DESTROYED:6},DataNode.Event={STATE:"DN_STATE",RECONNECTION_ERROR:"DN_RE_ERROR",CONSUMPTION_ERROR:"DN_CONSUMPTION_ERROR",CONSUMPTION_START:"DN_CONSUMPTION_START",CONSUMPTION_STOP:"DN_CONSUMPTION_STOP"},_c(DataNode,{removeStream:function(n,r,a){var c=Util._streamIdFromNames(n,r),e=_.bind(function(e){var t,s=_.bind(function(e){if(e&&this.streams[n]){var t;for(t=0;t<this.streams[n].length&&r!=this.streams[n][t].stream;t++);t<this.streams[n].length&&this.streams[n].splice(t,1),0==this.streams[n].length&&delete this.streams[n],_.isEmpty(this.streams)&&this.destroy()}return e},this);if(this.state==DataNode.State.CONNECTED&&a){t=Util._newPromise(),this.removalPromises[c]=t;var o=new StreamRegistryRequest(n,[r],StreamRegistryRequest.DEREGISTRATION);this.client.send(new MessageWrapper(o,StreamRegistryMessageGroup.REQUEST));var i=this;return window.setTimeout(function(){if(void 0!==i.removalPromises[c]){console.warn("Timeout waiting for deregistration response, proceeding to treat stream "+n+":"+r+" as deregistered for "+i.connection.hostname+":"+i.connection.port);var e={};e[r]=StreamRegistryResponse.DEREGISTRATION_TIMEOUT;var t=new StreamRegistryResponse(n,e,StreamRegistryResponse.DEREGISTRATION);i._processDeregistrationResult(t)}},7e3),t.then(s)}return t=Promise.resolve(s(!0))},this);return this.consumptionPromises&&void 0!==this.consumptionPromises[c]?this.consumptionPromises[c].then(e):e(!0)},hasStream:function(e,t){return!!this.streams[e]&&void 0!==(t=_.each(this.streams[e],function(e){return e.stream==t}))},consumeStream:function(e){console.log("Node "+this.id+" is going to consume "+e.group+"_"+e.stream),this.toBeConsumed.push(e),this.streams[e.group]||(this.streams[e.group]=[]),this.streams[e.group].push(e),0==this.consumeTaskTimeoutID&&this.state==DataNode.State.CONNECTED&&(this.consumeTaskTimeoutID=window.setTimeout(_.bind(this.consumeTask,this),100))},_consumeStreamsOnReconnected:function(){for(var e in _.isEmpty(this.streams)&&this.destroy(),this.streams)if(this.streams.hasOwnProperty(e))for(var t=0;t<this.streams[e].length;t++)this.toBeConsumed.push(this.streams[e][t])},connect:function(){console.log("[DataNode] Id ",this.id," counter ",this.counter," is going to connect. Current state ",this.state),this.client=new DefaultClient({url:this._getUrl(this.connection),msgClass:AuthGroup.classById(1),params:{clientId:this.clientId,ticket:this.ticket,feedScope:this.sConf.feedScope}},KeepAliveMessageGroup),this.client.attachListener(this),this.client.enableKeepAlive(),this.client.registerGroup(AuthGroup,this),this.client.registerGroup(StreamRegistryMessageGroup,this),this.client.connect()},reconnect:function(){console.log("[DataNode] Id ",this.id," counter ",this.counter," is going to re-connect. Current state ",this.state),this.client.config.params={clientId:this.clientId,ticket:this.ticket,feedScope:this.sConf.feedScope},this.client.connect()},_getUrl:function(e){return e.protocol+"://"+e.hostname+(void 0!==e.port?":"+e.port:"")+(void 0!==e.path?e.path:"")},consumeTask:function(){var n=this;if(this.state==DataNode.State.CONNECTED){var e=_.groupBy(this.toBeConsumed,function(e){return e.group});this.toBeConsumed=[],this.consumeTaskTimeoutID=0,_.each(e,function(e,t){var s=_.filter(e,function(e){var t=Util._streamId(e);return void 0===n.removalPromises[t]&&void 0===n.consumptionPromises[t]}),o=(_.reduce(s,function(e,t){var s=Util._streamId(t);return e[s]=Util._newPromise(),e[s].then(function(e){console.log("Consumption result of ",s," : successful = ",e)}),e},n.consumptionPromises),_.map(s,function(e){return e.stream})),i=new StreamRegistryRequest(t,o,StreamRegistryRequest.REGISTRATION);n.client.send(new MessageWrapper(i,StreamRegistryMessageGroup.REQUEST))})}},onEvent:function(e){if(console.log("[DataNode] Event from the stack client",e),e instanceof EPConnectedEvent)this.state===DataNode.State.DISCONNECTED&&this._consumeStreamsOnReconnected(),this.setState(DataNode.State.CONNECTED),console.log("[DataNode] Connect / Re-connect to ",this.connection.hostname,":",this.connection.port,"successfully. Consumption will be started!"),this.consumeTaskTimeoutID=window.setTimeout(_.bind(this.consumeTask,this),100);else if(e instanceof EPDisconnectedEvent||e instanceof EPConnectionError){if(this.state==DataNode.State.DESTROYED)return;this.setState(DataNode.State.DISCONNECTED),console.log("Connect / Re-connect to ",this.connection.hostname,":",this.connection.port," with error. Retry again later."),_.each(this.consumptionPromises,function(e,t){e.resolve(!1)}),_.each(this.removalPromises,function(e,t){e.resolve(!0)}),delete this.consumptionPromises,delete this.removalPromises,this.consumptionPromises={},this.removalPromises={},this.state!=DataNode.State.DESTROYED?window.setTimeout(_.bind(this.reconnect,this),this.sConf.reconnectionInterval):console.log("Data node ",this.id," has been destroyed. Stop reconnecting."),0!==this.consumeTaskTimeoutID&&window.clearTimeout(this.consumeTaskTimeoutID),e instanceof EPConnectionError?this.eventBus.emit(DataNode.Event.RECONNECTION_ERROR,this,++this.numFailedReconnection):this.numFailedReconnection=0}},process:function(e){if(e.message instanceof StreamRegistryResponse){var t=e.message;t.responseType==StreamRegistryResponse.REGISTRATION?this._processRegistrationResult(t):t.responseType==StreamRegistryResponse.CONSUMPTION?this._processConsumptionResult(t):t.responseType==StreamRegistryResponse.DEREGISTRATION?this._processDeregistrationResult(t):t.responseType==StreamRegistryResponse.STOP_CONSUMPTION&&this._processStopConsumptionResult(t)}},_resolveConsumptionPromise:function(e,t,s){var o=Util._streamIdFromNames(e,t);void 0!==this.consumptionPromises[o]&&this.consumptionPromises[o].resolve(s),delete this.consumptionPromises[o]},_resolveRemovalPromise:function(e,t,s){var o=Util._streamIdFromNames(e,t);void 0!==this.removalPromises[o]&&(this.removalPromises[o].resolve(s),delete this.removalPromises[o]),delete this.consumptionPromises[o]},_processDeregistrationResult:function(e){var s=this,o=e.messageGroup,i={};_.each(e.streams,function(e,t){e==StreamRegistryResponse.FAILED?s._resolveRemovalPromise(o,t,!1):(s._resolveRemovalPromise(o,t,!0),void 0===i[o]&&(i[o]=[]),i[o].push(t))}),0==_.isEmpty(i)&&(console.log("[DataNode] Emitting consumption error after Deregistration"),this.eventBus.emit(DataNode.Event.CONSUMPTION_ERROR,this,i))},_processStopConsumptionResult:function(e){var s=this,o=e.messageGroup,i={};_.each(e.streams,function(e,t){console.log("Stopping stream "+t),s.removeStream(o,t,!1),void 0===i[o]&&(i[o]=[]),i[o].push(t)}),_.isEmpty(i)||this.eventBus.emit(DataNode.Event.CONSUMPTION_STOP,this,i)},_processConsumptionResult:function(e){var s=this,o=e.messageGroup,i={},n={};_.each(e.streams,function(e,t){e==StreamRegistryResponse.FAILED?(s._resolveConsumptionPromise(o,t,!1),s.removeStream(o,t,!0),void 0===n[o]&&(n[o]=[]),n[o].push(t)):(s._resolveConsumptionPromise(o,t,!0),void 0===i[o]&&(i[o]=[]),i[o].push(t))}),0==_.isEmpty(i)&&this.eventBus.emit(DataNode.Event.CONSUMPTION_START,this,i),0==_.isEmpty(n)&&this.eventBus.emit(DataNode.Event.CONSUMPTION_ERROR,this,n)},_processRegistrationResult:function(e){var t=this,s=e.messageGroup,o=_.pairs(e.streams).filter(function(e){return e[1]==StreamRegistryResponse.FAILED}).map(_.first);o.forEach(function(e){t._resolveConsumptionPromise(s,e,!1),t.removeStream(s,e,!1)});var i=_.pairs(e.streams).filter(function(e){return e[1]==StreamRegistryResponse.SUCCESS}).map(_.first).filter(function(e){return void 0!==t.sConf.processors[s]||(t._resolveConsumptionPromise(s,e,!1),!1)});if(!_.isEmpty(i)){var n=t.sConf.processors[s];t.client.registerGroup(n.group(),n);var r=new StreamRegistryRequest(s,i,StreamRegistryRequest.CONSUMPTION);t.client.send(new MessageWrapper(r,StreamRegistryMessageGroup.REQUEST))}if(!_.isEmpty(o)){var a={};a[s]=o,console.log("Emitting consumption error due to registration result failed.",a),this.eventBus.emit(DataNode.Event.CONSUMPTION_ERROR,this,a)}},setState:function(e){if(e!=this.state){var t=this.state;this.state=e,this.eventBus.emit(DataNode.Event.STATE,this,t)}},renewTicket:function(e){this.ticket=e;var t=new MessageWrapper(new TicketRenew(this.clientId,this.ticket),AuthGroup.TICKET_RENEW);this.state==DataNode.State.CONNECTED&&this.client.send(t)},destroy:function(){this.setState(DataNode.State.DESTROYED),console.log("[DataNode] Disconnect the destroyed data node."),this.client.disconnect()}}),_c(ConsumptionStart,{},EPEvent),_c(ConsumptionStop,{},EPEvent),_c(ConsumptionError,{},EPEvent),Subscriber.Event={ConsumptionStart:ConsumptionStart,ConsumptionStop:ConsumptionStop,ConsumptionError:ConsumptionError},_c(Subscriber,{consumptionState:{PENDING:1,ERROR:3},uuid:function(){return generateUUID()},config:function(){return this.conf},start:function(){var s=new Discoverer(this.config().streamFinder);this.conf.discoverByScope&&(s=new Discoverer(this.config().streamFinder,void 0,this.conf.feedScope)),_.each(this.config().discoveryPattern,function(e,t){_.each(e,function(e){s.discover(t,e)})}),_.each(this.config().schemes,function(e){s.using(e)}),s.on("update",_.bind(this._updateSources,this)),s.on("error",_.bind(this._discovererError,this)),setInterval(_.bind(this._reconnectStreams,this),5e3),this._makeTicket(!0),this.discoverer=s},_makeTicket:function(s){SessionFactory.makeTicket(this.config().streamFinder,this.clientId,_.bind(function(e,t){0!=e.length?(this.ticket=e,console.log("New ticket obtained "+this.ticket),s?this.discoverer.start():_.each(this.nodes,_.bind(function(e,t){window.setTimeout(_.bind(e.renewTicket,e,this.ticket),0)},this)),window.setTimeout(_.bind(this._makeTicket,this),this.config().ticketRenewalInterval)):0!=t.length&&window.setTimeout(_.bind(this._makeTicket,this),1e3)},this))},_updateSources:function(e){console.log("Updating Sources!"),console.log(e),this.oldSources=this.currentAvailableSources,this.currentAvailableSources=e.streamConnections,this.hasUsedCurrentSources=!1;var s={},o={};if(this.oldSources&&0<this.oldSources.length){var i=[],n=[];_.each(e.streamConnections,function(e){i.push(Util._streamId(e)),n.push(Util._nodeId(e))});var r={};_.each(this.oldSources,_.bind(function(t){_.contains(i,Util._streamId(t))&&_.contains(n,Util._nodeId(t))||(console.log("[Subscriber] ",t," is no longer in the discovery results. Going to be removed."),_.each(this.nodes,_.bind(function(e){e.hasStream(t.group,t.stream)&&(console.log("[Subscriber] Remove old stream ",t," from Data Node ",e.id," dataNode counter ",e.counter),e.removeStream(t.group,t.stream,!0),void 0===r[t.group]&&(r[t.group]=[]),r[t.group].push(t.stream)),_.isEmpty(r)||(console.log("[DataNode] Emitting consumption error after removing streams"),this.eventBus.emit(DataNode.Event.CONSUMPTION_ERROR,e,r))},this)))},this))}_.each(e.streamConnections,_.bind(function(e){if(!this._exist(e)||this._error(e)||this._hasUpdatedConnection(e)){var t=Util._nodeId(e);this._hasNode(t)?(null==o[t]&&(o[t]=[]),o[t].push(e)):(null==s[t]&&(s[t]=[]),s[t].push(e))}},this)),this._connectUpdate(s),this._connectUpdate(o)},_reconnectStreams:function(e){var s={},o={};_.each(this.discoverer.getSources().streamConnections,_.bind(function(e){if(!this._exist(e)||this._error(e)&&this._hasUpdatedConnection(e)){console.log("Reconnecting to stopped stream ",e);var t=Util._nodeId(e);this._hasNode(t)?(null==o[t]&&(o[t]=[]),o[t].push(e)):(null==s[t]&&(s[t]=[]),s[t].push(e))}},this)),this._connectUpdate(s),this._connectUpdate(o)},onNodeReconnectionError:function(e,t){t==this.config().exclusionThreshold&&(this.discoverer.excluding(e.id),this._updateSourcesOnNodeReconnectionError(),console.log("Excluding node ",e.id," from discovery"))},_updateSourcesOnNodeReconnectionError:function(){0==this.hasUsedCurrentSource&&0==_.isEmpty(this.currentAvailableSources)&&(this._updateSources(this.currentAvailableSources),this.hasUsedCurrentSources=!0)},onNodeStateChanged:function(e,t){if(e.state==DataNode.State.DESTROYED||e.state==DataNode.State.DISCONNECTED){for(var s in this.consuming)this.consuming.hasOwnProperty(s)&&this.consuming[s].node.id==e.id&&(this.consuming[s].state=this.consumptionState.ERROR);var o={};for(var s in e.streams)if(e.streams.hasOwnProperty(s))for(streamInfo in o[s]=[],e.streams[s])o[s].push(streamInfo.stream);_.isEmpty(o)||(console.log("[Subscriber] emitting consumption error due to data node failed.",e.state),this.dispatch(new Subscriber.Event.ConsumptionError(e.client,o)))}t===DataNode.State.DISCONNECTED&&e.state===DataNode.State.CONNECTED?(this.discoverer.removeExclusion(e.id),console.log("Node ",e.id," resume successfully. Remove it from exclusion.")):e.state===DataNode.State.DESTROYED&&(this.discoverer.removeExclusion(e.id),delete this.nodes[e.id],console.log("Node ",e.id," is destroyed and removed from subscriber!"))},onStreamConsumption:function(e,t){this.dispatch(new Subscriber.Event.ConsumptionStart(e.client,t))},onStreamError:function(e,t){this.dispatch(new Subscriber.Event.ConsumptionError(e.client,t))},onStreamStop:function(e,t){this.dispatch(new Subscriber.Event.ConsumptionStop(e.client,t))},_discovererError:function(e){},_connectUpdate:function(e){_.each(e,_.bind(function(e,i){_.each(e,_.bind(function(t){var e=Util._streamId(t);this.consuming[e]||(this.consuming[e]=new StreamConsumptionWrapper);var s=this.consuming[e],o=Promise.resolve(0);void 0!==s&&null!=s.node&&s.state==this.consumptionState.ERROR&&(o=s.node.removeStream(t.group,t.stream,!1)),o.done=function(e,t){this.then(e,t).catch(function(e){console.log("Promise Rejected: "+e+"\n"+e.stack)})},s.state=this.consumptionState.PENDING,this.nodes[i]?o.done(_.bind(function(e){this.nodes[i].consumeStream(t),s.node=this.nodes[i]},this)):(this.nodes[i]=new DataNode(i,this.clientId,this.ticket,t,this.eventBus,this.config()),o.done(_.bind(function(e){s.node=this.nodes[i],this.nodes[i].connect()},this)))},this))},this))},_exist:function(e){var t=Util._streamId(e);return!!this.consuming[t]},_error:function(e){var t=Util._streamId(e);return!(!this.consuming[t]||this.consuming[t].state!=this.consumptionState.ERROR)},_hasUpdatedConnection:function(e){var t=Util._streamId(e);return this.consuming[t]&&this.consuming[t].node&&this.consuming[t].node.state!=DataNode.State.DESTROYED?Util._nodeId(e)!=this.consuming[t].node.id&&(console.log("Has updated source because the new node id is different from previous!"),!0):(console.log("Has updated source"),this.consuming[t]?this.consuming[t].node?this.consuming[t].node==DataNode.State.DESTROYED&&console.log("Because the source's node does not exist"):console.log("Because the source is not consumed from a node"):console.log("Because the source is not consuming"),!0)},_hasNode:function(e){return void 0!==this.nodes[e]}},IEndPointEventSource),module.exports=exports={Subscriber:Subscriber,SubscriberConfig:SubscriberConfig};
|
|
1
|
+
var coreMsges = require("./CoreMessages")
|
|
2
|
+
var deliveryStack = require("./DeliveryStack")
|
|
3
|
+
var _ = require("underscore")
|
|
4
|
+
var EventEmitter = require("eventemitter3");
|
|
5
|
+
|
|
6
|
+
var streamFinder = require("@jayesol/jayeson.lib.streamfinder");
|
|
7
|
+
var Discoverer = streamFinder.Discoverer;
|
|
8
|
+
|
|
9
|
+
var util = require("./util")
|
|
10
|
+
var _c = util._c
|
|
11
|
+
|
|
12
|
+
var DefaultClient = deliveryStack.DefaultClient
|
|
13
|
+
var EPEvent = deliveryStack.EPEvent
|
|
14
|
+
var EPConnectedEvent = deliveryStack.EPConnectedEvent
|
|
15
|
+
var EPConnectionError = deliveryStack.EPConnectionError
|
|
16
|
+
var EPDisconnectedEvent = deliveryStack.EPDisconnectedEvent
|
|
17
|
+
var IEndPointEventSource = deliveryStack.IEndPointEventSource
|
|
18
|
+
var generateUUID = deliveryStack.generateUUID;
|
|
19
|
+
var Util = deliveryStack.Util;
|
|
20
|
+
var MessageWrapper = deliveryStack.MessageWrapper;
|
|
21
|
+
|
|
22
|
+
var StreamRegistryMessageGroup = coreMsges.StreamRegistryMessageGroup;
|
|
23
|
+
var EmptyMessageClass = coreMsges.EmptyMessageClass;
|
|
24
|
+
var StringMessageClass = coreMsges.StringMessageClass;
|
|
25
|
+
var JSonMessageClass = coreMsges.JSonMessageClass;
|
|
26
|
+
var StreamRegistryRequest = coreMsges.StreamRegistryRequest;
|
|
27
|
+
var StreamRegistryResponse = coreMsges.StreamRegistryResponse;
|
|
28
|
+
var AuthGroup = coreMsges.AuthGroup;
|
|
29
|
+
var GenericMessageGroup = coreMsges.GenericMessageGroup;
|
|
30
|
+
var KeepAliveMessageGroup = coreMsges.KeepAliveMessageGroup;
|
|
31
|
+
var TicketRenew = coreMsges.TicketRenew;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @author Ryan
|
|
35
|
+
*
|
|
36
|
+
* This depends on external libraries: underscorejs, eventemitter, promisejs
|
|
37
|
+
*
|
|
38
|
+
* This depends on internal libraries: coremessage, deliverystack, jsdiscoverer
|
|
39
|
+
*
|
|
40
|
+
* The subscriber has the following features:
|
|
41
|
+
* TODO: FILL IN THE FEATURES
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Represents a configuration being passed
|
|
47
|
+
* into the subscriber. In the javascript context,
|
|
48
|
+
*
|
|
49
|
+
* The discovery pattern has the following format
|
|
50
|
+
* {
|
|
51
|
+
* id1 : [pattern 1, pattern 2 .... ],
|
|
52
|
+
* id2 : [pattern A, pattern B .... ]
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* example
|
|
56
|
+
*
|
|
57
|
+
* {
|
|
58
|
+
* 8 : ["*_LIVE", "SBO_*"], // price feed
|
|
59
|
+
* 7 : ["order_ryan"] // order data feed for user ryan
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* The processor setup is as following:
|
|
63
|
+
* {
|
|
64
|
+
* id1 : processor 1 of type IMessageGroupProcessor,
|
|
65
|
+
* id2 : processor 2 of type IMessageGroupProcessor
|
|
66
|
+
*
|
|
67
|
+
* }
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
function SubscriberConfig() {
|
|
71
|
+
this.autoDiscover = true; // by default, enable auto discovery
|
|
72
|
+
this.streamFinder = ""; // the URL to connect to the stream finder
|
|
73
|
+
this.username = ""; // if we want to login directly to the stream finder
|
|
74
|
+
this.password = "";
|
|
75
|
+
this.discoveryPattern = {};
|
|
76
|
+
this.processors = {};
|
|
77
|
+
this.schemes = ["ws"]; // by default we consume from ws
|
|
78
|
+
this.ticketRenewalInterval = 14*60*1000; // default to 14 minutes
|
|
79
|
+
this.reconnectionInterval = 3000; // reconnection to a relay node
|
|
80
|
+
this.exclusionThreshold = 3; // the number of reconnection failed before the relay node is added to the excluded list
|
|
81
|
+
this.feedScope = "";
|
|
82
|
+
this.discoverByScope = true; //new version will discover by scope
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Represents a data node. This encapsulate the following information
|
|
87
|
+
* <li>The client used to connect to the node</li>
|
|
88
|
+
* <li>The stream being consumed from the node</li>
|
|
89
|
+
*
|
|
90
|
+
* It also provides functionalities such as reconnection. It listen to ticket being renewed
|
|
91
|
+
* by the subscriber and send the ticket to the server accordingly.
|
|
92
|
+
*
|
|
93
|
+
* @params
|
|
94
|
+
* <li>id - id of the data node</li>
|
|
95
|
+
* <li>streamInfo - the first stream information used for connection<br/>
|
|
96
|
+
* <pre>
|
|
97
|
+
* {
|
|
98
|
+
* "group": "<groupId>",
|
|
99
|
+
* "stream": "<streamName>",
|
|
100
|
+
* "level": <level>,
|
|
101
|
+
* "serviceId" : <serviceId>,
|
|
102
|
+
* "connection": {
|
|
103
|
+
* "protocol": "<protocol>",
|
|
104
|
+
* "hostname": "<hostname>",
|
|
105
|
+
* "port": <port>
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
* </pre>
|
|
109
|
+
* </li>
|
|
110
|
+
* <li>clientId - id of the client</li>
|
|
111
|
+
* <li>ticket - ticket of the subscriber</li>
|
|
112
|
+
* <li>eventBus - an Event Emitter which is responsible to propagate events from DataNode</li>
|
|
113
|
+
* <li>subscriberConfig - configuration of the subscriber that created this data node</li>
|
|
114
|
+
*/
|
|
115
|
+
var DATA_NODE_COUNTER = 0;
|
|
116
|
+
|
|
117
|
+
function DataNode(id, clientId, ticket, streamInfo, eventBus, subscriberConfig) {
|
|
118
|
+
this.client = null;
|
|
119
|
+
/**
|
|
120
|
+
* This data structure has the following form:
|
|
121
|
+
* { group -> [stream information] }
|
|
122
|
+
*/
|
|
123
|
+
this.streams = {};
|
|
124
|
+
this.id = id; // id of the data node
|
|
125
|
+
|
|
126
|
+
this.counter = DATA_NODE_COUNTER++;
|
|
127
|
+
console.log("[DataNode] Node ", id, " counter ", this.counter, " is created!");
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Current state of the data node
|
|
131
|
+
*/
|
|
132
|
+
this.state = DataNode.State.CREATED;
|
|
133
|
+
this.connection = streamInfo.connection;
|
|
134
|
+
|
|
135
|
+
this.toBeConsumed = [];
|
|
136
|
+
|
|
137
|
+
this.consumeStream(streamInfo);
|
|
138
|
+
|
|
139
|
+
this.clientId = clientId;
|
|
140
|
+
this.ticket = ticket;
|
|
141
|
+
|
|
142
|
+
this.consumeTaskTimeoutID = 0;
|
|
143
|
+
|
|
144
|
+
this.eventBus = eventBus;
|
|
145
|
+
|
|
146
|
+
this.sConf = subscriberConfig;
|
|
147
|
+
|
|
148
|
+
// the list of promises which will be resolved when stop consumption messages
|
|
149
|
+
// for a stream is handled and replied by the server. The promise
|
|
150
|
+
// shall be successful if there is disconnection while waiting for the reply
|
|
151
|
+
// Format: streamId -> promiseObject e.g. "8_CROWN"
|
|
152
|
+
// the promise value is true / false
|
|
153
|
+
this.removalPromises = {};
|
|
154
|
+
// the list of promises which will be resolved when consumption messages
|
|
155
|
+
// for a stream is handled and replied by the server. The promise shall
|
|
156
|
+
// resolve to failed if there is disconnection waiting for the reply. The promise
|
|
157
|
+
// is created when the registration (not consumption) for the stream starts.
|
|
158
|
+
// The promise value is true / false.
|
|
159
|
+
this.consumptionPromises = {};
|
|
160
|
+
|
|
161
|
+
};
|
|
162
|
+
DataNode.State = {
|
|
163
|
+
CREATED : 1,
|
|
164
|
+
CONNECTING : 2,
|
|
165
|
+
CONNECTED : 3,
|
|
166
|
+
RECONNECTING : 4,
|
|
167
|
+
DISCONNECTED : 5,
|
|
168
|
+
DESTROYED : 6
|
|
169
|
+
};
|
|
170
|
+
DataNode.Event = {
|
|
171
|
+
STATE : "DN_STATE",
|
|
172
|
+
RECONNECTION_ERROR : "DN_RE_ERROR",
|
|
173
|
+
CONSUMPTION_ERROR : "DN_CONSUMPTION_ERROR",
|
|
174
|
+
CONSUMPTION_START : "DN_CONSUMPTION_START",
|
|
175
|
+
CONSUMPTION_STOP : "DN_CONSUMPTION_STOP",
|
|
176
|
+
};
|
|
177
|
+
_c(DataNode, {
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* This method return a PROMISE of the removal of the stream from the
|
|
181
|
+
* from consumption. This has to be asynchronous because involving sending
|
|
182
|
+
* deregistration message to the stream registry of the consuming node. We however, only
|
|
183
|
+
* send unregister when the node is in CONNECTED STATE and the person invoke this method
|
|
184
|
+
* specify he want to do so (sendDeregistration = true)
|
|
185
|
+
*
|
|
186
|
+
* There are two cases that a stream can be removed from a data node:
|
|
187
|
+
* <li>The node that consuming the stream has an error and needs reconnection</li>
|
|
188
|
+
* <li>A better node can be found for the stream according to the stream selection strategy</li>
|
|
189
|
+
*
|
|
190
|
+
* @return a promise which is resolved when the stream is completely removed from consumption.
|
|
191
|
+
*/
|
|
192
|
+
removeStream : function(group, stream, sendDeregistration) {
|
|
193
|
+
|
|
194
|
+
var id = Util._streamIdFromNames(group, stream);
|
|
195
|
+
|
|
196
|
+
var deregistrationAction = _.bind(function(registrationResult) {
|
|
197
|
+
// the unregistration promise. This is done when the
|
|
198
|
+
// the stream is unregister from this data node by sending message to
|
|
199
|
+
// the data source
|
|
200
|
+
var promise;
|
|
201
|
+
|
|
202
|
+
// the action to be done, remove the stream from the array
|
|
203
|
+
var unregisterPostAction = _.bind(function(result) {
|
|
204
|
+
if (result && this.streams[group]) {
|
|
205
|
+
var index;
|
|
206
|
+
|
|
207
|
+
for (index = 0; index < this.streams[group].length; index++)
|
|
208
|
+
if (stream == this.streams[group][index].stream) break;
|
|
209
|
+
|
|
210
|
+
if (index < this.streams[group].length) this.streams[group].splice(index, 1);
|
|
211
|
+
|
|
212
|
+
if (this.streams[group].length == 0) delete this.streams[group];
|
|
213
|
+
|
|
214
|
+
// if there is no more stream this node is consuming, just destroy itself
|
|
215
|
+
if (_.isEmpty(this.streams)) this.destroy();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return result;
|
|
219
|
+
}, this);
|
|
220
|
+
|
|
221
|
+
if (this.state == DataNode.State.CONNECTED && sendDeregistration) {
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
promise = Util._newPromise();
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
this.removalPromises[id] = promise;
|
|
228
|
+
|
|
229
|
+
var req = new StreamRegistryRequest(group, [stream], StreamRegistryRequest.DEREGISTRATION);
|
|
230
|
+
this.client.send(new MessageWrapper(req, StreamRegistryMessageGroup.REQUEST));
|
|
231
|
+
|
|
232
|
+
var self = this;
|
|
233
|
+
window.setTimeout(function() {
|
|
234
|
+
//proceed with deregistration if no deregistration responses are received.
|
|
235
|
+
if (self.removalPromises[id] !== undefined) {
|
|
236
|
+
console.warn("Timeout waiting for deregistration response, proceeding to treat stream " + group + ":" + stream + " as deregistered for " + self.connection.hostname + ":" + self.connection.port);
|
|
237
|
+
var streamResponse = {};
|
|
238
|
+
streamResponse[stream] = StreamRegistryResponse.DEREGISTRATION_TIMEOUT;
|
|
239
|
+
var response = new StreamRegistryResponse(group, streamResponse, StreamRegistryResponse.DEREGISTRATION);
|
|
240
|
+
|
|
241
|
+
self._processDeregistrationResult(response);
|
|
242
|
+
}
|
|
243
|
+
}, 7000);
|
|
244
|
+
|
|
245
|
+
// chain the promise
|
|
246
|
+
return promise.then(unregisterPostAction);
|
|
247
|
+
|
|
248
|
+
} else {
|
|
249
|
+
// if the DataNode is not in connected state, no need to send anything!
|
|
250
|
+
promise = Promise.resolve(unregisterPostAction(true));
|
|
251
|
+
return promise;
|
|
252
|
+
}
|
|
253
|
+
}, this);
|
|
254
|
+
|
|
255
|
+
// if there is a consumption in progress, wait for it! We will de-register the stream after the it finish consumption
|
|
256
|
+
// de-registration only happens in failing over or switching to better stream, so it's okay to de-register
|
|
257
|
+
// even when the pending consumption is successful. We switch anyway.
|
|
258
|
+
if (this.consumptionPromises && this.consumptionPromises[id] !== undefined) {
|
|
259
|
+
return this.consumptionPromises[id].then(deregistrationAction);
|
|
260
|
+
} else {
|
|
261
|
+
return deregistrationAction(true);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
hasStream : function(group, stream) {
|
|
267
|
+
if (!this.streams[group]) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
var stream = _.each(this.streams[group], function(node) { return node.stream == stream; } );
|
|
271
|
+
return stream !== undefined;
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Start consumption of a particular stream by putting it into tobeConsumed queue.
|
|
276
|
+
* When a stream is consumed successfully, DataNode will emit an event
|
|
277
|
+
* so that other parties know about it.
|
|
278
|
+
*/
|
|
279
|
+
consumeStream : function(streamInfo) {
|
|
280
|
+
|
|
281
|
+
console.log("Node "+this.id+" is going to consume "+streamInfo.group+"_"+streamInfo.stream);
|
|
282
|
+
|
|
283
|
+
this.toBeConsumed.push(streamInfo);
|
|
284
|
+
|
|
285
|
+
if (!this.streams[streamInfo.group]) this.streams[streamInfo.group] = [];
|
|
286
|
+
|
|
287
|
+
this.streams[streamInfo.group].push(streamInfo); // put the stream into the list of streams that this DataNode handling
|
|
288
|
+
|
|
289
|
+
if (this.consumeTaskTimeoutID == 0 && this.state == DataNode.State.CONNECTED)
|
|
290
|
+
this.consumeTaskTimeoutID = window.setTimeout(_.bind(this.consumeTask, this), 100);
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
_consumeStreamsOnReconnected : function(){
|
|
294
|
+
|
|
295
|
+
if (_.isEmpty(this.streams)) this.destroy();
|
|
296
|
+
|
|
297
|
+
for(var group in this.streams){
|
|
298
|
+
if(this.streams.hasOwnProperty(group)){
|
|
299
|
+
for(var i=0 ; i<this.streams[group].length; i++){
|
|
300
|
+
this.toBeConsumed.push(this.streams[group][i]);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
connect : function() {
|
|
307
|
+
|
|
308
|
+
console.log("[DataNode] Id ", this.id, " counter ", this.counter, " is going to connect. Current state ", this.state);
|
|
309
|
+
|
|
310
|
+
//temp code
|
|
311
|
+
//this.connection.hostname = "local.vodds.jayeson.com.sg";
|
|
312
|
+
//this.connection.port = 5000;
|
|
313
|
+
|
|
314
|
+
this.client = new DefaultClient({
|
|
315
|
+
url : this._getUrl(this.connection),
|
|
316
|
+
msgClass : AuthGroup.classById(1),
|
|
317
|
+
params : {
|
|
318
|
+
clientId : this.clientId,
|
|
319
|
+
ticket : this.ticket,
|
|
320
|
+
feedScope : this.sConf.feedScope
|
|
321
|
+
}
|
|
322
|
+
}, KeepAliveMessageGroup);
|
|
323
|
+
this.client.attachListener(this);
|
|
324
|
+
this.client.enableKeepAlive();
|
|
325
|
+
this.client.registerGroup(AuthGroup, this);
|
|
326
|
+
this.client.registerGroup(StreamRegistryMessageGroup, this);
|
|
327
|
+
|
|
328
|
+
this.client.connect();
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
reconnect : function() {
|
|
332
|
+
|
|
333
|
+
console.log("[DataNode] Id ", this.id, " counter ", this.counter, " is going to re-connect. Current state ", this.state);
|
|
334
|
+
|
|
335
|
+
// update the parameters to be updated when reconnect
|
|
336
|
+
// the ticket might have changed!!
|
|
337
|
+
this.client.config.params = {
|
|
338
|
+
clientId : this.clientId,
|
|
339
|
+
ticket : this.ticket,
|
|
340
|
+
feedScope : this.sConf.feedScope
|
|
341
|
+
}
|
|
342
|
+
this.client.connect();
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
_getUrl : function (connection) {
|
|
346
|
+
return connection.protocol+"://"+connection.hostname+((connection.port !== undefined)?(":"+connection.port):"")+((connection.path !== undefined)?connection.path:"");
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* check the consume queue and do registration one by one
|
|
351
|
+
*/
|
|
352
|
+
consumeTask : function () {
|
|
353
|
+
|
|
354
|
+
var self = this;
|
|
355
|
+
|
|
356
|
+
if (this.state == DataNode.State.CONNECTED) {
|
|
357
|
+
var streamByGroup = _.groupBy(this.toBeConsumed, function(streamInfo) { return streamInfo.group; } );
|
|
358
|
+
|
|
359
|
+
// javascript is single threaded so no need to worry about synchronization here!!
|
|
360
|
+
this.toBeConsumed = []; // clear the toBeConsumed list.
|
|
361
|
+
this.consumeTaskTimeoutID = 0; // reset the task handler
|
|
362
|
+
|
|
363
|
+
// for each group to be consumed, create a promise, send a message
|
|
364
|
+
// the promise will be resolved by the onMessage method, or when disconnection
|
|
365
|
+
_.each(streamByGroup, function(streams, group) {
|
|
366
|
+
|
|
367
|
+
// if no one is removing the stream, then we can consume it
|
|
368
|
+
// this is to avoid double consumption from two different node
|
|
369
|
+
// because if removal can only be the result of failing over
|
|
370
|
+
// or switching to a better source
|
|
371
|
+
// if the stream is being in the process of consuming, we don't
|
|
372
|
+
// resend registration as well
|
|
373
|
+
var canConsume = _.filter(streams, function(streamInfo) {
|
|
374
|
+
var sid = Util._streamId(streamInfo);
|
|
375
|
+
|
|
376
|
+
return self.removalPromises[sid] === undefined &&
|
|
377
|
+
self.consumptionPromises[sid] === undefined;
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// we have to create one promise for each stream whose registration
|
|
381
|
+
// is going to be sent, because we handle at stream level
|
|
382
|
+
// store those promises to the consuming list
|
|
383
|
+
var promises = _.reduce(canConsume, function(memo, streamInfo) {
|
|
384
|
+
|
|
385
|
+
var sid = Util._streamId(streamInfo);
|
|
386
|
+
memo[sid] = Util._newPromise();
|
|
387
|
+
memo[sid].then(function(result) {
|
|
388
|
+
console.log("Consumption result of ", sid, " : successful = ",result);
|
|
389
|
+
});
|
|
390
|
+
return memo;
|
|
391
|
+
|
|
392
|
+
}, self.consumptionPromises);
|
|
393
|
+
|
|
394
|
+
// send the registration messages
|
|
395
|
+
var streamNames = _.map(canConsume, function(streamInfo) {
|
|
396
|
+
return streamInfo.stream;
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
var req = new StreamRegistryRequest(group, streamNames, StreamRegistryRequest.REGISTRATION);
|
|
400
|
+
// start consumption by first registration message
|
|
401
|
+
self.client.send(new MessageWrapper(req, StreamRegistryMessageGroup.REQUEST));
|
|
402
|
+
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
onEvent : function(event) {
|
|
409
|
+
|
|
410
|
+
console.log("[DataNode] Event from the stack client", event);
|
|
411
|
+
|
|
412
|
+
if (event instanceof EPConnectedEvent) {
|
|
413
|
+
if(this.state === DataNode.State.DISCONNECTED){
|
|
414
|
+
this._consumeStreamsOnReconnected();
|
|
415
|
+
}
|
|
416
|
+
this.setState(DataNode.State.CONNECTED);
|
|
417
|
+
console.log("[DataNode] Connect / Re-connect to ", this.connection.hostname,":", this.connection.port, "successfully. Consumption will be started!");
|
|
418
|
+
|
|
419
|
+
this.consumeTaskTimeoutID = window.setTimeout(_.bind(this.consumeTask, this), 100);
|
|
420
|
+
|
|
421
|
+
} else if (event instanceof EPDisconnectedEvent || event instanceof EPConnectionError) {
|
|
422
|
+
if (this.state == DataNode.State.DESTROYED) return; // do nothing if the data node is destroyed.
|
|
423
|
+
|
|
424
|
+
this.setState(DataNode.State.DISCONNECTED);
|
|
425
|
+
console.log("Connect / Re-connect to ",this.connection.hostname,":", this.connection.port, " with error. Retry again later.");
|
|
426
|
+
|
|
427
|
+
// disconnection will result in all consumptionPromises resolve to false, and all
|
|
428
|
+
// de-registration promises resolve to true
|
|
429
|
+
_.each(this.consumptionPromises, function(p, key) { p.resolve(false); } );
|
|
430
|
+
_.each(this.removalPromises, function(p, key) { p.resolve(true); } );
|
|
431
|
+
|
|
432
|
+
delete this.consumptionPromises;
|
|
433
|
+
delete this.removalPromises;
|
|
434
|
+
|
|
435
|
+
this.consumptionPromises = {};
|
|
436
|
+
this.removalPromises = {};
|
|
437
|
+
|
|
438
|
+
if (this.state != DataNode.State.DESTROYED) window.setTimeout(_.bind(this.reconnect, this), this.sConf.reconnectionInterval);
|
|
439
|
+
else console.log("Data node ", this.id, " has been destroyed. Stop reconnecting.");
|
|
440
|
+
|
|
441
|
+
if (this.consumeTaskTimeoutID !== 0) window.clearTimeout(this.consumeTaskTimeoutID);
|
|
442
|
+
|
|
443
|
+
if (event instanceof EPConnectionError) this.eventBus.emit(DataNode.Event.RECONNECTION_ERROR, this, ++this.numFailedReconnection);
|
|
444
|
+
else this.numFailedReconnection = 0;
|
|
445
|
+
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
process : function(messageWrapper) {
|
|
451
|
+
|
|
452
|
+
if (messageWrapper.message instanceof StreamRegistryResponse) {
|
|
453
|
+
var rep = messageWrapper.message;
|
|
454
|
+
|
|
455
|
+
if (rep.responseType == StreamRegistryResponse.REGISTRATION) { // registration response
|
|
456
|
+
|
|
457
|
+
this._processRegistrationResult(rep);
|
|
458
|
+
|
|
459
|
+
} else if (rep.responseType == StreamRegistryResponse.CONSUMPTION) {
|
|
460
|
+
|
|
461
|
+
this._processConsumptionResult(rep);
|
|
462
|
+
|
|
463
|
+
} else if (rep.responseType == StreamRegistryResponse.DEREGISTRATION) {
|
|
464
|
+
|
|
465
|
+
this._processDeregistrationResult(rep);
|
|
466
|
+
|
|
467
|
+
} else if (rep.responseType == StreamRegistryResponse.STOP_CONSUMPTION) {
|
|
468
|
+
this._processStopConsumptionResult(rep);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
_resolveConsumptionPromise : function(group, stream, value) {
|
|
475
|
+
var id = Util._streamIdFromNames(group, stream);
|
|
476
|
+
if (this.consumptionPromises[id] !== undefined)
|
|
477
|
+
this.consumptionPromises[id].resolve(value);
|
|
478
|
+
|
|
479
|
+
delete this.consumptionPromises[id];
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
_resolveRemovalPromise : function(group, stream, value) {
|
|
483
|
+
var id = Util._streamIdFromNames(group, stream);
|
|
484
|
+
if (this.removalPromises[id] !== undefined) {
|
|
485
|
+
this.removalPromises[id].resolve(value);
|
|
486
|
+
delete this.removalPromises[id];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
delete this.consumptionPromises[id];
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
_processDeregistrationResult : function (rep) {
|
|
493
|
+
var self = this;
|
|
494
|
+
|
|
495
|
+
var group = rep.messageGroup;
|
|
496
|
+
var errorConsumedStreamByGroup = {}; // contain all unsuccessfully consumed streams
|
|
497
|
+
_.each(rep.streams, function(result, stream) {
|
|
498
|
+
if (result == StreamRegistryResponse.FAILED) {
|
|
499
|
+
self._resolveRemovalPromise(group, stream, false);
|
|
500
|
+
} else {
|
|
501
|
+
self._resolveRemovalPromise(group, stream, true);
|
|
502
|
+
if(errorConsumedStreamByGroup[group] === undefined){
|
|
503
|
+
errorConsumedStreamByGroup[group] = [];
|
|
504
|
+
}
|
|
505
|
+
errorConsumedStreamByGroup[group].push(stream);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
if(_.isEmpty(errorConsumedStreamByGroup) == false){
|
|
509
|
+
console.log("[DataNode] Emitting consumption error after Deregistration")
|
|
510
|
+
this.eventBus.emit(DataNode.Event.CONSUMPTION_ERROR, this, errorConsumedStreamByGroup);
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
|
|
514
|
+
_processStopConsumptionResult : function(rep) {
|
|
515
|
+
var self = this;
|
|
516
|
+
var group = rep.messageGroup;
|
|
517
|
+
var stoppedStreamsByGroup = {};
|
|
518
|
+
_.each(rep.streams, function(result, stream) {
|
|
519
|
+
console.log("Stopping stream " + stream);
|
|
520
|
+
self.removeStream(group, stream, false);
|
|
521
|
+
if(stoppedStreamsByGroup[group] === undefined){
|
|
522
|
+
stoppedStreamsByGroup[group] = [];
|
|
523
|
+
}
|
|
524
|
+
stoppedStreamsByGroup[group].push(stream);
|
|
525
|
+
});
|
|
526
|
+
if (!_.isEmpty(stoppedStreamsByGroup)) {
|
|
527
|
+
this.eventBus.emit(DataNode.Event.CONSUMPTION_STOP, this, stoppedStreamsByGroup);
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
_processConsumptionResult : function (rep) {
|
|
532
|
+
var self = this;
|
|
533
|
+
|
|
534
|
+
var group = rep.messageGroup;
|
|
535
|
+
var consumedStreamByGroup = {}; // contain all successfully consumed streams
|
|
536
|
+
var errorConsumedStreamByGroup = {}; // contain all unsuccessfully consumed streams
|
|
537
|
+
|
|
538
|
+
_.each(rep.streams, function(result, stream) {
|
|
539
|
+
if (result == StreamRegistryResponse.FAILED) {
|
|
540
|
+
self._resolveConsumptionPromise(group, stream, false);
|
|
541
|
+
self.removeStream(group, stream, true);
|
|
542
|
+
|
|
543
|
+
if(errorConsumedStreamByGroup[group] === undefined){
|
|
544
|
+
errorConsumedStreamByGroup[group] = [];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
errorConsumedStreamByGroup[group].push(stream);
|
|
548
|
+
} else {
|
|
549
|
+
self._resolveConsumptionPromise(group, stream, true);
|
|
550
|
+
if(consumedStreamByGroup[group] === undefined){
|
|
551
|
+
consumedStreamByGroup[group] = [];
|
|
552
|
+
}
|
|
553
|
+
consumedStreamByGroup[group].push(stream);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
if(_.isEmpty(consumedStreamByGroup) == false){
|
|
558
|
+
this.eventBus.emit(DataNode.Event.CONSUMPTION_START, this, consumedStreamByGroup);
|
|
559
|
+
}
|
|
560
|
+
if(_.isEmpty(errorConsumedStreamByGroup) == false){
|
|
561
|
+
this.eventBus.emit(DataNode.Event.CONSUMPTION_ERROR, this, errorConsumedStreamByGroup);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
_processRegistrationResult : function(rep) {
|
|
567
|
+
|
|
568
|
+
var self = this;
|
|
569
|
+
var group = rep.messageGroup;
|
|
570
|
+
|
|
571
|
+
// complete and remove any failed promise
|
|
572
|
+
var cannotConsume = _.pairs(rep.streams)
|
|
573
|
+
.filter(function(p) { return p[1] == StreamRegistryResponse.FAILED; })
|
|
574
|
+
.map(_.first);
|
|
575
|
+
|
|
576
|
+
// remove these streams from the node
|
|
577
|
+
cannotConsume.forEach(function(streamName) {
|
|
578
|
+
self._resolveConsumptionPromise(group, streamName, false);
|
|
579
|
+
self.removeStream(group, streamName, false); // the registration is failed, so no need to send deregistration
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
var canConsume = _.pairs(rep.streams)
|
|
583
|
+
.filter(function(p) { return p[1] == StreamRegistryResponse.SUCCESS; })
|
|
584
|
+
.map(_.first) // get the stream name
|
|
585
|
+
.filter(function(streamName) {
|
|
586
|
+
if (self.sConf.processors[group] !== undefined) { // can consume because processor for the group is defined
|
|
587
|
+
return true;
|
|
588
|
+
} else { // cannot consume because processor is undefined
|
|
589
|
+
self._resolveConsumptionPromise(group, streamName, false);
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// send consumption message, at this point, the server can be sure that the processor is ready to handle the stream
|
|
595
|
+
if (!_.isEmpty(canConsume)) {
|
|
596
|
+
|
|
597
|
+
var processor = self.sConf.processors[group];
|
|
598
|
+
self.client.registerGroup(processor.group(), processor); // attach processor for all the consumable group, whose processor is defined.
|
|
599
|
+
|
|
600
|
+
var req = new StreamRegistryRequest(group, canConsume, StreamRegistryRequest.CONSUMPTION);
|
|
601
|
+
self.client.send(new MessageWrapper(req, StreamRegistryMessageGroup.REQUEST));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (!_.isEmpty(cannotConsume)) {
|
|
605
|
+
var eventData = {};
|
|
606
|
+
eventData[group] = cannotConsume;
|
|
607
|
+
console.log("Emitting consumption error due to registration result failed.", eventData);
|
|
608
|
+
|
|
609
|
+
this.eventBus.emit(DataNode.Event.CONSUMPTION_ERROR, this, eventData);
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
setState : function(state) {
|
|
614
|
+
|
|
615
|
+
if (state != this.state) {
|
|
616
|
+
var oldState = this.state;
|
|
617
|
+
this.state = state;
|
|
618
|
+
this.eventBus.emit(DataNode.Event.STATE, this, oldState);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
},
|
|
622
|
+
|
|
623
|
+
renewTicket : function(ticket) {
|
|
624
|
+
this.ticket = ticket;
|
|
625
|
+
var wrapper = new MessageWrapper(new TicketRenew(this.clientId, this.ticket), AuthGroup.TICKET_RENEW);
|
|
626
|
+
if (this.state == DataNode.State.CONNECTED) this.client.send(wrapper);
|
|
627
|
+
},
|
|
628
|
+
|
|
629
|
+
destroy : function() {
|
|
630
|
+
this.setState(DataNode.State.DESTROYED);
|
|
631
|
+
console.log("[DataNode] Disconnect the destroyed data node.");
|
|
632
|
+
this.client.disconnect();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
function Subscriber(configuration) {
|
|
638
|
+
IEndPointEventSource.call(this); // initial IEndPointEventSource
|
|
639
|
+
this.conf = new SubscriberConfig();
|
|
640
|
+
|
|
641
|
+
_.extend(this.conf, configuration); // copy the configuration over
|
|
642
|
+
|
|
643
|
+
this.consuming = {}; // a map from group / stream to the wrapper
|
|
644
|
+
this.nodes = {}; // a map from source id to the information related to the source, e.g. state whether connecting, reconnecting, connected or the client used to connect to the node
|
|
645
|
+
|
|
646
|
+
// store the currently available sources retrieved from the discoverer
|
|
647
|
+
// this data will be used for fast failover whenever there is some
|
|
648
|
+
// connection error in a DataNode.
|
|
649
|
+
this.currentAvailableSources = null;
|
|
650
|
+
// Check currentAvailableSources has used.
|
|
651
|
+
this.hasUsedCurrentSources = false;
|
|
652
|
+
|
|
653
|
+
this.clientId = this.uuid();
|
|
654
|
+
this.ticket = null;
|
|
655
|
+
|
|
656
|
+
this.eventBus = new EventEmitter();
|
|
657
|
+
|
|
658
|
+
this.eventBus.on(DataNode.Event.RECONNECTION_ERROR, _.bind(this.onNodeReconnectionError, this));
|
|
659
|
+
this.eventBus.on(DataNode.Event.STATE, _.bind(this.onNodeStateChanged, this));
|
|
660
|
+
this.eventBus.on(DataNode.Event.CONSUMPTION_START, _.bind(this.onStreamConsumption,this));
|
|
661
|
+
this.eventBus.on(DataNode.Event.CONSUMPTION_ERROR, _.bind(this.onStreamError, this));
|
|
662
|
+
this.eventBus.on(DataNode.Event.CONSUMPTION_STOP, _.bind(this.onStreamStop, this));
|
|
663
|
+
}
|
|
664
|
+
function StreamConsumptionWrapper(state, node) {
|
|
665
|
+
this.state = state;
|
|
666
|
+
this.node = node;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Define Subscriber event and will be fired everytime a client is connected or disconnected.
|
|
671
|
+
*/
|
|
672
|
+
function ConsumptionStart(client, streams) {
|
|
673
|
+
EPEvent.call(this, client._ep);
|
|
674
|
+
this.client = client;
|
|
675
|
+
this.streams = streams;
|
|
676
|
+
}
|
|
677
|
+
function ConsumptionStop(client, streams) {
|
|
678
|
+
EPEvent.call(this, client._ep);
|
|
679
|
+
this.client = client;
|
|
680
|
+
this.streams = streams;
|
|
681
|
+
}
|
|
682
|
+
function ConsumptionError(client, streams) {
|
|
683
|
+
EPEvent.call(this, client._ep);
|
|
684
|
+
this.client = client;
|
|
685
|
+
this.streams = streams;
|
|
686
|
+
}
|
|
687
|
+
_c(ConsumptionStart, {}, EPEvent);
|
|
688
|
+
_c(ConsumptionStop, {}, EPEvent);
|
|
689
|
+
_c(ConsumptionError, {}, EPEvent);
|
|
690
|
+
|
|
691
|
+
Subscriber.Event = {
|
|
692
|
+
ConsumptionStart : ConsumptionStart,
|
|
693
|
+
ConsumptionStop : ConsumptionStop,
|
|
694
|
+
ConsumptionError : ConsumptionError
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
_c(Subscriber, {
|
|
698
|
+
|
|
699
|
+
consumptionState : {
|
|
700
|
+
PENDING : 1, // a stream is in this state when it is waiting for a data node to connect and then perform registration
|
|
701
|
+
ERROR : 3 // a stream is in this state when its data node is having connection problem
|
|
702
|
+
},
|
|
703
|
+
|
|
704
|
+
uuid : function (){
|
|
705
|
+
|
|
706
|
+
return generateUUID();
|
|
707
|
+
|
|
708
|
+
},
|
|
709
|
+
|
|
710
|
+
config : function() { return this.conf; },
|
|
711
|
+
/**
|
|
712
|
+
* This method starts the discovery process
|
|
713
|
+
*/
|
|
714
|
+
start : function() {
|
|
715
|
+
var discoverer = new Discoverer(this.config().streamFinder); // create a discoverer to connect to stream finder
|
|
716
|
+
if (this.conf.discoverByScope) {
|
|
717
|
+
discoverer = new Discoverer(this.config().streamFinder, undefined, this.conf.feedScope); // discover by streamfinder scope
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// configure discovery
|
|
721
|
+
_.each(this.config().discoveryPattern, function(patternList, group) {
|
|
722
|
+
|
|
723
|
+
_.each(patternList, function(pattern) {
|
|
724
|
+
discoverer.discover(group, pattern);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
_.each(this.config().schemes, function(scheme) {
|
|
730
|
+
discoverer.using(scheme);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
discoverer.on("update", _.bind(this._updateSources, this));
|
|
734
|
+
discoverer.on("error", _.bind(this._discovererError, this));
|
|
735
|
+
setInterval(_.bind(this._reconnectStreams, this), 5000);
|
|
736
|
+
this._makeTicket(true);
|
|
737
|
+
|
|
738
|
+
this.discoverer = discoverer;
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
_makeTicket : function(isStarting) {
|
|
742
|
+
SessionFactory.makeTicket(this.config().streamFinder, this.clientId, _.bind(function(ticket, error) {
|
|
743
|
+
if (ticket.length != 0) {
|
|
744
|
+
|
|
745
|
+
this.ticket = ticket;
|
|
746
|
+
console.log("New ticket obtained "+this.ticket);
|
|
747
|
+
|
|
748
|
+
if (isStarting) { // if this is not renewal, we should start the discovery process
|
|
749
|
+
this.discoverer.start();
|
|
750
|
+
} else {
|
|
751
|
+
_.each(this.nodes, _.bind(function(node, id) {
|
|
752
|
+
window.setTimeout(_.bind(node.renewTicket, node, this.ticket), 0); // asynchronously
|
|
753
|
+
}, this));
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
window.setTimeout(_.bind(this._makeTicket, this), this.config().ticketRenewalInterval);
|
|
757
|
+
|
|
758
|
+
} else if (error.length != 0) {
|
|
759
|
+
window.setTimeout(_.bind(this._makeTicket, this), 1000);
|
|
760
|
+
}
|
|
761
|
+
}, this));
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
_updateSources : function(sources) {
|
|
765
|
+
|
|
766
|
+
console.log("Updating Sources!");
|
|
767
|
+
console.log(sources);
|
|
768
|
+
|
|
769
|
+
this.oldSources = this.currentAvailableSources;
|
|
770
|
+
this.currentAvailableSources = sources.streamConnections;
|
|
771
|
+
this.hasUsedCurrentSources = false;
|
|
772
|
+
|
|
773
|
+
var toBeConnected = {}; // a map of new nodes to be connected
|
|
774
|
+
var toBeUpdated = {}; // a map of existing node to be updated with new group, streams
|
|
775
|
+
|
|
776
|
+
if (this.oldSources && this.oldSources.length > 0) {
|
|
777
|
+
var streamIds = [];
|
|
778
|
+
var nodeIds = [];
|
|
779
|
+
|
|
780
|
+
_.each(sources.streamConnections, function(source) {
|
|
781
|
+
streamIds.push(Util._streamId(source));
|
|
782
|
+
nodeIds.push(Util._nodeId(source));
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
// remove from corresponding old sources not exist in new sources from data nodes
|
|
786
|
+
var stopStreamsByGroup = {};
|
|
787
|
+
_.each(this.oldSources, _.bind(function(oldSource) {
|
|
788
|
+
|
|
789
|
+
if (!_.contains(streamIds, Util._streamId(oldSource)) || !_.contains(nodeIds, Util._nodeId(oldSource))) {
|
|
790
|
+
|
|
791
|
+
console.log("[Subscriber] ", oldSource, " is no longer in the discovery results. Going to be removed.")
|
|
792
|
+
|
|
793
|
+
_.each(this.nodes, _.bind(function(dataNode) {
|
|
794
|
+
|
|
795
|
+
if (dataNode.hasStream(oldSource.group, oldSource.stream)) {
|
|
796
|
+
console.log("[Subscriber] Remove old stream ", oldSource, " from Data Node ", dataNode.id, " dataNode counter ", dataNode.counter);
|
|
797
|
+
dataNode.removeStream(oldSource.group, oldSource.stream, true);
|
|
798
|
+
if(stopStreamsByGroup[oldSource.group] === undefined){
|
|
799
|
+
stopStreamsByGroup[oldSource.group] = [];
|
|
800
|
+
}
|
|
801
|
+
stopStreamsByGroup[oldSource.group].push(oldSource.stream);
|
|
802
|
+
}
|
|
803
|
+
if(!_.isEmpty(stopStreamsByGroup)) {
|
|
804
|
+
console.log("[DataNode] Emitting consumption error after removing streams")
|
|
805
|
+
this.eventBus.emit(DataNode.Event.CONSUMPTION_ERROR, dataNode, stopStreamsByGroup);
|
|
806
|
+
}
|
|
807
|
+
}, this));
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
}, this));
|
|
811
|
+
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
_.each(sources.streamConnections, _.bind(function(source) { // for each source, we check if we are already consuming it
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* if we are not currently consuming a particular group & stream,
|
|
818
|
+
* or if we are consuming it but the connection for that group & stream is having
|
|
819
|
+
* problem, we will gather the group & stream by source id.
|
|
820
|
+
*/
|
|
821
|
+
if (!this._exist(source) ||
|
|
822
|
+
// if there is error at the node currently consuming the stream
|
|
823
|
+
// and the connection returned by discoverer is different from the one
|
|
824
|
+
// currently use (which is causing error)
|
|
825
|
+
this._error(source) || this._hasUpdatedConnection(source)) {
|
|
826
|
+
|
|
827
|
+
var nodeId = Util._nodeId(source);
|
|
828
|
+
|
|
829
|
+
if (this._hasNode(nodeId)) {
|
|
830
|
+
if (toBeUpdated[nodeId] == undefined) toBeUpdated[nodeId] = [];
|
|
831
|
+
toBeUpdated[nodeId].push(source);
|
|
832
|
+
} else {
|
|
833
|
+
if (toBeConnected[nodeId] == undefined) toBeConnected[nodeId] = [];
|
|
834
|
+
toBeConnected[nodeId].push(source);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
}, this));
|
|
839
|
+
|
|
840
|
+
// any group, and stream which are in these list should be removed from current
|
|
841
|
+
// data node first, before being added to new data node
|
|
842
|
+
this._connectUpdate(toBeConnected);
|
|
843
|
+
this._connectUpdate(toBeUpdated);
|
|
844
|
+
|
|
845
|
+
},
|
|
846
|
+
|
|
847
|
+
_reconnectStreams : function(sources) {
|
|
848
|
+
var toBeConnected = {}; // a map of new nodes to be connected
|
|
849
|
+
var toBeUpdated = {}; // a map of existing node to be updated with new group, streams
|
|
850
|
+
_.each(this.discoverer.getSources().streamConnections, _.bind(function(source) { // for each source, we check if we are already consuming it
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* if we are not currently consuming a particular group & stream,
|
|
854
|
+
* or if we are consuming it but the connection for that group & stream is having
|
|
855
|
+
* problem, we will gather the group & stream by source id.
|
|
856
|
+
*/
|
|
857
|
+
if (!this._exist(source) ||
|
|
858
|
+
// if there is error at the node currently consuming the stream
|
|
859
|
+
// and the connection returned by discoverer is different from the one
|
|
860
|
+
// currently use (which is causing error)
|
|
861
|
+
(this._error(source) && this._hasUpdatedConnection(source))) {
|
|
862
|
+
|
|
863
|
+
console.log("Reconnecting to stopped stream ", source);
|
|
864
|
+
var nodeId = Util._nodeId(source);
|
|
865
|
+
if (this._hasNode(nodeId)) {
|
|
866
|
+
if (toBeUpdated[nodeId] == undefined) toBeUpdated[nodeId] = [];
|
|
867
|
+
toBeUpdated[nodeId].push(source);
|
|
868
|
+
} else {
|
|
869
|
+
if (toBeConnected[nodeId] == undefined) toBeConnected[nodeId] = [];
|
|
870
|
+
toBeConnected[nodeId].push(source);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
}, this));
|
|
875
|
+
this._connectUpdate(toBeConnected);
|
|
876
|
+
this._connectUpdate(toBeUpdated);
|
|
877
|
+
},
|
|
878
|
+
|
|
879
|
+
onNodeReconnectionError : function(dataNode, num) {
|
|
880
|
+
|
|
881
|
+
if (num == this.config().exclusionThreshold) {
|
|
882
|
+
this.discoverer.excluding(dataNode.id);
|
|
883
|
+
this._updateSourcesOnNodeReconnectionError();
|
|
884
|
+
console.log("Excluding node ", dataNode.id, " from discovery");
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
},
|
|
888
|
+
|
|
889
|
+
_updateSourcesOnNodeReconnectionError : function(){
|
|
890
|
+
// Check if currentAvailableSources is not empty and not used.
|
|
891
|
+
if(this.hasUsedCurrentSource == false && _.isEmpty(this.currentAvailableSources) == false){
|
|
892
|
+
this._updateSources(this.currentAvailableSources);
|
|
893
|
+
this.hasUsedCurrentSources = true; // mark currentAvailableSources has used.
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
|
|
897
|
+
onNodeStateChanged : function(dataNode, oldState) {
|
|
898
|
+
|
|
899
|
+
// if the data node is destroyed or disconnected. Need to inform clients about the consumption error
|
|
900
|
+
// also mark those sources provided by that nodes as having error state
|
|
901
|
+
if (dataNode.state == DataNode.State.DESTROYED || dataNode.state == DataNode.State.DISCONNECTED) {
|
|
902
|
+
|
|
903
|
+
for(var property in this.consuming){
|
|
904
|
+
if(this.consuming.hasOwnProperty(property)){
|
|
905
|
+
if(this.consuming[property].node.id == dataNode.id){
|
|
906
|
+
this.consuming[property].state = this.consumptionState.ERROR;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
var streamsByGroup = {};
|
|
912
|
+
|
|
913
|
+
for(var property in dataNode.streams){
|
|
914
|
+
if(dataNode.streams.hasOwnProperty(property)){
|
|
915
|
+
streamsByGroup[property] = [];
|
|
916
|
+
for (streamInfo in dataNode.streams[property])
|
|
917
|
+
streamsByGroup[property].push(streamInfo.stream);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (!_.isEmpty(streamsByGroup)) { // if streamsByGroup doesn't contain any thing, no need to emit event
|
|
922
|
+
console.log("[Subscriber] emitting consumption error due to data node failed.", dataNode.state);
|
|
923
|
+
this.dispatch(new Subscriber.Event.ConsumptionError(dataNode.client, streamsByGroup));
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (oldState === DataNode.State.DISCONNECTED && dataNode.state === DataNode.State.CONNECTED) {
|
|
928
|
+
this.discoverer.removeExclusion(dataNode.id);
|
|
929
|
+
console.log("Node ", dataNode.id, " resume successfully. Remove it from exclusion.");
|
|
930
|
+
} else if (dataNode.state === DataNode.State.DESTROYED) {
|
|
931
|
+
// when it's destroyed, we are not going to reconnect to it automatically, so remove from exclusion
|
|
932
|
+
this.discoverer.removeExclusion(dataNode.id);
|
|
933
|
+
delete this.nodes[dataNode.id];
|
|
934
|
+
console.log("Node ", dataNode.id, " is destroyed and removed from subscriber!");
|
|
935
|
+
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
},
|
|
939
|
+
|
|
940
|
+
onStreamConsumption : function(dataNode, streamsByGroup){
|
|
941
|
+
this.dispatch(new Subscriber.Event.ConsumptionStart(dataNode.client, streamsByGroup));
|
|
942
|
+
},
|
|
943
|
+
|
|
944
|
+
onStreamError : function(dataNode, streamsByGroup){
|
|
945
|
+
this.dispatch(new Subscriber.Event.ConsumptionError(dataNode.client, streamsByGroup));
|
|
946
|
+
},
|
|
947
|
+
|
|
948
|
+
onStreamStop : function(dataNode, streamsByGroup){
|
|
949
|
+
this.dispatch(new Subscriber.Event.ConsumptionStop(dataNode.client, streamsByGroup));
|
|
950
|
+
},
|
|
951
|
+
|
|
952
|
+
_discovererError : function (err) {
|
|
953
|
+
|
|
954
|
+
},
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* This method remove the group & stream which are going to be
|
|
958
|
+
* consumed in NEW DataNode or to be ADDED to other EXISTING
|
|
959
|
+
* DataNodes from the current DataNodes they belong to (because current DataNodes
|
|
960
|
+
* might be having connection error, which marks the respective group & streams
|
|
961
|
+
* as error). Only when there is no one else consuming the stream,
|
|
962
|
+
* the stream can be registered into a new node.
|
|
963
|
+
*
|
|
964
|
+
* @param sources : a map from the nodeId of nodes to be added / updated to the group and stream.
|
|
965
|
+
*/
|
|
966
|
+
_connectUpdate : function(sources) {
|
|
967
|
+
|
|
968
|
+
_.each(sources, _.bind(function(connections, nodeId) {
|
|
969
|
+
|
|
970
|
+
_.each(connections, _.bind(function(streamInfo) {
|
|
971
|
+
var streamId = Util._streamId(streamInfo);
|
|
972
|
+
|
|
973
|
+
if (!this.consuming[streamId]) this.consuming[streamId] = new StreamConsumptionWrapper();
|
|
974
|
+
|
|
975
|
+
var wrapper = this.consuming[streamId];
|
|
976
|
+
|
|
977
|
+
var promise = Promise.resolve(0);
|
|
978
|
+
|
|
979
|
+
// this will automatically destroy the node if there is no stream for it to consume
|
|
980
|
+
if (wrapper !== undefined && wrapper.node != undefined && wrapper.state == this.consumptionState.ERROR)
|
|
981
|
+
promise = wrapper.node.removeStream(streamInfo.group, streamInfo.stream, false); // if consumption state is already error, no need to send deregistration
|
|
982
|
+
|
|
983
|
+
promise.done = function(onFulfilled, onRejected) {
|
|
984
|
+
this.then(onFulfilled, onRejected)
|
|
985
|
+
.catch(function (reason) {
|
|
986
|
+
console.log("Promise Rejected: " + reason + "\n" + reason.stack);
|
|
987
|
+
});
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// this stream is going to be consume soon
|
|
991
|
+
wrapper.state = this.consumptionState.PENDING;
|
|
992
|
+
/**
|
|
993
|
+
* when the promise is done
|
|
994
|
+
* it means that there is no one consuming the group & stream
|
|
995
|
+
* it's okay now that I'll start consuming it
|
|
996
|
+
*/
|
|
997
|
+
if (this.nodes[nodeId]) { // if the node already existed
|
|
998
|
+
promise.done(_.bind(function(result) {
|
|
999
|
+
|
|
1000
|
+
this.nodes[nodeId].consumeStream(streamInfo);
|
|
1001
|
+
wrapper.node = this.nodes[nodeId]; // mark this so that no one else can consume this stream
|
|
1002
|
+
|
|
1003
|
+
}, this));
|
|
1004
|
+
} else { // a new data node has to be created
|
|
1005
|
+
|
|
1006
|
+
this.nodes[nodeId] = new DataNode(nodeId, this.clientId, this.ticket, streamInfo, this.eventBus, this.config());
|
|
1007
|
+
promise.done(_.bind(function(result) {
|
|
1008
|
+
wrapper.node = this.nodes[nodeId]; // mark this so that no one else can consume this stream
|
|
1009
|
+
this.nodes[nodeId].connect();
|
|
1010
|
+
|
|
1011
|
+
}, this));
|
|
1012
|
+
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
}, this));
|
|
1016
|
+
|
|
1017
|
+
}, this));
|
|
1018
|
+
|
|
1019
|
+
},
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Check if a group and stream existed
|
|
1023
|
+
* This means, the subscriber is consuming data for this group and stream from
|
|
1024
|
+
* a source.
|
|
1025
|
+
*/
|
|
1026
|
+
_exist : function(source) {
|
|
1027
|
+
var streamId = Util._streamId(source);
|
|
1028
|
+
|
|
1029
|
+
if (this.consuming[streamId]) return true;
|
|
1030
|
+
|
|
1031
|
+
return false;
|
|
1032
|
+
},
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Check if a consuming group and stream is having any problem.
|
|
1036
|
+
* A consuming group and stream has problem if the DataNode for it is having
|
|
1037
|
+
* connection problem.
|
|
1038
|
+
*/
|
|
1039
|
+
_error : function(source) {
|
|
1040
|
+
var streamId = Util._streamId(source);
|
|
1041
|
+
|
|
1042
|
+
if (this.consuming[streamId] && this.consuming[streamId].state == this.consumptionState.ERROR) return true;
|
|
1043
|
+
|
|
1044
|
+
return false;
|
|
1045
|
+
},
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Check if a source returned by discoverer has newer connection
|
|
1049
|
+
* than the one being used
|
|
1050
|
+
*/
|
|
1051
|
+
_hasUpdatedConnection : function (source) {
|
|
1052
|
+
var streamId = Util._streamId(source);
|
|
1053
|
+
|
|
1054
|
+
if (!this.consuming[streamId] || !this.consuming[streamId].node ||
|
|
1055
|
+
this.consuming[streamId].node.state == DataNode.State.DESTROYED) {
|
|
1056
|
+
console.log("Has updated source");
|
|
1057
|
+
|
|
1058
|
+
if (!this.consuming[streamId])
|
|
1059
|
+
console.log("Because the source is not consuming");
|
|
1060
|
+
else if (!this.consuming[streamId].node)
|
|
1061
|
+
console.log("Because the source is not consumed from a node");
|
|
1062
|
+
else if (this.consuming[streamId].node == DataNode.State.DESTROYED)
|
|
1063
|
+
console.log("Because the source's node does not exist");
|
|
1064
|
+
|
|
1065
|
+
return true; // if this source is not in the consuming list or if it is, but there is no node for it
|
|
1066
|
+
} else if (Util._nodeId(source) != this.consuming[streamId].node.id) {
|
|
1067
|
+
console.log("Has updated source because the new node id is different from previous!");
|
|
1068
|
+
return true; // if the service id of the source is different from service id of the node
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
return false;
|
|
1072
|
+
},
|
|
1073
|
+
|
|
1074
|
+
_hasNode : function(nodeId) {
|
|
1075
|
+
return this.nodes[nodeId] !== undefined;
|
|
1076
|
+
},
|
|
1077
|
+
|
|
1078
|
+
}, IEndPointEventSource);
|
|
1079
|
+
|
|
1080
|
+
module.exports = exports = {
|
|
1081
|
+
Subscriber : Subscriber,
|
|
1082
|
+
SubscriberConfig : SubscriberConfig
|
|
1083
|
+
}
|