tchannel 3.6.17 → 3.6.18

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/channel.js CHANGED
@@ -230,14 +230,6 @@ function TChannel(options) {
230
230
  this.statsd = this.options.statsd;
231
231
  this.batchStats = null;
232
232
 
233
- if (this.statsd) {
234
- ObjectPool.bootstrap({
235
- statsd: this.statsd,
236
- reportInterval: 1000,
237
- timers: this.timers
238
- });
239
- }
240
-
241
233
  this.requestDefaults = this.options.requestDefaults ?
242
234
  new RequestDefaults(this.options.requestDefaults) : null;
243
235
 
@@ -262,6 +254,15 @@ function TChannel(options) {
262
254
  this.batchStats = this.topChannel.batchStats;
263
255
  }
264
256
 
257
+ if (this.batchStats) {
258
+ ObjectPool.bootstrap({
259
+ channel: this,
260
+ reportInterval: 5000,
261
+ timers: this.timers,
262
+ debug: this.options.objectPoolDebug ? true : false
263
+ });
264
+ }
265
+
265
266
  this.maximumRelayTTL = MAXIMUM_TTL_ALLOWED;
266
267
 
267
268
  function doSanitySweep() {
@@ -918,7 +919,7 @@ TChannel.prototype.close = function close(callback) {
918
919
 
919
920
  var counter = 1;
920
921
 
921
- if (self.statsd) {
922
+ if (self.batchStats) {
922
923
  ObjectPool.unref();
923
924
  }
924
925
 
package/lazy_relay.js CHANGED
@@ -20,6 +20,7 @@
20
20
 
21
21
  'use strict';
22
22
 
23
+ var process = require('process');
23
24
  var errors = require('./errors');
24
25
  var v2 = require('./v2');
25
26
  var stat = require('./stat-tags.js');
@@ -61,6 +62,7 @@ function LazyRelayInReq(conn, reqFrame) {
61
62
  this.tracing = null;
62
63
  this.reqContFrames = [];
63
64
  this.hasRead = null;
65
+ this.waitForIdentSlot = null;
64
66
 
65
67
  this.boundExtendLogInfo = extendLogInfo;
66
68
  this.boundOnIdentified = onIdentified;
@@ -72,6 +74,10 @@ function LazyRelayInReq(conn, reqFrame) {
72
74
  }
73
75
 
74
76
  function onIdentified(err) {
77
+ // The ident descriptor will be cleared out of the peer when the ident
78
+ // comes back, so this slot id will be invalid once ident happens.
79
+ self.waitForIdentSlot = -1;
80
+
75
81
  if (err) {
76
82
  self.onError(err);
77
83
  } else {
@@ -102,6 +108,7 @@ LazyRelayInReq.prototype.reset = function reset(conn, reqFrame) {
102
108
  this.reqContFrames.length = 0;
103
109
  this.hasRead = false;
104
110
  this.circuit = reqFrame.circuit;
111
+ this.waitForIdentSlot = null;
105
112
  };
106
113
 
107
114
  LazyRelayInReq.prototype.clear = function clear() {
@@ -109,6 +116,10 @@ LazyRelayInReq.prototype.clear = function clear() {
109
116
  this.outreq.free();
110
117
  }
111
118
 
119
+ if (this.peer && this.waitForIdentSlot !== null) {
120
+ this.peer.stopWaitingForIdentified(this.waitForIdentSlot);
121
+ }
122
+
112
123
  this.channel = null;
113
124
  this.conn = null;
114
125
  this.start = null;
@@ -130,6 +141,7 @@ LazyRelayInReq.prototype.clear = function clear() {
130
141
  this.reqContFrames.length = 0;
131
142
  this.hasRead = null;
132
143
  this.circuit = null;
144
+ this.waitForIdentSlot = null;
133
145
  };
134
146
 
135
147
  ObjectPool.setup({Type: LazyRelayInReq, maxSize: 200});
@@ -273,7 +285,7 @@ function createOutRequest() {
273
285
  if (conn && conn.remoteName && !conn.closing) {
274
286
  self.forwardTo(conn);
275
287
  } else {
276
- self.peer.waitForIdentified(self.boundOnIdentified);
288
+ self.waitForIdentSlot = self.peer.waitForIdentified(self.boundOnIdentified);
277
289
  }
278
290
  };
279
291
 
@@ -433,6 +445,8 @@ function onError(err) {
433
445
  }));
434
446
 
435
447
  self.reqContFrames.length = 0;
448
+
449
+ self.free();
436
450
  };
437
451
 
438
452
  LazyRelayInReq.prototype.sendErrorFrame =
@@ -698,7 +712,14 @@ function emitError(err) {
698
712
  self.inreq.circuit.state.onRequestError(err);
699
713
  }
700
714
 
701
- self.inreq.onError(err);
715
+ // We need to defer the inreq onError work, because we might be
716
+ // *syncronously* processing an error after an attempt to write to a
717
+ // socket. Otherwise, we might end up freeing the in/outreq pair before a
718
+ // handleFrameLazily has completed.
719
+ process.nextTick(deferInReqOnError);
720
+ function deferInReqOnError() {
721
+ self.inreq.onError(err);
722
+ }
702
723
  };
703
724
 
704
725
  LazyRelayOutReq.prototype.logError =
@@ -21,6 +21,7 @@
21
21
  'use strict';
22
22
 
23
23
  var assert = require('assert');
24
+ var stat = require('../stat-tags');
24
25
 
25
26
  var DEFAULT_MAX_SIZE = 1000;
26
27
 
@@ -42,16 +43,28 @@ function ObjectPool(options) {
42
43
 
43
44
  this.freeList = [];
44
45
  this.outstanding = 0;
45
- this.freeStatName = 'object-pools.' + this.name + '.free';
46
- this.outstandingStatName = 'object-pools.' + this.name + '.outstanding';
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 = [];
47
59
  }
48
60
 
49
- ObjectPool.statsd = null;
61
+ ObjectPool.channel = null;
50
62
  ObjectPool.reportInterval = null;
51
63
  ObjectPool.timers = null;
52
64
  ObjectPool.pools = [];
53
65
  ObjectPool.timer = null;
54
66
  ObjectPool.refs = 0;
67
+ ObjectPool.debug = false;
55
68
 
56
69
  ObjectPool.setup = function setup(options) {
57
70
  var pool = new ObjectPool(options);
@@ -82,11 +95,11 @@ ObjectPool.bootstrap = function bootstrap(options) {
82
95
  assert(typeof options === 'object', 'expected options object');
83
96
 
84
97
  assert(
85
- typeof options.statsd === 'object' &&
86
- typeof options.statsd.gauge === 'function',
87
- 'expected options.statsd to be statsd instance'
98
+ typeof options.channel === 'object' &&
99
+ typeof options.channel.emitFastStat === 'function',
100
+ 'expected options.channel to be TChannel instance'
88
101
  );
89
- ObjectPool.statsd = options.statsd;
102
+ ObjectPool.channel = options.channel;
90
103
 
91
104
  assert(
92
105
  typeof options.reportInterval === 'number',
@@ -101,6 +114,10 @@ ObjectPool.bootstrap = function bootstrap(options) {
101
114
  );
102
115
  ObjectPool.timers = options.timers;
103
116
 
117
+ if (typeof options.debug === 'boolean') {
118
+ ObjectPool.debug = options.debug;
119
+ }
120
+
104
121
  ObjectPool.timer = ObjectPool.timers.setTimeout(
105
122
  ObjectPool.reportStats,
106
123
  ObjectPool.reportInterval
@@ -120,7 +137,7 @@ ObjectPool.reportStats = function reportStats() {
120
137
 
121
138
  var i;
122
139
  for (i = 0; i < ObjectPool.pools.length; i++) {
123
- ObjectPool.pools[i].reportStats(ObjectPool.statsd);
140
+ ObjectPool.pools[i].reportStats(ObjectPool.channel);
124
141
  }
125
142
 
126
143
  ObjectPool.timer = ObjectPool.timers.setTimeout(
@@ -129,16 +146,19 @@ ObjectPool.reportStats = function reportStats() {
129
146
  );
130
147
  };
131
148
 
132
- ObjectPool.prototype.reportStats = function reportStats(statsd) {
133
- statsd.gauge(
134
- this.freeStatName,
135
- this.freeList.length
149
+ ObjectPool.prototype.reportStats = function reportStats(channel) {
150
+ channel.emitFastStat(
151
+ 'tchannel.object-pool',
152
+ 'gauge',
153
+ this.freeList.length,
154
+ this.freeListStatTags
136
155
  );
137
156
 
138
- // This stat is how we infer the maxSize property
139
- statsd.gauge(
140
- this.outstandingStatName,
141
- this.outstanding
157
+ channel.emitFastStat(
158
+ 'tchannel.object-pool',
159
+ 'gauge',
160
+ this.outstanding,
161
+ this.outstandingStatTags
142
162
  );
143
163
  };
144
164
 
@@ -149,10 +169,20 @@ ObjectPool.prototype.get = function get() {
149
169
  inst = this.freeList.pop();
150
170
  assert(inst._objectPoolIsFreed, 'instance retreived from pool is free');
151
171
  inst._objectPoolIsFreed = false;
172
+
173
+ if (ObjectPool.debug) {
174
+ this.outstandingList.push(inst);
175
+ }
176
+
152
177
  return inst;
153
178
  } else {
154
179
  inst = new this.Type();
155
180
  inst._objectPoolIsFreed = false;
181
+
182
+ if (ObjectPool.debug) {
183
+ this.outstandingList.push(inst);
184
+ }
185
+
156
186
  return inst;
157
187
  }
158
188
  };
@@ -166,4 +196,14 @@ ObjectPool.prototype.free = function free(inst) {
166
196
  this.freeList.push(inst);
167
197
  }
168
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
+ }
169
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.17",
5
+ "version": "3.6.18",
6
6
  "scripts": {
7
7
  "lint": "eslint $(git ls-files | grep '.js$')",
8
8
  "travis": "npm run test",
package/peer.js CHANGED
@@ -36,6 +36,7 @@ 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;
41
42
 
@@ -65,6 +66,8 @@ function TChannelPeer(channel, hostPort, options) {
65
66
  this.boundOnPendingChange = onPendingChange;
66
67
  this.scoreRange = null;
67
68
 
69
+ this.waitForIdentifiedListeners = [];
70
+
68
71
  this.reportInterval = options.reportInterval || DEFAULT_REPORT_INTERVAL;
69
72
  if (this.reportInterval > 0 && this.channel.emitConnectionMetrics) {
70
73
  this.reportTimer = this.timers.setTimeout(
@@ -419,20 +422,35 @@ function waitForIdentified(conn, callback) {
419
422
  } else if (conn.remoteName) {
420
423
  callback(null);
421
424
  } else {
422
- self._waitForIdentified(conn, callback);
425
+ return self._waitForIdentified(conn, callback);
423
426
  }
427
+
428
+ return -1;
424
429
  };
425
430
 
426
431
  TChannelPeer.prototype._waitForIdentified =
427
432
  function _waitForIdentified(conn, callback) {
428
433
  var self = this;
429
434
 
435
+ // Setup an ident descriptor so we can stop waiting for identified later
436
+ var slot = self.getIdentDescriptorSlot();
437
+ var descriptor = WaitForIdentifiedDescriptor.alloc();
438
+ descriptor.reset(
439
+ onConnectionClose,
440
+ onConnectionError,
441
+ onIdentified,
442
+ conn
443
+ );
444
+ self.waitForIdentifiedListeners[slot] = descriptor;
445
+
430
446
  self.pendingIdentified++;
431
447
  conn.errorEvent.on(onConnectionError);
432
448
  conn.closeEvent.on(onConnectionClose);
433
449
  conn.identifiedEvent.on(onIdentified);
434
450
  self.invalidateScore('waitForIdentified');
435
451
 
452
+ return slot;
453
+
436
454
  function onConnectionError(err) {
437
455
  finish(err);
438
456
  }
@@ -446,15 +464,45 @@ function _waitForIdentified(conn, callback) {
446
464
  }
447
465
 
448
466
  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');
467
+ self.stopWaitingForIdentified(slot);
454
468
  callback(err);
455
469
  }
456
470
  };
457
471
 
472
+ TChannelPeer.prototype.stopWaitingForIdentified =
473
+ function stopWaitingForIdentified(slot) {
474
+ assert(typeof slot === 'number', 'stopWaitingForIdentified arg1 should be number');
475
+
476
+ if (slot === -1) {
477
+ // when connection was already identified, `waitForIdentified` will
478
+ // return -1
479
+ return;
480
+ }
481
+
482
+ var descriptor = this.waitForIdentifiedListeners[slot];
483
+ this.waitForIdentifiedListeners[slot] = null;
484
+ var conn = descriptor.conn;
485
+
486
+ conn.errorEvent.removeListener(descriptor.error);
487
+ conn.closeEvent.removeListener(descriptor.close);
488
+ conn.identifiedEvent.removeListener(descriptor.ident);
489
+ this.pendingIdentified = 0;
490
+ this.invalidateScore('waitForIdentified > finish');
491
+
492
+ descriptor.free();
493
+ };
494
+
495
+ TChannelPeer.prototype.getIdentDescriptorSlot =
496
+ function getIdentDescriptorSlot() {
497
+ var i;
498
+ for (i = 0; i < this.waitForIdentifiedListeners.length; i++) {
499
+ if (this.waitForIdentifiedListeners[i] === null) {
500
+ return i;
501
+ }
502
+ }
503
+ return this.waitForIdentifiedListeners.length;
504
+ };
505
+
458
506
  TChannelPeer.prototype.request = function peerRequest(options) {
459
507
  var self = this;
460
508
  options.timeout = options.timeout || Request.defaultTimeout;
@@ -677,3 +725,28 @@ TChannelPeer.prototype.getScore = function getScore() {
677
725
  };
678
726
 
679
727
  module.exports = TChannelPeer;
728
+
729
+ function WaitForIdentifiedDescriptor(close, error, ident, conn) {
730
+ this.close = null;
731
+ this.error = null;
732
+ this.ident = null;
733
+ this.conn = null;
734
+ }
735
+
736
+ WaitForIdentifiedDescriptor.prototype.reset =
737
+ function reset(close, error, ident, conn) {
738
+ this.close = close;
739
+ this.error = error;
740
+ this.ident = ident;
741
+ this.conn = conn;
742
+ };
743
+
744
+ WaitForIdentifiedDescriptor.prototype.clear =
745
+ function clear() {
746
+ this.close = null;
747
+ this.error = null;
748
+ this.ident = null;
749
+ this.conn = null;
750
+ };
751
+
752
+ ObjectPool.setup({Type: WaitForIdentifiedDescriptor, maxSize: 100});
package/relay_handler.js CHANGED
@@ -59,6 +59,7 @@ RelayHandler.prototype.handleLazily = function handleLazily(conn, reqFrame) {
59
59
  if (!rereq.peer) {
60
60
  rereq.sendErrorFrame('Declined', 'no peer available for request');
61
61
  self.logger.info('no relay peer available', rereq.extendLogInfo({}));
62
+ rereq.free();
62
63
  return true;
63
64
  }
64
65
 
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
+ };
@@ -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);
@@ -47,10 +47,14 @@ ObjectPool.setup({
47
47
  test('object pool happy', function t1(assert) {
48
48
  var timers = Timer(0);
49
49
  var stats = {};
50
- var statsd = {gauge: function (k, v) { stats[k] = v; }};
50
+ var channel = {emitFastStat: fakeEmitFastStat};
51
+
52
+ function fakeEmitFastStat(name, type, val, tags) {
53
+ stats[name + tags.toStatKey('')] = val;
54
+ }
51
55
 
52
56
  ObjectPool.bootstrap({
53
- statsd: statsd,
57
+ channel: channel,
54
58
  timers: timers,
55
59
  reportInterval: 500
56
60
  });
@@ -89,8 +93,8 @@ test('object pool happy', function t1(assert) {
89
93
  assert.equal(pool.outstanding, 1, '1 outstanding instace');
90
94
 
91
95
  timers.advance(500);
92
- assert.equal(stats['object-pools.Widget.free'], 1);
93
- assert.equal(stats['object-pools.Widget.outstanding'], 1);
96
+ assert.equal(stats['tchannel.object-pool.Widget.free'], 1);
97
+ assert.equal(stats['tchannel.object-pool.Widget.outstanding'], 1);
94
98
 
95
99
  w = Widget.alloc();
96
100
 
@@ -108,8 +112,8 @@ test('object pool happy', function t1(assert) {
108
112
  assert.equal(pool.outstanding, 0, '0 outstanding instace');
109
113
 
110
114
  timers.advance(500);
111
- assert.equal(stats['object-pools.Widget.free'], 2);
112
- assert.equal(stats['object-pools.Widget.outstanding'], 0);
115
+ assert.equal(stats['tchannel.object-pool.Widget.free'], 2);
116
+ assert.equal(stats['tchannel.object-pool.Widget.outstanding'], 0);
113
117
 
114
118
  assert.end();
115
119
  });
@@ -301,6 +301,7 @@ allocCluster.test('lazy relay request times out', {
301
301
 
302
302
  var relayOutPeer = relayChan.peers.get(dest.hostPort);
303
303
  relayOutPeer.waitForIdentified = function punchWaitForIdentified() {
304
+ return -1;
304
305
  };
305
306
 
306
307
  sourceChan.request({
@@ -442,7 +443,7 @@ allocCluster.test('relay request handles channel close correctly', {
442
443
  'tchannel.connection.reset',
443
444
  'expected connection error');
444
445
  assert.notOk(res, 'expected no response');
445
- finish();
446
+ process.nextTick(finish);
446
447
  }
447
448
 
448
449
  function finish() {