vrpc 3.12.0 → 3.12.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vrpc",
3
- "version": "3.12.0",
3
+ "version": "3.12.1",
4
4
  "description": "Variadic Remote Procedure Calls",
5
5
  "main": "index.js",
6
6
  "directories": {
package/vrpc/VrpcAgent.js CHANGED
@@ -153,6 +153,11 @@ class VrpcAgent extends EventEmitter {
153
153
  subscribe: (topics, options) => this._mqttSubscribe(topics, options),
154
154
  log: this._log
155
155
  })
156
+ // counts the connections: an announcement belongs to the connection
157
+ // that prepared it (see _handleConnect)
158
+ this._connectGeneration = 0
159
+ // connected, request topics subscribed and info announced
160
+ this._serving = false
156
161
 
157
162
  // Handle the internal error event in case the user forgot to implement it
158
163
  this.on('error', err => {
@@ -172,8 +177,9 @@ class VrpcAgent extends EventEmitter {
172
177
  * If the connection could not be established because of authorization
173
178
  * failure, the 'error' event will be emitted.
174
179
  *
175
- * @return {Promise} Resolves once connected or explicitly ended, never
176
- * rejects
180
+ * @return {Promise} Resolves once the agent serves - connected, its
181
+ * request topics subscribed and its info announced - or once it was
182
+ * explicitly ended before that, never rejects
177
183
  */
178
184
  async serve () {
179
185
  let username = this._username
@@ -377,7 +383,13 @@ class VrpcAgent extends EventEmitter {
377
383
  )
378
384
  }
379
385
 
380
- _mqttSubscribe (topic, options) {
386
+ /**
387
+ * Subscribes topic(s); `onSettled` is called once the broker has
388
+ * answered (granted, refused or failed alike).
389
+ *
390
+ * @private
391
+ */
392
+ _mqttSubscribe (topic, options, onSettled) {
381
393
  this._client.subscribe(
382
394
  topic,
383
395
  { qos: this._qos, ...options },
@@ -424,6 +436,7 @@ class VrpcAgent extends EventEmitter {
424
436
  this._log.debug(`Already subscribed to topic '${topic}'`)
425
437
  }
426
438
  }
439
+ if (onSettled) onSettled()
427
440
  }
428
441
  )
429
442
  }
@@ -458,12 +471,18 @@ class VrpcAgent extends EventEmitter {
458
471
  return VrpcAdapter._getMetaData(className)
459
472
  }
460
473
 
474
+ /**
475
+ * Resolves once the agent serves (its own 'connect' event: connected,
476
+ * request topics subscribed, info announced), or once it was ended.
477
+ *
478
+ * @private
479
+ */
461
480
  async _ensureConnected () {
462
481
  return new Promise(resolve => {
463
- if (this._client.connected) {
482
+ if (this._serving) {
464
483
  resolve()
465
484
  } else {
466
- this._client.once('connect', resolve)
485
+ this.once('connect', resolve)
467
486
  // Will be triggered if the user called 'agent.end()'
468
487
  this.once('end', resolve)
469
488
  }
@@ -502,36 +521,64 @@ class VrpcAgent extends EventEmitter {
502
521
  return str
503
522
  }
504
523
 
524
+ /**
525
+ * Serve first, announce second. The request topics - the statics and
526
+ * the methods of every instance that exists already - are subscribed
527
+ * before the agent publishes its agent and class info, and the
528
+ * announcement waits until the broker has answered every subscription.
529
+ * A client acts on the announcement at once; a request it publishes
530
+ * before the broker routes the topic is lost (QoS 0) and only times out
531
+ * on the caller's side. That was the fate of the first call into an
532
+ * agent that came online after the client waiting for it. A refused
533
+ * subscription is the retrier's business and delays nothing.
534
+ *
535
+ * @private
536
+ */
505
537
  _handleConnect () {
506
538
  this._log.info('[OK]')
507
539
  // everything is subscribed afresh: pending retries are moot
508
540
  this._subscribeRetrier.cancel()
541
+ const generation = ++this._connectGeneration
542
+ const batches = []
509
543
  try {
510
- const topics = this._generateTopics()
511
- if (topics.length > 0) this._mqttSubscribe(topics)
544
+ const statics = this._generateTopics()
545
+ if (statics.length > 0) batches.push(statics)
512
546
  } catch (err) {
513
547
  this._log.error(
514
548
  err,
515
549
  `Problem during initial topic subscription: ${err.message}`
516
550
  )
517
551
  }
518
- // Publish agent online
519
- this._publishAgentInfoMessage()
520
-
521
- // Publish class information
522
- const classes = this._getClasses()
523
- classes.forEach(className => {
524
- this._publishClassInfoMessage(className)
525
- this._publishClassInfoConciseMessage(className)
526
- })
527
- // Register all pre-existing instances
552
+ // all pre-existing instances
528
553
  for (const [
529
554
  instanceId,
530
555
  { className }
531
556
  ] of VrpcAdapter._instances.entries()) {
532
- this._subscribeToMethodsOfNewInstance(className, instanceId)
557
+ batches.push(this._methodsTopic(className, instanceId))
558
+ }
559
+ let pending = batches.length
560
+ const announce = () => {
561
+ // a connection that ended meanwhile announces nothing: the next
562
+ // one prepares and announces itself
563
+ if (generation !== this._connectGeneration) return
564
+ this._publishAgentInfoMessage()
565
+ for (const className of this._getClasses()) {
566
+ this._publishClassInfoMessage(className)
567
+ this._publishClassInfoConciseMessage(className)
568
+ }
569
+ this._serving = true
570
+ this.emit('connect')
571
+ }
572
+ if (pending === 0) {
573
+ announce()
574
+ return
575
+ }
576
+ for (const topic of batches) {
577
+ this._mqttSubscribe(topic, undefined, () => {
578
+ pending -= 1
579
+ if (pending === 0) announce()
580
+ })
533
581
  }
534
- this.emit('connect')
535
582
  }
536
583
 
537
584
  _publishAgentInfoMessage () {
@@ -785,14 +832,23 @@ class VrpcAgent extends EventEmitter {
785
832
  return true
786
833
  }
787
834
 
835
+ /**
836
+ * The request topics of one instance: every method of it.
837
+ *
838
+ * @private
839
+ */
840
+ _methodsTopic (className, instance) {
841
+ return `${this._baseTopic}/${className}/${instance}/+`
842
+ }
843
+
788
844
  _subscribeToMethodsOfNewInstance (className, instance) {
789
- const topic = `${this._baseTopic}/${className}/${instance}/+`
845
+ const topic = this._methodsTopic(className, instance)
790
846
  this._mqttSubscribe(topic)
791
847
  this._log.debug(`Subscribed to new topic after instantiation: ${topic}`)
792
848
  }
793
849
 
794
850
  _unsubscribeMethodsOfDeletedInstance (className, instance) {
795
- const topic = `${this._baseTopic}/${className}/${instance}/+`
851
+ const topic = this._methodsTopic(className, instance)
796
852
  this._mqttUnsubscribe(topic)
797
853
  this._log.debug(`Unsubscribed from topic after deletion: ${topic}`)
798
854
  }
@@ -807,6 +863,8 @@ class VrpcAgent extends EventEmitter {
807
863
  }
808
864
 
809
865
  _handleClose () {
866
+ // the next connection prepares and announces itself afresh
867
+ this._serving = false
810
868
  this.emit('close')
811
869
  }
812
870
 
@@ -71,6 +71,11 @@ class VrpcAgent extends EventEmitter {
71
71
  // maps clientId to instanceId
72
72
  this._isolatedInstances = new Map()
73
73
  this._sharedInstances = new Map()
74
+ // counts the connections: an announcement belongs to the connection
75
+ // that prepared it (see _handleConnect)
76
+ this._connectGeneration = 0
77
+ // connected, request topics subscribed and info announced
78
+ this._serving = false
74
79
 
75
80
  // Handle the internal error event in case the user forgot to implement it
76
81
  this.on('error', err => {
@@ -90,8 +95,9 @@ class VrpcAgent extends EventEmitter {
90
95
  * If the connection could not be established because of authorization
91
96
  * failure, the 'error' event will be emitted.
92
97
  *
93
- * @return {Promise} Resolves once connected or explicitly ended, never
94
- * rejects
98
+ * @return {Promise} Resolves once the agent serves - connected, its
99
+ * request topics subscribed and its info announced - or once it was
100
+ * explicitly ended before that, never rejects
95
101
  */
96
102
  async serve () {
97
103
  let username = this._username
@@ -226,7 +232,11 @@ class VrpcAgent extends EventEmitter {
226
232
  )
227
233
  }
228
234
 
229
- _mqttSubscribe (topic, options) {
235
+ /**
236
+ * Subscribes topic(s); `onSettled` is called once the broker has
237
+ * answered (granted, refused or failed alike).
238
+ */
239
+ _mqttSubscribe (topic, options, onSettled) {
230
240
  this._client.subscribe(
231
241
  topic,
232
242
  { qos: this._qos, ...options },
@@ -269,6 +279,7 @@ class VrpcAgent extends EventEmitter {
269
279
  this._log.debug(`Already subscribed to topic '${topic}'`)
270
280
  }
271
281
  }
282
+ if (onSettled) onSettled()
272
283
  }
273
284
  )
274
285
  }
@@ -303,12 +314,16 @@ class VrpcAgent extends EventEmitter {
303
314
  return VrpcAdapter._getMetaData(className)
304
315
  }
305
316
 
317
+ /**
318
+ * Resolves once the agent serves (its own 'connect' event: connected,
319
+ * request topics subscribed, info announced), or once it was ended.
320
+ */
306
321
  async _ensureConnected () {
307
322
  return new Promise(resolve => {
308
- if (this._client.connected) {
323
+ if (this._serving) {
309
324
  resolve()
310
325
  } else {
311
- this._client.once('connect', resolve)
326
+ this.once('connect', resolve)
312
327
  // Will be triggered if the user called 'agent.end()'
313
328
  this.once('end', resolve)
314
329
  }
@@ -347,33 +362,56 @@ class VrpcAgent extends EventEmitter {
347
362
  return str
348
363
  }
349
364
 
365
+ /**
366
+ * Serve first, announce second - the twin of VrpcAgent._handleConnect:
367
+ * every request topic (statics, the methods of every existing instance)
368
+ * is subscribed before the agent and class info go out, and they go out
369
+ * once the broker has answered every subscription. A client acts on the
370
+ * announcement at once; a request published before the broker routes
371
+ * the topic is lost (QoS 0) and only times out on the caller's side.
372
+ */
350
373
  _handleConnect () {
351
374
  this._log.info('[OK]')
375
+ const generation = ++this._connectGeneration
376
+ const batches = []
352
377
  try {
353
- const topics = this._generateTopics()
354
- if (topics.length > 0) this._mqttSubscribe(topics)
378
+ const statics = this._generateTopics()
379
+ if (statics.length > 0) batches.push(statics)
355
380
  } catch (err) {
356
381
  this._log.error(
357
382
  err,
358
383
  `Problem during initial topic subscription: ${err.message}`
359
384
  )
360
385
  }
361
- // Publish agent online
362
- this._publishAgentInfoMessage()
363
-
364
- // Publish class information
365
- const classes = this._getClasses()
366
- classes.forEach(className => {
367
- this._publishClassInfoMessage(className)
368
- })
369
- // Register all pre-existing instances
386
+ // all pre-existing instances
370
387
  for (const [
371
388
  instanceId,
372
389
  { className }
373
390
  ] of VrpcAdapter._instances.entries()) {
374
- this._subscribeToMethodsOfNewInstance(className, instanceId)
391
+ batches.push(this._methodsTopic(className, instanceId))
392
+ }
393
+ let pending = batches.length
394
+ const announce = () => {
395
+ // a connection that ended meanwhile announces nothing: the next
396
+ // one prepares and announces itself
397
+ if (generation !== this._connectGeneration) return
398
+ this._publishAgentInfoMessage()
399
+ for (const className of this._getClasses()) {
400
+ this._publishClassInfoMessage(className)
401
+ }
402
+ this._serving = true
403
+ this.emit('connect')
404
+ }
405
+ if (pending === 0) {
406
+ announce()
407
+ return
408
+ }
409
+ for (const topic of batches) {
410
+ this._mqttSubscribe(topic, undefined, () => {
411
+ pending -= 1
412
+ if (pending === 0) announce()
413
+ })
375
414
  }
376
- this.emit('connect')
377
415
  }
378
416
 
379
417
  _publishAgentInfoMessage () {
@@ -592,14 +630,19 @@ class VrpcAgent extends EventEmitter {
592
630
  return true
593
631
  }
594
632
 
633
+ /** The request topics of one instance: every method of it. */
634
+ _methodsTopic (className, instance) {
635
+ return `${this._baseTopic}/${className}/${instance}/+`
636
+ }
637
+
595
638
  _subscribeToMethodsOfNewInstance (className, instance) {
596
- const topic = `${this._baseTopic}/${className}/${instance}/+`
639
+ const topic = this._methodsTopic(className, instance)
597
640
  this._mqttSubscribe(topic)
598
641
  this._log.debug(`Subscribed to new topic after instantiation: ${topic}`)
599
642
  }
600
643
 
601
644
  _unsubscribeMethodsOfDeletedInstance (className, instance) {
602
- const topic = `${this._baseTopic}/${className}/${instance}/+`
645
+ const topic = this._methodsTopic(className, instance)
603
646
  this._mqttUnsubscribe(topic)
604
647
  this._log.debug(`Unsubscribed from topic after deletion: ${topic}`)
605
648
  }
@@ -614,6 +657,8 @@ class VrpcAgent extends EventEmitter {
614
657
  }
615
658
 
616
659
  _handleClose () {
660
+ // the next connection prepares and announces itself afresh
661
+ this._serving = false
617
662
  this.emit('close')
618
663
  }
619
664