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/stat-tags.js CHANGED
@@ -58,7 +58,8 @@ module.exports = {
58
58
  ConnectionsErrorsTags: ConnectionsErrorsTags,
59
59
  ConnectionsClosedTags: ConnectionsClosedTags,
60
60
  RelayLatencyTags: RelayLatencyTags,
61
- HTTPHanlderBuildLatencyTags: HTTPHanlderBuildLatencyTags
61
+ HTTPHanlderBuildLatencyTags: HTTPHanlderBuildLatencyTags,
62
+ ObjectPoolTags: ObjectPoolTags
62
63
  };
63
64
 
64
65
  function InboundCallsRecvdTags(cn, serviceName, endpoint) {
@@ -663,3 +664,25 @@ HTTPHanlderBuildLatencyTags.prototype.toStatKey = function toStatKey(prefix) {
663
664
  clean(this.targetService, 'no-target-service') + '.' +
664
665
  (this.streamed ? 'streamed' : 'unstreamed');
665
666
  };
667
+
668
+ function ObjectPoolTags(poolName, statType) {
669
+ this.app = '';
670
+ this.host = '';
671
+ this.cluster = '';
672
+ this.version = '';
673
+
674
+ this.poolName = poolName;
675
+ this.statType = statType;
676
+
677
+ this._cachedPrefix = '';
678
+ this._key = '';
679
+ }
680
+
681
+ ObjectPoolTags.prototype.toStatKey = function toStatKey(prefix) {
682
+ // prefix should never change but we store it in _cachedPrefix just in case
683
+ if (!this._key || prefix !== this._cachedPrefix) {
684
+ this._key = prefix + '.' + this.poolName + '.' + this.statType;
685
+ this._cachedPrefix = prefix;
686
+ }
687
+ return this._key;
688
+ };
package/sub_peers.js CHANGED
@@ -27,13 +27,37 @@ var PeerHeap = require('./peer_heap.js');
27
27
 
28
28
  function TChannelSubPeers(channel, options) {
29
29
  TChannelPeersBase.call(this, channel, options);
30
+
31
+ var self = this;
30
32
  this.peerScoreThreshold = this.options.peerScoreThreshold || 0;
31
- this._heap = new PeerHeap(channel.random);
32
33
  this.choosePeerWithHeap = channel.choosePeerWithHeap;
34
+
35
+ this.hasMinConnections = typeof options.minConnections === 'number';
36
+ this.minConnections = options.minConnections;
37
+
38
+ this.currentConnectedPeers = 0;
39
+ this._heap = new PeerHeap(this, channel.random);
40
+
41
+ this.boundOnOutConnectionDelta = boundOnOutConnectionDelta;
42
+
43
+ function boundOnOutConnectionDelta(delta, peer) {
44
+ self.onOutConnectionDelta(peer, delta);
45
+ }
33
46
  }
34
47
 
35
48
  inherits(TChannelSubPeers, TChannelPeersBase);
36
49
 
50
+ TChannelSubPeers.prototype.onOutConnectionDelta =
51
+ function onOutConnectionDelta(peer, delta) {
52
+ var connCount = peer.countConnections('out');
53
+
54
+ if (delta === 1 && connCount === 1) {
55
+ this.currentConnectedPeers++;
56
+ } else if (delta === -1 && connCount === 0) {
57
+ this.currentConnectedPeers--;
58
+ }
59
+ };
60
+
37
61
  TChannelSubPeers.prototype.close = function close(callback) {
38
62
  var self = this;
39
63
 
@@ -55,6 +79,12 @@ TChannelSubPeers.prototype.add = function add(hostPort, options) {
55
79
  peer = topChannel.peers.add(hostPort, options);
56
80
  peer.setPreferConnectionDirection(self.preferConnectionDirection);
57
81
 
82
+ if (peer.countConnections('out') > 0) {
83
+ this.currentConnectedPeers++;
84
+ }
85
+
86
+ peer.deltaOutConnectionEvent.on(self.boundOnOutConnectionDelta);
87
+
58
88
  self._map[hostPort] = peer;
59
89
  self._keys.push(hostPort);
60
90
 
@@ -80,6 +110,13 @@ TChannelSubPeers.prototype._delete = function _del(peer) {
80
110
  return;
81
111
  }
82
112
 
113
+ if (peer.countConnections('out') > 0) {
114
+ this.currentConnectedPeers--;
115
+ }
116
+
117
+ peer.deltaOutConnectionEvent
118
+ .removeListener(self.boundOnOutConnectionDelta);
119
+
83
120
  delete self._map[peer.hostPort];
84
121
  popout(self._keys, index);
85
122
 
@@ -109,6 +146,7 @@ TChannelSubPeers.prototype.choosePeer = function choosePeer(req) {
109
146
  return self.chooseLinearPeer(req);
110
147
  };
111
148
 
149
+ /*eslint max-statements: [2, 40]*/
112
150
  TChannelSubPeers.prototype.chooseLinearPeer = function chooseLinearPeer(req) {
113
151
  /* eslint complexity: [2, 15]*/
114
152
  var self = this;
@@ -121,11 +159,22 @@ TChannelSubPeers.prototype.chooseLinearPeer = function chooseLinearPeer(req) {
121
159
  var threshold = self.peerScoreThreshold;
122
160
 
123
161
  var selectedPeer = null;
162
+ var secondaryPeer = null;
124
163
  var selectedScore = 0;
164
+ var secondaryScore = 0;
165
+
166
+ var notEnoughPeers = false;
167
+ if (this.hasMinConnections) {
168
+ notEnoughPeers = this.currentConnectedPeers < this.minConnections;
169
+ }
170
+
125
171
  for (var i = 0; i < hosts.length; i++) {
126
172
  var hostPort = hosts[i];
127
173
  var peer = self._map[hostPort];
128
- if (!req || !req.triedRemoteAddrs || !req.triedRemoteAddrs[hostPort]) {
174
+
175
+ var shouldSkip = req && req.triedRemoteAddrs && req.triedRemoteAddrs[hostPort];
176
+ if (!shouldSkip) {
177
+ var isSecondary = notEnoughPeers && peer.isConnected('out');
129
178
  var score = peer.getScore(req);
130
179
 
131
180
  if (self.channel.topChannel.peerScoredEvent) {
@@ -136,11 +185,24 @@ TChannelSubPeers.prototype.chooseLinearPeer = function chooseLinearPeer(req) {
136
185
  });
137
186
  }
138
187
 
139
- var want = score > threshold &&
140
- (selectedPeer === null || score > selectedScore);
188
+ var want;
189
+ if (isSecondary) {
190
+ want = score > threshold && (
191
+ secondaryPeer === null || score > secondaryScore
192
+ );
193
+ } else {
194
+ want = score > threshold &&
195
+ (selectedPeer === null || score > selectedScore);
196
+ }
197
+
141
198
  if (want) {
142
- selectedPeer = peer;
143
- selectedScore = score;
199
+ if (isSecondary) {
200
+ secondaryPeer = peer;
201
+ secondaryScore = score;
202
+ } else {
203
+ selectedPeer = peer;
204
+ selectedScore = score;
205
+ }
144
206
  }
145
207
  }
146
208
  }
@@ -152,14 +214,21 @@ TChannelSubPeers.prototype.chooseLinearPeer = function chooseLinearPeer(req) {
152
214
  });
153
215
  }
154
216
 
155
- return selectedPeer;
217
+ if (secondaryScore > selectedScore && selectedPeer) {
218
+ selectedPeer.tryConnect(noop);
219
+ return secondaryPeer;
220
+ }
221
+
222
+ return selectedPeer || secondaryPeer;
156
223
  };
157
224
 
225
+ function noop() {}
226
+
158
227
  TChannelSubPeers.prototype.chooseHeapPeer = function chooseHeapPeer(req) {
159
228
  var self = this;
160
229
 
161
230
  var peer;
162
- if (req && req.triedRemoteAddrs) {
231
+ if ((req && req.triedRemoteAddrs)) {
163
232
  peer = self._choosePeerSkipTried(req);
164
233
  } else {
165
234
  peer = self._heap.choose(self.peerScoreThreshold);
@@ -182,7 +251,8 @@ function _choosePeerSkipTried(req) {
182
251
  return self._heap.choose(self.peerScoreThreshold, filterTriedPeers);
183
252
 
184
253
  function filterTriedPeers(peer) {
185
- return !req.triedRemoteAddrs[peer.hostPort];
254
+ var shouldSkip = req.triedRemoteAddrs[peer.hostPort];
255
+ return !shouldSkip;
186
256
  }
187
257
  };
188
258
 
package/test/index.js CHANGED
@@ -76,6 +76,8 @@ require('./peer_drain.js');
76
76
  require('./relay-clamps-the-ttl.js');
77
77
  require('./relay-under-timeouts.js');
78
78
  require('./relay-kills-socket-after-timeouts.js');
79
+ require('./object_pool.js');
80
+ require('./peer-to-peer-load-balance.js');
79
81
 
80
82
  require('./trace/basic_server.js');
81
83
  require('./trace/server_2_requests.js');
@@ -20,6 +20,8 @@
20
20
 
21
21
  'use strict';
22
22
 
23
+ var ReadResult = require('bufrw').ReadResult;
24
+
23
25
  var v2 = require('../v2/index.js');
24
26
  var allocCluster = require('./lib/alloc-cluster.js');
25
27
 
@@ -45,16 +47,18 @@ allocCluster.test('connection.handler: lazy call handling', 2, function t(cluste
45
47
  conn.handler.handleCallLazily = handleCallLazily;
46
48
  }
47
49
 
50
+ var readRes = new ReadResult();
51
+
48
52
  function handleCallLazily(frame) {
49
53
  // this is conn.handler
50
54
 
51
- var res = frame.bodyRW.lazy.readService(frame);
55
+ var res = frame.bodyRW.lazy.poolReadService(readRes, frame);
52
56
  if (res.err) {
53
57
  throw res.err;
54
58
  }
55
59
  assert.equal(res.value, 'bob', 'expected called service name');
56
60
 
57
- res = frame.bodyRW.lazy.readArg1(frame);
61
+ res = frame.bodyRW.lazy.poolReadArg1(readRes, frame);
58
62
  if (res.err) {
59
63
  throw res.err;
60
64
  }
@@ -23,6 +23,10 @@
23
23
  var v2 = require('../v2/index.js');
24
24
  var allocCluster = require('./lib/alloc-cluster.js');
25
25
 
26
+ var ReadResult = require('bufrw').ReadResult;
27
+
28
+ var readRes = new ReadResult();
29
+
26
30
  allocCluster.test('channel.handler: lazy call handling', 2, function t(cluster, assert) {
27
31
  var one = cluster.channels[0];
28
32
  var two = cluster.channels[1];
@@ -42,13 +46,13 @@ allocCluster.test('channel.handler: lazy call handling', 2, function t(cluster,
42
46
  server.handler.handleLazily = handleCallLazily;
43
47
 
44
48
  function handleCallLazily(conn, frame) {
45
- var res = frame.bodyRW.lazy.readService(frame);
49
+ var res = frame.bodyRW.lazy.poolReadService(readRes, frame);
46
50
  if (res.err) {
47
51
  throw res.err;
48
52
  }
49
53
  assert.equal(res.value, 'bob', 'expected called service name');
50
54
 
51
- res = frame.bodyRW.lazy.readArg1(frame);
55
+ res = frame.bodyRW.lazy.poolReadArg1(readRes, frame);
52
56
  if (res.err) {
53
57
  throw res.err;
54
58
  }
@@ -29,6 +29,7 @@ var debugLogtron = require('debug-logtron');
29
29
 
30
30
  var TChannel = require('../../channel.js');
31
31
  var CollapsedAssert = require('./collapsed-assert.js');
32
+ var ObjectPool = require('../../lib/object_pool');
32
33
 
33
34
  var loadConfig = require('./load_config.js');
34
35
 
@@ -77,7 +78,8 @@ function allocCluster(opts) {
77
78
  var channelOptions = extend({
78
79
  logger: logger,
79
80
  timeoutFuzz: 0,
80
- traceSample: 1
81
+ traceSample: 1,
82
+ objectPoolDebug: true
81
83
  }, defaultChannelOptions, opts.channelOptions || opts);
82
84
 
83
85
  for (var i = 0; i < opts.numPeers; i++) {
@@ -217,6 +219,8 @@ function clusterTester(opts, t) {
217
219
 
218
220
  cluster.assertEmptyState(collapsedAssert);
219
221
  collapsedAssert.report(assert, 'cluster has an empty state');
222
+
223
+ checkObjectPools(assert);
220
224
  }
221
225
  cluster.destroy();
222
226
  });
@@ -225,6 +229,24 @@ function clusterTester(opts, t) {
225
229
  }
226
230
  }
227
231
 
232
+ function checkObjectPools(assert) {
233
+ var i;
234
+ var pool;
235
+ var length;
236
+ for (i = 0; i < ObjectPool.pools.length; i++) {
237
+ pool = ObjectPool.pools[i];
238
+ length = pool.outstandingList.length;
239
+ assert.ok(
240
+ length === 0,
241
+ 'expected object pool ' + pool.name + ' to have no outstanding ' +
242
+ 'instances, found ' + length + ' instances'
243
+ );
244
+ if (length) {
245
+ assert.comment(util.inspect(pool.outstandingList));
246
+ }
247
+ }
248
+ }
249
+
228
250
  allocCluster.test = function testCluster(desc, opts, t) {
229
251
  if (opts === undefined) {
230
252
  return test(desc);
@@ -237,6 +259,10 @@ allocCluster.test.only = function testClusterOnly(desc, opts, t) {
237
259
  test.only(desc, clusterTester(opts, t));
238
260
  };
239
261
 
262
+ allocCluster.test.skip = function testClusterSkip(desc, opts, t) {
263
+ test.skip(desc, clusterTester(opts, t));
264
+ };
265
+
240
266
  function connectChannels(channels, callback) {
241
267
  return parallel(channels.map(function (channel) {
242
268
  return function connectChannelToHosts(callback) {
@@ -24,6 +24,19 @@ var parallel = require('run-parallel');
24
24
  var collectParallel = require('collect-parallel/array');
25
25
  var setTimeout = require('timers').setTimeout;
26
26
 
27
+ /*
28
+ BatchClient takes a channel and a set of peers to talk
29
+ to.
30
+
31
+ It has a set of options including:
32
+ - totalRequests
33
+ - delay
34
+ - batchSize
35
+
36
+ If you want to do 100QPS you can set batchSize=1 and delay=10
37
+ If you want to do 1000QPS you can set batchSize=50 and delay=50
38
+
39
+ */
27
40
  module.exports = BatchClient;
28
41
 
29
42
  function BatchClient(channel, hosts, options) {
@@ -42,11 +55,12 @@ function BatchClient(channel, hosts, options) {
42
55
  self.serviceName = 'server';
43
56
  self.endpoint = options.endpoint || 'echo';
44
57
  self.body = 'foobar';
45
- self.timeout = 500;
58
+ self.timeout = options.timeout || 500;
46
59
 
47
60
  self.subChannel = self.channel.makeSubChannel({
48
61
  serviceName: self.serviceName,
49
- peers: self.hosts
62
+ peers: self.hosts,
63
+ minConnections: options.minConnections || null
50
64
  });
51
65
 
52
66
  self.requestOptions = {
@@ -54,6 +68,7 @@ function BatchClient(channel, hosts, options) {
54
68
  hasNoParent: true,
55
69
  timeout: self.timeout,
56
70
  retryFlags: options && options.retryFlags,
71
+ retryLimit: options && options.retryLimit ? options.retryLimit : 5,
57
72
  headers: {
58
73
  cn: 'client',
59
74
  as: 'raw'
@@ -0,0 +1,119 @@
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 test = require('tape');
24
+ var ObjectPool = require('../lib/object_pool');
25
+ var Timer = require('time-mock');
26
+
27
+ function Widget() {
28
+ this.type = null;
29
+ this.count = null;
30
+ }
31
+
32
+ Widget.prototype.reset = function reset(options) {
33
+ this.type = options.type;
34
+ this.count = options.count;
35
+ };
36
+
37
+ Widget.prototype.clear = function clear() {
38
+ this.type = null;
39
+ this.count = null;
40
+ }
41
+
42
+ ObjectPool.setup({
43
+ Type: Widget,
44
+ maxSize: 2
45
+ });
46
+
47
+ test('object pool happy', function t1(assert) {
48
+ var timers = Timer(0);
49
+ var stats = {};
50
+ var channel = {emitFastStat: fakeEmitFastStat};
51
+
52
+ function fakeEmitFastStat(name, type, val, tags) {
53
+ stats[name + tags.toStatKey('')] = val;
54
+ }
55
+
56
+ ObjectPool.bootstrap({
57
+ channel: channel,
58
+ timers: timers,
59
+ reportInterval: 500
60
+ });
61
+
62
+ var pool = ObjectPool.pools.filter(function (p) {
63
+ return p.name === 'Widget';
64
+ })[0];
65
+
66
+ var w = Widget.alloc();
67
+ w.reset({type: 'foo', count: 10});
68
+
69
+ assert.equal(pool.freeList.length, 0, 'no free instances yet');
70
+ assert.equal(pool.outstanding, 1, '1 outstanding instace');
71
+
72
+ var w2 = Widget.alloc();
73
+ w2.reset({type: 'foo', count: 10});
74
+
75
+ assert.equal(pool.freeList.length, 0, 'no free instances yet');
76
+ assert.equal(pool.outstanding, 2, '2 outstanding instace');
77
+
78
+ var w3 = Widget.alloc();
79
+ w3.reset({type: 'foo', count: 10});
80
+
81
+ assert.equal(pool.freeList.length, 0, 'no free instances yet');
82
+ assert.equal(pool.outstanding, 3, '3 outstanding instace');
83
+
84
+ w.free();
85
+ // after w.free() the free list is still 0 because maxSize of the pool is 2
86
+
87
+ assert.equal(pool.freeList.length, 0, 'no free instances');
88
+ assert.equal(pool.outstanding, 2, '2 outstanding instace');
89
+
90
+ w2.free();
91
+
92
+ assert.equal(pool.freeList.length, 1, '1 free instance');
93
+ assert.equal(pool.outstanding, 1, '1 outstanding instace');
94
+
95
+ timers.advance(500);
96
+ assert.equal(stats['tchannel.object-pool.Widget.free'], 1);
97
+ assert.equal(stats['tchannel.object-pool.Widget.outstanding'], 1);
98
+
99
+ w = Widget.alloc();
100
+
101
+ assert.ok(w2 === w, 'object was allocated from free list');
102
+ assert.equal(w2.type, null, 'object type field was cleared');
103
+ assert.equal(w2.count, null, 'object count field was cleared');
104
+
105
+ assert.equal(pool.freeList.length, 0, '0 free instances');
106
+ assert.equal(pool.outstanding, 2, '2 outstanding instace');
107
+
108
+ w.free();
109
+ w3.free();
110
+
111
+ assert.equal(pool.freeList.length, 2, '2 free instances');
112
+ assert.equal(pool.outstanding, 0, '0 outstanding instace');
113
+
114
+ timers.advance(500);
115
+ assert.equal(stats['tchannel.object-pool.Widget.free'], 2);
116
+ assert.equal(stats['tchannel.object-pool.Widget.outstanding'], 0);
117
+
118
+ assert.end();
119
+ });