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.
- package/as/http.js +2 -2
- package/benchmarks/Makefile +5 -1
- package/benchmarks/index.js +1 -0
- package/benchmarks/multi_bench.js +15 -1
- package/channel.js +18 -5
- package/hyperbahn-client.js +3 -1
- package/lazy_relay.js +167 -24
- package/lib/object_pool.js +209 -0
- package/package.json +4 -4
- package/peer.js +143 -6
- package/peer_heap.js +52 -36
- package/relay_handler.js +4 -1
- package/service-name-handler.js +4 -1
- package/stat-tags.js +24 -1
- package/sub_peers.js +79 -9
- package/test/index.js +2 -0
- package/test/lazy_conn_handler.js +6 -2
- package/test/lazy_handler.js +6 -2
- package/test/lib/alloc-cluster.js +27 -1
- package/test/lib/batch-client.js +17 -2
- package/test/object_pool.js +119 -0
- package/test/peer-to-peer-load-balance.js +459 -0
- package/test/relay_lazy.js +2 -1
- package/test/v2/call.js +1 -0
- package/test/v2/lazy_frame.js +17 -12
- package/v2/args.js +28 -29
- package/v2/call.js +108 -68
- package/v2/checksum.js +2 -2
- package/v2/cont.js +19 -18
- package/v2/error_response.js +6 -8
- package/v2/frame.js +16 -14
- package/v2/handler.js +4 -1
- package/v2/header.js +30 -29
- package/v2/init.js +10 -12
- package/v2/tracing.js +21 -20
|
@@ -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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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 (
|
|
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
|
-
}
|
|
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
|
-
|
|
124
|
-
highestProbability
|
|
125
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
package/service-name-handler.js
CHANGED
|
@@ -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.
|
|
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({
|