tchannel 3.6.14 → 3.6.24

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.
@@ -0,0 +1,209 @@
1
+ // Copyright (c) 2015 Uber Technologies, Inc.
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in
11
+ // all copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ // THE SOFTWARE.
20
+
21
+ 'use strict';
22
+
23
+ var assert = require('assert');
24
+ var stat = require('../stat-tags');
25
+
26
+ var DEFAULT_MAX_SIZE = 1000;
27
+
28
+ module.exports = ObjectPool;
29
+
30
+ function ObjectPool(options) {
31
+ assert(typeof options === 'object', 'expected options object');
32
+
33
+ this.Type = options.Type;
34
+ assert(typeof this.Type === 'function', 'expected options.Type to be constructor function');
35
+ assert(typeof this.Type.prototype.reset === 'function', 'expected options.Type to have reset method');
36
+ assert(typeof this.Type.prototype.clear === 'function', 'expected options.Type to have clear method');
37
+
38
+ this.maxSize = options.maxSize || DEFAULT_MAX_SIZE;
39
+ assert(typeof this.maxSize === 'number', 'expected options.maxSize to be number');
40
+
41
+ this.name = options.name || this.Type.name;
42
+ assert(typeof this.name === 'string', 'expected options.name to be string');
43
+
44
+ this.freeList = [];
45
+ this.outstanding = 0;
46
+
47
+ this.freeListStatTags = new stat.ObjectPoolTags(
48
+ this.name,
49
+ 'free'
50
+ );
51
+
52
+ this.outstandingStatTags = new stat.ObjectPoolTags(
53
+ this.name,
54
+ 'outstanding'
55
+ );
56
+
57
+ // only used in debug mode
58
+ this.outstandingList = [];
59
+ }
60
+
61
+ ObjectPool.channel = null;
62
+ ObjectPool.reportInterval = null;
63
+ ObjectPool.timers = null;
64
+ ObjectPool.pools = [];
65
+ ObjectPool.timer = null;
66
+ ObjectPool.refs = 0;
67
+ ObjectPool.debug = false;
68
+
69
+ ObjectPool.setup = function setup(options) {
70
+ var pool = new ObjectPool(options);
71
+ options.Type.alloc = alloc;
72
+
73
+ options.Type.prototype.free = function freeThisObj() {
74
+ pool.free(this);
75
+ };
76
+
77
+ ObjectPool.pools.push(pool);
78
+
79
+ return pool;
80
+
81
+ function alloc() {
82
+ var obj = pool.get();
83
+ return obj;
84
+ }
85
+ };
86
+
87
+ ObjectPool.bootstrap = function bootstrap(options) {
88
+ if (ObjectPool.refs >= 1) {
89
+ ObjectPool.refs += 1;
90
+ return;
91
+ }
92
+
93
+ ObjectPool.refs += 1;
94
+
95
+ assert(typeof options === 'object', 'expected options object');
96
+
97
+ assert(
98
+ typeof options.channel === 'object' &&
99
+ typeof options.channel.emitFastStat === 'function',
100
+ 'expected options.channel to be TChannel instance'
101
+ );
102
+ ObjectPool.channel = options.channel;
103
+
104
+ assert(
105
+ typeof options.reportInterval === 'number',
106
+ 'expected options.reportInterval to be number'
107
+ );
108
+ ObjectPool.reportInterval = options.reportInterval;
109
+
110
+ assert(
111
+ typeof options.timers === 'object' &&
112
+ typeof options.timers.setTimeout === 'function',
113
+ 'expected options.timers to be timers object'
114
+ );
115
+ ObjectPool.timers = options.timers;
116
+
117
+ if (typeof options.debug === 'boolean') {
118
+ ObjectPool.debug = options.debug;
119
+ }
120
+
121
+ ObjectPool.timer = ObjectPool.timers.setTimeout(
122
+ ObjectPool.reportStats,
123
+ ObjectPool.reportInterval
124
+ );
125
+ };
126
+
127
+ ObjectPool.unref = function unref() {
128
+ ObjectPool.refs = Math.max(0, ObjectPool.refs - 1);
129
+ if (ObjectPool.refs === 0) {
130
+ ObjectPool.timers.clearTimeout(ObjectPool.timer);
131
+ ObjectPool.timer = null;
132
+ }
133
+ };
134
+
135
+ ObjectPool.reportStats = function reportStats() {
136
+ // Iterate over pools, report their current size
137
+
138
+ var i;
139
+ for (i = 0; i < ObjectPool.pools.length; i++) {
140
+ ObjectPool.pools[i].reportStats(ObjectPool.channel);
141
+ }
142
+
143
+ ObjectPool.timer = ObjectPool.timers.setTimeout(
144
+ ObjectPool.reportStats,
145
+ ObjectPool.reportInterval
146
+ );
147
+ };
148
+
149
+ ObjectPool.prototype.reportStats = function reportStats(channel) {
150
+ channel.emitFastStat(
151
+ 'tchannel.object-pool',
152
+ 'gauge',
153
+ this.freeList.length,
154
+ this.freeListStatTags
155
+ );
156
+
157
+ channel.emitFastStat(
158
+ 'tchannel.object-pool',
159
+ 'gauge',
160
+ this.outstanding,
161
+ this.outstandingStatTags
162
+ );
163
+ };
164
+
165
+ ObjectPool.prototype.get = function get() {
166
+ var inst;
167
+ this.outstanding += 1;
168
+ if (this.freeList.length) {
169
+ inst = this.freeList.pop();
170
+ assert(inst._objectPoolIsFreed, 'instance retreived from pool is free');
171
+ inst._objectPoolIsFreed = false;
172
+
173
+ if (ObjectPool.debug) {
174
+ this.outstandingList.push(inst);
175
+ }
176
+
177
+ return inst;
178
+ } else {
179
+ inst = new this.Type();
180
+ inst._objectPoolIsFreed = false;
181
+
182
+ if (ObjectPool.debug) {
183
+ this.outstandingList.push(inst);
184
+ }
185
+
186
+ return inst;
187
+ }
188
+ };
189
+
190
+ ObjectPool.prototype.free = function free(inst) {
191
+ assert(!inst._objectPoolIsFreed, 'object pool double free');
192
+ inst._objectPoolIsFreed = true;
193
+
194
+ inst.clear();
195
+ if (this.outstanding <= this.maxSize) {
196
+ this.freeList.push(inst);
197
+ }
198
+ this.outstanding -= 1;
199
+
200
+ var i;
201
+ if (ObjectPool.debug) {
202
+ for (i = 0; i < this.outstandingList.length; i++) {
203
+ if (this.outstandingList[i] === inst) {
204
+ this.outstandingList.splice(i, 1);
205
+ return;
206
+ }
207
+ }
208
+ }
209
+ };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "tchannel",
3
3
  "description": "network multiplexing and framing protocol for RPC or parser drag racing",
4
4
  "author": "mranney@uber.com",
5
- "version": "3.6.14",
5
+ "version": "3.6.24",
6
6
  "scripts": {
7
7
  "lint": "eslint $(git ls-files | grep '.js$')",
8
8
  "travis": "npm run test",
@@ -10,7 +10,7 @@
10
10
  "benchmark": "echo '!!! DEPRECATED: Better to just run `node benchmarks` directly' >&2; node benchmarks",
11
11
  "hyperbahn-link-test": "./test/link_hyperbahn.sh",
12
12
  "tcurl-link-test": "./test/link_tcurl.sh",
13
- "check-benchmark": "node benchmarks -- -r 10000 -p 100",
13
+ "check-benchmark": "node benchmarks -- -r 10000 -p 750",
14
14
  "take-benchmark": "make -C benchmarks take",
15
15
  "take-relay-benchmark": "make -C benchmarks take_relay",
16
16
  "take-trace-benchmark": "amke -C benchmarks take_trace",
@@ -28,7 +28,7 @@
28
28
  "url": "git@github.com:uber/tchannel-node"
29
29
  },
30
30
  "dependencies": {
31
- "bufrw": "^1.1.0",
31
+ "bufrw": "^1.2.1",
32
32
  "crc": "^3.2.1",
33
33
  "error": "^7.0.1",
34
34
  "farmhash": "1.1.0",
@@ -43,7 +43,7 @@
43
43
  "safe-json-parse": "^4.0.0",
44
44
  "sse4_crc32": "4.1.1",
45
45
  "tape-cluster": "2.1.0",
46
- "thriftrw": "^3.1.0",
46
+ "thriftrw": "^3.4.3",
47
47
  "xorshift": "^0.2.0",
48
48
  "xtend": "^4.0.0"
49
49
  },
package/peer.js CHANGED
@@ -36,8 +36,11 @@ var NoPreference = require('./peer_score_strategies.js').NoPreference;
36
36
  var PreferIncoming = require('./peer_score_strategies.js').PreferIncoming;
37
37
  var Range = require('./range');
38
38
  var PeerDrain = require('./drain.js').PeerDrain;
39
+ var ObjectPool = require('./lib/object_pool');
39
40
 
40
41
  var DEFAULT_REPORT_INTERVAL = 1000;
42
+ var INITIAL_CONN_ATTEMPT_DELAY = 5000;
43
+ var CONN_ATTEMPT_DELAY_MULTIPLER = 2;
41
44
 
42
45
  /*eslint max-statements: [2, 40]*/
43
46
  function TChannelPeer(channel, hostPort, options) {
@@ -49,6 +52,8 @@ function TChannelPeer(channel, hostPort, options) {
49
52
  this.stateChangedEvent = this.defineEvent('stateChanged');
50
53
  this.allocConnectionEvent = this.defineEvent('allocConnection');
51
54
  this.removeConnectionEvent = this.defineEvent('removeConnection');
55
+ this.deltaOutConnectionEvent = this.defineEvent('deltaOutConnection');
56
+
52
57
  this.channel = channel;
53
58
  this.logger = this.channel.logger;
54
59
  this.timers = this.channel.timers;
@@ -65,6 +70,13 @@ function TChannelPeer(channel, hostPort, options) {
65
70
  this.boundOnPendingChange = onPendingChange;
66
71
  this.scoreRange = null;
67
72
 
73
+ // Timestamp when next conn attempt is allowed
74
+ this.nextConnAttemptTime = 0;
75
+ // How long to delay conn attempt by on failure (ms)
76
+ this.nextConnAttemptDelay = 0;
77
+
78
+ this.waitForIdentifiedListeners = [];
79
+
68
80
  this.reportInterval = options.reportInterval || DEFAULT_REPORT_INTERVAL;
69
81
  if (this.reportInterval > 0 && this.channel.emitConnectionMetrics) {
70
82
  this.reportTimer = this.timers.setTimeout(
@@ -405,6 +417,47 @@ TChannelPeer.prototype.connectTo = function connectTo() {
405
417
  return self.connect(true);
406
418
  };
407
419
 
420
+ TChannelPeer.prototype.tryConnect = function tryConnect() {
421
+ var self = this;
422
+
423
+ var connectTime = Date.now();
424
+ if (connectTime < self.nextConnAttemptTime) {
425
+ return;
426
+ }
427
+
428
+ var conn = this.getOutConnection();
429
+ if (!conn || conn.direction !== 'out') {
430
+ conn = this.connectTo();
431
+ }
432
+
433
+ this.waitForIdentified(conn, onIdentified);
434
+
435
+ function onIdentified(err) {
436
+ if (!err) {
437
+ self.nextConnAttemptDelay = 0;
438
+ self.nextConnAttemptTime = 0;
439
+ return;
440
+ }
441
+
442
+ if (self.nextConnAttemptDelay === 0) {
443
+ self.nextConnAttemptDelay = INITIAL_CONN_ATTEMPT_DELAY;
444
+ } else {
445
+ self.nextConnAttemptDelay *= CONN_ATTEMPT_DELAY_MULTIPLER;
446
+ // Add some amount of fuzz, +-100ms
447
+ self.nextConnAttemptDelay += Math.floor(100 * (Math.random() - 0.5));
448
+ }
449
+
450
+ var afterConnect = Date.now();
451
+ if (self.nextConnAttemptTime < afterConnect) {
452
+ // When in the past set next attempt to now + delay
453
+ self.nextConnAttemptTime = afterConnect + self.nextConnAttemptDelay;
454
+ } else {
455
+ // When in the future; go further in the future
456
+ self.nextConnAttemptTime += self.nextConnAttemptDelay;
457
+ }
458
+ }
459
+ };
460
+
408
461
  TChannelPeer.prototype.waitForIdentified =
409
462
  function waitForIdentified(conn, callback) {
410
463
  var self = this;
@@ -419,13 +472,27 @@ function waitForIdentified(conn, callback) {
419
472
  } else if (conn.remoteName) {
420
473
  callback(null);
421
474
  } else {
422
- self._waitForIdentified(conn, callback);
475
+ return self._waitForIdentified(conn, callback);
423
476
  }
477
+
478
+ return -1;
424
479
  };
425
480
 
426
481
  TChannelPeer.prototype._waitForIdentified =
427
482
  function _waitForIdentified(conn, callback) {
428
483
  var self = this;
484
+ var called = false;
485
+
486
+ // Setup an ident descriptor so we can stop waiting for identified later
487
+ var slot = self.getIdentDescriptorSlot();
488
+ var descriptor = WaitForIdentifiedDescriptor.alloc();
489
+ descriptor.reset(
490
+ onConnectionClose,
491
+ onConnectionError,
492
+ onIdentified,
493
+ conn
494
+ );
495
+ self.waitForIdentifiedListeners[slot] = descriptor;
429
496
 
430
497
  self.pendingIdentified++;
431
498
  conn.errorEvent.on(onConnectionError);
@@ -433,6 +500,8 @@ function _waitForIdentified(conn, callback) {
433
500
  conn.identifiedEvent.on(onIdentified);
434
501
  self.invalidateScore('waitForIdentified');
435
502
 
503
+ return slot;
504
+
436
505
  function onConnectionError(err) {
437
506
  finish(err);
438
507
  }
@@ -446,15 +515,51 @@ function _waitForIdentified(conn, callback) {
446
515
  }
447
516
 
448
517
  function finish(err) {
449
- self.pendingIdentified = 0;
450
- conn.errorEvent.removeListener(onConnectionError);
451
- conn.closeEvent.removeListener(onConnectionClose);
452
- conn.identifiedEvent.removeListener(onIdentified);
453
- self.invalidateScore('waitForIdentified > finish');
518
+ // Multiple events can trigger which causes double callback hilarity.
519
+ if (called) {
520
+ return;
521
+ }
522
+ called = true;
523
+
524
+ self.stopWaitingForIdentified(slot);
454
525
  callback(err);
455
526
  }
456
527
  };
457
528
 
529
+ TChannelPeer.prototype.stopWaitingForIdentified =
530
+ function stopWaitingForIdentified(slot) {
531
+ assert(typeof slot === 'number', 'stopWaitingForIdentified arg1 should be number');
532
+
533
+ if (slot === -1) {
534
+ // when connection was already identified, `waitForIdentified` will
535
+ // return -1
536
+ return;
537
+ }
538
+
539
+ var descriptor = this.waitForIdentifiedListeners[slot];
540
+ this.waitForIdentifiedListeners[slot] = null;
541
+ var conn = descriptor.conn;
542
+
543
+ conn.errorEvent.removeListener(descriptor.error);
544
+ conn.closeEvent.removeListener(descriptor.close);
545
+ conn.identifiedEvent.removeListener(descriptor.ident);
546
+ this.pendingIdentified = 0;
547
+ this.invalidateScore('waitForIdentified > finish');
548
+
549
+ descriptor.free();
550
+ };
551
+
552
+ TChannelPeer.prototype.getIdentDescriptorSlot =
553
+ function getIdentDescriptorSlot() {
554
+ var i;
555
+ for (i = 0; i < this.waitForIdentifiedListeners.length; i++) {
556
+ if (this.waitForIdentifiedListeners[i] === null) {
557
+ return i;
558
+ }
559
+ }
560
+ return this.waitForIdentifiedListeners.length;
561
+ };
562
+
458
563
  TChannelPeer.prototype.request = function peerRequest(options) {
459
564
  var self = this;
460
565
  options.timeout = options.timeout || Request.defaultTimeout;
@@ -467,9 +572,11 @@ TChannelPeer.prototype.addConnection = function addConnection(conn) {
467
572
  // TODO: second approx support pruning
468
573
  if (conn.direction === 'out') {
469
574
  self.connections.push(conn);
575
+ self.deltaOutConnectionEvent.emit(self, 1);
470
576
  } else {
471
577
  self.connections.unshift(conn);
472
578
  }
579
+
473
580
  conn.errorEvent.on(self.boundOnConnectionError);
474
581
  conn.closeEvent.on(self.boundOnConnectionClose);
475
582
  conn.ops.pendingChangeEvent.on(self.boundOnPendingChange);
@@ -551,15 +658,20 @@ TChannelPeer.prototype.removeConnection = function removeConnection(conn) {
551
658
  var self = this;
552
659
 
553
660
  var ret = null;
661
+ var isRemoved = false;
554
662
 
555
663
  var index = self.connections ? self.connections.indexOf(conn) : -1;
556
664
  if (index !== -1) {
557
665
  ret = self.connections.splice(index, 1)[0];
666
+ isRemoved = conn.direction === 'out';
558
667
  }
559
668
 
560
669
  self._maybeInvalidateScore('removeConnection');
561
670
 
562
671
  self.removeConnectionEvent.emit(self, conn);
672
+ if (isRemoved) {
673
+ self.deltaOutConnectionEvent.emit(self, -1);
674
+ }
563
675
  return ret;
564
676
  };
565
677
 
@@ -677,3 +789,28 @@ TChannelPeer.prototype.getScore = function getScore() {
677
789
  };
678
790
 
679
791
  module.exports = TChannelPeer;
792
+
793
+ function WaitForIdentifiedDescriptor(close, error, ident, conn) {
794
+ this.close = null;
795
+ this.error = null;
796
+ this.ident = null;
797
+ this.conn = null;
798
+ }
799
+
800
+ WaitForIdentifiedDescriptor.prototype.reset =
801
+ function reset(close, error, ident, conn) {
802
+ this.close = close;
803
+ this.error = error;
804
+ this.ident = ident;
805
+ this.conn = conn;
806
+ };
807
+
808
+ WaitForIdentifiedDescriptor.prototype.clear =
809
+ function clear() {
810
+ this.close = null;
811
+ this.error = null;
812
+ this.ident = null;
813
+ this.conn = null;
814
+ };
815
+
816
+ ObjectPool.setup({Type: WaitForIdentifiedDescriptor, maxSize: 100});
package/peer_heap.js CHANGED
@@ -30,8 +30,10 @@ module.exports = PeerHeap;
30
30
  // peer list.
31
31
  var dfsStack = [0, 1, 2];
32
32
 
33
- function PeerHeap(random) {
33
+ function PeerHeap(peers, random) {
34
34
  this.array = [];
35
+ this.peers = peers || null;
36
+ this.hasMinConnections = peers ? peers.hasMinConnections : false;
35
37
 
36
38
  this.random = random || Math.random;
37
39
  assert(typeof this.random === 'function', 'PeerHeap expected random fn');
@@ -44,25 +46,6 @@ function PeerHeap(random) {
44
46
  this.maxRangeStart = 0;
45
47
  }
46
48
 
47
- PeerHeap.prototype.choose1 = function choose1(threshold, filter) {
48
- var self = this;
49
- if (self.array[0].range.lo >= threshold && (!filter || filter(self.array[0].peer))) {
50
- return self.array[0].peer;
51
- }
52
- };
53
-
54
- PeerHeap.prototype.choose2 = function choose2(threshold, filter) {
55
- var self = this;
56
- var prob1 = self.array[0].peer.getScore();
57
- var prob2 = self.array[1].peer.getScore();
58
-
59
- if (prob1 > threshold && prob1 >= prob2 && (!filter || filter(self.array[0].peer))) {
60
- return self.array[0].peer;
61
- } else if (prob2 > threshold && (!filter || filter(self.array[1].peer))) {
62
- return self.array[1].peer;
63
- }
64
- };
65
-
66
49
  /*eslint-disable complexity */
67
50
  /*eslint-disable max-statements */
68
51
  PeerHeap.prototype.choose = function choose(threshold, filter) {
@@ -72,26 +55,36 @@ PeerHeap.prototype.choose = function choose(threshold, filter) {
72
55
  return null;
73
56
  }
74
57
 
75
- if (self.array.length === 1) {
76
- return self.choose1(threshold, filter);
77
- }
78
-
79
- if (self.array.length === 2) {
80
- return self.choose2(threshold, filter);
81
- }
82
-
58
+ var isSecondary = false;
83
59
  var chosenPeer = null;
60
+ var secondaryPeer = null;
84
61
  var highestProbability = 0;
62
+ var secondaryProbability = 0;
85
63
  var firstScore = self.array[0].peer.getScore();
86
64
 
65
+ var notEnoughPeers = false;
66
+ if (self.hasMinConnections) {
67
+ notEnoughPeers = self.peers.currentConnectedPeers < self.peers.minConnections;
68
+ }
69
+
87
70
  // Pointers into dfsStack
88
71
  var stackBegin = 0;
89
72
  var stackEnd = 0;
90
73
 
91
74
  if (firstScore > threshold && (!filter || filter(self.array[0].peer))) {
92
75
  // Don't check first peer if it looks good, check its children though
93
- chosenPeer = self.array[0].peer;
94
- highestProbability = firstScore;
76
+
77
+ var firstPeer = self.array[0].peer;
78
+ isSecondary = notEnoughPeers && firstPeer.isConnected('out');
79
+
80
+ if (isSecondary) {
81
+ secondaryPeer = firstPeer;
82
+ secondaryProbability = firstScore;
83
+ } else {
84
+ chosenPeer = firstPeer;
85
+ highestProbability = firstScore;
86
+ }
87
+
95
88
  // The array is seeded with 0, 1, 2 so we just have to advance the
96
89
  // stack pointers
97
90
  stackBegin = 1;
@@ -104,13 +97,20 @@ PeerHeap.prototype.choose = function choose(threshold, filter) {
104
97
 
105
98
  var el = self.array[i];
106
99
 
107
- if (self.rangehis[i] <= self.maxRangeStart) {
100
+ if (!el || (
101
+ self.rangehis[i] <= self.maxRangeStart &&
102
+ (!filter && !notEnoughPeers)
103
+ )) {
108
104
  // This range ends before the range with the largest start begins,
109
105
  // so it can't possibly be chosen over any of the ranges we've
110
106
  // seen. All ranges below this one have a smaller end, so this
111
107
  // range and any below it can't be chosen.
112
108
  continue;
113
- } else if (!filter || filter(el.peer)) {
109
+ }
110
+
111
+ if (!filter || filter(el.peer)) {
112
+ isSecondary = notEnoughPeers && el.peer.isConnected('out');
113
+
114
114
  // INLINE of TChannelPeer#getScore
115
115
  var lo = self.rangelos[i];
116
116
  var hi = self.rangehis[i];
@@ -120,9 +120,18 @@ PeerHeap.prototype.choose = function choose(threshold, filter) {
120
120
  }
121
121
  var probability = lo + ((hi - lo) * rand);
122
122
 
123
- if ((probability > highestProbability) && (probability > threshold)) {
124
- highestProbability = probability;
125
- chosenPeer = el.peer;
123
+ var isBestChoice = !isSecondary ?
124
+ (probability > highestProbability) :
125
+ (probability > secondaryProbability);
126
+
127
+ if ((probability > threshold) && isBestChoice) {
128
+ if (isSecondary) {
129
+ secondaryProbability = probability;
130
+ secondaryPeer = el.peer;
131
+ } else {
132
+ highestProbability = probability;
133
+ chosenPeer = el.peer;
134
+ }
126
135
  }
127
136
  }
128
137
 
@@ -138,11 +147,18 @@ PeerHeap.prototype.choose = function choose(threshold, filter) {
138
147
  }
139
148
  }
140
149
 
141
- return chosenPeer;
150
+ if (secondaryProbability > highestProbability && chosenPeer) {
151
+ chosenPeer.tryConnect(noop);
152
+ return secondaryPeer;
153
+ }
154
+
155
+ return chosenPeer || secondaryPeer;
142
156
  };
143
157
  /*eslint-enable complexity */
144
158
  /*eslint-enable max-statements */
145
159
 
160
+ function noop() {}
161
+
146
162
  PeerHeap.prototype.clear = function clear() {
147
163
  var self = this;
148
164
 
package/relay_handler.js CHANGED
@@ -46,7 +46,9 @@ RelayHandler.prototype.handleLazily = function handleLazily(conn, reqFrame) {
46
46
  return false;
47
47
  }
48
48
 
49
- var rereq = new LazyRelayInReq(conn, reqFrame);
49
+ var rereq = LazyRelayInReq.alloc();
50
+ rereq.reset(conn, reqFrame);
51
+
50
52
  var err = rereq.initRead();
51
53
  if (err) {
52
54
  rereq.onReadError(err);
@@ -57,6 +59,7 @@ RelayHandler.prototype.handleLazily = function handleLazily(conn, reqFrame) {
57
59
  if (!rereq.peer) {
58
60
  rereq.sendErrorFrame('Declined', 'no peer available for request');
59
61
  self.logger.info('no relay peer available', rereq.extendLogInfo({}));
62
+ rereq.free();
60
63
  return true;
61
64
  }
62
65
 
@@ -22,6 +22,9 @@
22
22
 
23
23
  var errors = require('./errors');
24
24
  var assert = require('assert');
25
+ var ReadResult = require('bufrw').ReadResult;
26
+
27
+ var readRes = new ReadResult();
25
28
 
26
29
  function TChannelServiceNameHandler(options) {
27
30
  if (!(this instanceof TChannelServiceNameHandler)) {
@@ -45,7 +48,7 @@ TChannelServiceNameHandler.prototype.type = 'tchannel.service-name-handler';
45
48
  TChannelServiceNameHandler.prototype.handleLazily = function handleLazily(conn, reqFrame) {
46
49
  var self = this;
47
50
 
48
- var res = reqFrame.bodyRW.lazy.readService(reqFrame);
51
+ var res = reqFrame.bodyRW.lazy.poolReadService(readRes, reqFrame);
49
52
  if (res.err) {
50
53
  // TODO: stat?
51
54
  self.channel.logger.warn('failed to lazy read frame serviceName', conn.extendLogInfo({