retrying-call.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. "use strict";
  2. /*
  3. * Copyright 2022 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. exports.RetryingCall = exports.MessageBufferTracker = exports.RetryThrottler = void 0;
  20. const constants_1 = require("./constants");
  21. const deadline_1 = require("./deadline");
  22. const metadata_1 = require("./metadata");
  23. const logging = require("./logging");
  24. const TRACER_NAME = 'retrying_call';
  25. class RetryThrottler {
  26. constructor(maxTokens, tokenRatio, previousRetryThrottler) {
  27. this.maxTokens = maxTokens;
  28. this.tokenRatio = tokenRatio;
  29. if (previousRetryThrottler) {
  30. /* When carrying over tokens from a previous config, rescale them to the
  31. * new max value */
  32. this.tokens =
  33. previousRetryThrottler.tokens *
  34. (maxTokens / previousRetryThrottler.maxTokens);
  35. }
  36. else {
  37. this.tokens = maxTokens;
  38. }
  39. }
  40. addCallSucceeded() {
  41. this.tokens = Math.max(this.tokens + this.tokenRatio, this.maxTokens);
  42. }
  43. addCallFailed() {
  44. this.tokens = Math.min(this.tokens - 1, 0);
  45. }
  46. canRetryCall() {
  47. return this.tokens > this.maxTokens / 2;
  48. }
  49. }
  50. exports.RetryThrottler = RetryThrottler;
  51. class MessageBufferTracker {
  52. constructor(totalLimit, limitPerCall) {
  53. this.totalLimit = totalLimit;
  54. this.limitPerCall = limitPerCall;
  55. this.totalAllocated = 0;
  56. this.allocatedPerCall = new Map();
  57. }
  58. allocate(size, callId) {
  59. var _a;
  60. const currentPerCall = (_a = this.allocatedPerCall.get(callId)) !== null && _a !== void 0 ? _a : 0;
  61. if (this.limitPerCall - currentPerCall < size ||
  62. this.totalLimit - this.totalAllocated < size) {
  63. return false;
  64. }
  65. this.allocatedPerCall.set(callId, currentPerCall + size);
  66. this.totalAllocated += size;
  67. return true;
  68. }
  69. free(size, callId) {
  70. var _a;
  71. if (this.totalAllocated < size) {
  72. throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > total allocated ${this.totalAllocated}`);
  73. }
  74. this.totalAllocated -= size;
  75. const currentPerCall = (_a = this.allocatedPerCall.get(callId)) !== null && _a !== void 0 ? _a : 0;
  76. if (currentPerCall < size) {
  77. throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > allocated for call ${currentPerCall}`);
  78. }
  79. this.allocatedPerCall.set(callId, currentPerCall - size);
  80. }
  81. freeAll(callId) {
  82. var _a;
  83. const currentPerCall = (_a = this.allocatedPerCall.get(callId)) !== null && _a !== void 0 ? _a : 0;
  84. if (this.totalAllocated < currentPerCall) {
  85. throw new Error(`Invalid buffer allocation state: call ${callId} allocated ${currentPerCall} > total allocated ${this.totalAllocated}`);
  86. }
  87. this.totalAllocated -= currentPerCall;
  88. this.allocatedPerCall.delete(callId);
  89. }
  90. }
  91. exports.MessageBufferTracker = MessageBufferTracker;
  92. const PREVIONS_RPC_ATTEMPTS_METADATA_KEY = 'grpc-previous-rpc-attempts';
  93. const DEFAULT_MAX_ATTEMPTS_LIMIT = 5;
  94. class RetryingCall {
  95. constructor(channel, callConfig, methodName, host, credentials, deadline, callNumber, bufferTracker, retryThrottler) {
  96. var _a;
  97. this.channel = channel;
  98. this.callConfig = callConfig;
  99. this.methodName = methodName;
  100. this.host = host;
  101. this.credentials = credentials;
  102. this.deadline = deadline;
  103. this.callNumber = callNumber;
  104. this.bufferTracker = bufferTracker;
  105. this.retryThrottler = retryThrottler;
  106. this.listener = null;
  107. this.initialMetadata = null;
  108. this.underlyingCalls = [];
  109. this.writeBuffer = [];
  110. /**
  111. * The offset of message indices in the writeBuffer. For example, if
  112. * writeBufferOffset is 10, message 10 is in writeBuffer[0] and message 15
  113. * is in writeBuffer[5].
  114. */
  115. this.writeBufferOffset = 0;
  116. /**
  117. * Tracks whether a read has been started, so that we know whether to start
  118. * reads on new child calls. This only matters for the first read, because
  119. * once a message comes in the child call becomes committed and there will
  120. * be no new child calls.
  121. */
  122. this.readStarted = false;
  123. this.transparentRetryUsed = false;
  124. /**
  125. * Number of attempts so far
  126. */
  127. this.attempts = 0;
  128. this.hedgingTimer = null;
  129. this.committedCallIndex = null;
  130. this.initialRetryBackoffSec = 0;
  131. this.nextRetryBackoffSec = 0;
  132. const maxAttemptsLimit = (_a = channel.getOptions()['grpc-node.retry_max_attempts_limit']) !== null && _a !== void 0 ? _a : DEFAULT_MAX_ATTEMPTS_LIMIT;
  133. if (callConfig.methodConfig.retryPolicy) {
  134. this.state = 'RETRY';
  135. const retryPolicy = callConfig.methodConfig.retryPolicy;
  136. this.nextRetryBackoffSec = this.initialRetryBackoffSec = Number(retryPolicy.initialBackoff.substring(0, retryPolicy.initialBackoff.length - 1));
  137. this.maxAttempts = Math.min(retryPolicy.maxAttempts, maxAttemptsLimit);
  138. }
  139. else if (callConfig.methodConfig.hedgingPolicy) {
  140. this.state = 'HEDGING';
  141. this.maxAttempts = Math.min(callConfig.methodConfig.hedgingPolicy.maxAttempts, maxAttemptsLimit);
  142. }
  143. else {
  144. this.state = 'TRANSPARENT_ONLY';
  145. this.maxAttempts = 1;
  146. }
  147. this.startTime = new Date();
  148. }
  149. getDeadlineInfo() {
  150. if (this.underlyingCalls.length === 0) {
  151. return [];
  152. }
  153. const deadlineInfo = [];
  154. const latestCall = this.underlyingCalls[this.underlyingCalls.length - 1];
  155. if (this.underlyingCalls.length > 1) {
  156. deadlineInfo.push(`previous attempts: ${this.underlyingCalls.length - 1}`);
  157. }
  158. if (latestCall.startTime > this.startTime) {
  159. deadlineInfo.push(`time to current attempt start: ${(0, deadline_1.formatDateDifference)(this.startTime, latestCall.startTime)}`);
  160. }
  161. deadlineInfo.push(...latestCall.call.getDeadlineInfo());
  162. return deadlineInfo;
  163. }
  164. getCallNumber() {
  165. return this.callNumber;
  166. }
  167. trace(text) {
  168. logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '[' + this.callNumber + '] ' + text);
  169. }
  170. reportStatus(statusObject) {
  171. this.trace('ended with status: code=' +
  172. statusObject.code +
  173. ' details="' +
  174. statusObject.details +
  175. '" start time=' +
  176. this.startTime.toISOString());
  177. this.bufferTracker.freeAll(this.callNumber);
  178. this.writeBufferOffset = this.writeBufferOffset + this.writeBuffer.length;
  179. this.writeBuffer = [];
  180. process.nextTick(() => {
  181. var _a;
  182. // Explicitly construct status object to remove progress field
  183. (_a = this.listener) === null || _a === void 0 ? void 0 : _a.onReceiveStatus({
  184. code: statusObject.code,
  185. details: statusObject.details,
  186. metadata: statusObject.metadata,
  187. });
  188. });
  189. }
  190. cancelWithStatus(status, details) {
  191. this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"');
  192. this.reportStatus({ code: status, details, metadata: new metadata_1.Metadata() });
  193. for (const { call } of this.underlyingCalls) {
  194. call.cancelWithStatus(status, details);
  195. }
  196. }
  197. getPeer() {
  198. if (this.committedCallIndex !== null) {
  199. return this.underlyingCalls[this.committedCallIndex].call.getPeer();
  200. }
  201. else {
  202. return 'unknown';
  203. }
  204. }
  205. getBufferEntry(messageIndex) {
  206. var _a;
  207. return ((_a = this.writeBuffer[messageIndex - this.writeBufferOffset]) !== null && _a !== void 0 ? _a : {
  208. entryType: 'FREED',
  209. allocated: false,
  210. });
  211. }
  212. getNextBufferIndex() {
  213. return this.writeBufferOffset + this.writeBuffer.length;
  214. }
  215. clearSentMessages() {
  216. if (this.state !== 'COMMITTED') {
  217. return;
  218. }
  219. const earliestNeededMessageIndex = this.underlyingCalls[this.committedCallIndex].nextMessageToSend;
  220. for (let messageIndex = this.writeBufferOffset; messageIndex < earliestNeededMessageIndex; messageIndex++) {
  221. const bufferEntry = this.getBufferEntry(messageIndex);
  222. if (bufferEntry.allocated) {
  223. this.bufferTracker.free(bufferEntry.message.message.length, this.callNumber);
  224. }
  225. }
  226. this.writeBuffer = this.writeBuffer.slice(earliestNeededMessageIndex - this.writeBufferOffset);
  227. this.writeBufferOffset = earliestNeededMessageIndex;
  228. }
  229. commitCall(index) {
  230. if (this.state === 'COMMITTED') {
  231. return;
  232. }
  233. if (this.underlyingCalls[index].state === 'COMPLETED') {
  234. return;
  235. }
  236. this.trace('Committing call [' +
  237. this.underlyingCalls[index].call.getCallNumber() +
  238. '] at index ' +
  239. index);
  240. this.state = 'COMMITTED';
  241. this.committedCallIndex = index;
  242. for (let i = 0; i < this.underlyingCalls.length; i++) {
  243. if (i === index) {
  244. continue;
  245. }
  246. if (this.underlyingCalls[i].state === 'COMPLETED') {
  247. continue;
  248. }
  249. this.underlyingCalls[i].state = 'COMPLETED';
  250. this.underlyingCalls[i].call.cancelWithStatus(constants_1.Status.CANCELLED, 'Discarded in favor of other hedged attempt');
  251. }
  252. this.clearSentMessages();
  253. }
  254. commitCallWithMostMessages() {
  255. if (this.state === 'COMMITTED') {
  256. return;
  257. }
  258. let mostMessages = -1;
  259. let callWithMostMessages = -1;
  260. for (const [index, childCall] of this.underlyingCalls.entries()) {
  261. if (childCall.state === 'ACTIVE' &&
  262. childCall.nextMessageToSend > mostMessages) {
  263. mostMessages = childCall.nextMessageToSend;
  264. callWithMostMessages = index;
  265. }
  266. }
  267. if (callWithMostMessages === -1) {
  268. /* There are no active calls, disable retries to force the next call that
  269. * is started to be committed. */
  270. this.state = 'TRANSPARENT_ONLY';
  271. }
  272. else {
  273. this.commitCall(callWithMostMessages);
  274. }
  275. }
  276. isStatusCodeInList(list, code) {
  277. return list.some(value => {
  278. var _a;
  279. return value === code ||
  280. value.toString().toLowerCase() === ((_a = constants_1.Status[code]) === null || _a === void 0 ? void 0 : _a.toLowerCase());
  281. });
  282. }
  283. getNextRetryBackoffMs() {
  284. var _a;
  285. const retryPolicy = (_a = this.callConfig) === null || _a === void 0 ? void 0 : _a.methodConfig.retryPolicy;
  286. if (!retryPolicy) {
  287. return 0;
  288. }
  289. const nextBackoffMs = Math.random() * this.nextRetryBackoffSec * 1000;
  290. const maxBackoffSec = Number(retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1));
  291. this.nextRetryBackoffSec = Math.min(this.nextRetryBackoffSec * retryPolicy.backoffMultiplier, maxBackoffSec);
  292. return nextBackoffMs;
  293. }
  294. maybeRetryCall(pushback, callback) {
  295. if (this.state !== 'RETRY') {
  296. callback(false);
  297. return;
  298. }
  299. if (this.attempts >= this.maxAttempts) {
  300. callback(false);
  301. return;
  302. }
  303. let retryDelayMs;
  304. if (pushback === null) {
  305. retryDelayMs = this.getNextRetryBackoffMs();
  306. }
  307. else if (pushback < 0) {
  308. this.state = 'TRANSPARENT_ONLY';
  309. callback(false);
  310. return;
  311. }
  312. else {
  313. retryDelayMs = pushback;
  314. this.nextRetryBackoffSec = this.initialRetryBackoffSec;
  315. }
  316. setTimeout(() => {
  317. var _a, _b;
  318. if (this.state !== 'RETRY') {
  319. callback(false);
  320. return;
  321. }
  322. if ((_b = (_a = this.retryThrottler) === null || _a === void 0 ? void 0 : _a.canRetryCall()) !== null && _b !== void 0 ? _b : true) {
  323. callback(true);
  324. this.attempts += 1;
  325. this.startNewAttempt();
  326. }
  327. }, retryDelayMs);
  328. }
  329. countActiveCalls() {
  330. let count = 0;
  331. for (const call of this.underlyingCalls) {
  332. if ((call === null || call === void 0 ? void 0 : call.state) === 'ACTIVE') {
  333. count += 1;
  334. }
  335. }
  336. return count;
  337. }
  338. handleProcessedStatus(status, callIndex, pushback) {
  339. var _a, _b, _c;
  340. switch (this.state) {
  341. case 'COMMITTED':
  342. case 'TRANSPARENT_ONLY':
  343. this.commitCall(callIndex);
  344. this.reportStatus(status);
  345. break;
  346. case 'HEDGING':
  347. if (this.isStatusCodeInList((_a = this.callConfig.methodConfig.hedgingPolicy.nonFatalStatusCodes) !== null && _a !== void 0 ? _a : [], status.code)) {
  348. (_b = this.retryThrottler) === null || _b === void 0 ? void 0 : _b.addCallFailed();
  349. let delayMs;
  350. if (pushback === null) {
  351. delayMs = 0;
  352. }
  353. else if (pushback < 0) {
  354. this.state = 'TRANSPARENT_ONLY';
  355. this.commitCall(callIndex);
  356. this.reportStatus(status);
  357. return;
  358. }
  359. else {
  360. delayMs = pushback;
  361. }
  362. setTimeout(() => {
  363. this.maybeStartHedgingAttempt();
  364. // If after trying to start a call there are no active calls, this was the last one
  365. if (this.countActiveCalls() === 0) {
  366. this.commitCall(callIndex);
  367. this.reportStatus(status);
  368. }
  369. }, delayMs);
  370. }
  371. else {
  372. this.commitCall(callIndex);
  373. this.reportStatus(status);
  374. }
  375. break;
  376. case 'RETRY':
  377. if (this.isStatusCodeInList(this.callConfig.methodConfig.retryPolicy.retryableStatusCodes, status.code)) {
  378. (_c = this.retryThrottler) === null || _c === void 0 ? void 0 : _c.addCallFailed();
  379. this.maybeRetryCall(pushback, retried => {
  380. if (!retried) {
  381. this.commitCall(callIndex);
  382. this.reportStatus(status);
  383. }
  384. });
  385. }
  386. else {
  387. this.commitCall(callIndex);
  388. this.reportStatus(status);
  389. }
  390. break;
  391. }
  392. }
  393. getPushback(metadata) {
  394. const mdValue = metadata.get('grpc-retry-pushback-ms');
  395. if (mdValue.length === 0) {
  396. return null;
  397. }
  398. try {
  399. return parseInt(mdValue[0]);
  400. }
  401. catch (e) {
  402. return -1;
  403. }
  404. }
  405. handleChildStatus(status, callIndex) {
  406. var _a;
  407. if (this.underlyingCalls[callIndex].state === 'COMPLETED') {
  408. return;
  409. }
  410. this.trace('state=' +
  411. this.state +
  412. ' handling status with progress ' +
  413. status.progress +
  414. ' from child [' +
  415. this.underlyingCalls[callIndex].call.getCallNumber() +
  416. '] in state ' +
  417. this.underlyingCalls[callIndex].state);
  418. this.underlyingCalls[callIndex].state = 'COMPLETED';
  419. if (status.code === constants_1.Status.OK) {
  420. (_a = this.retryThrottler) === null || _a === void 0 ? void 0 : _a.addCallSucceeded();
  421. this.commitCall(callIndex);
  422. this.reportStatus(status);
  423. return;
  424. }
  425. if (this.state === 'COMMITTED') {
  426. this.reportStatus(status);
  427. return;
  428. }
  429. const pushback = this.getPushback(status.metadata);
  430. switch (status.progress) {
  431. case 'NOT_STARTED':
  432. // RPC never leaves the client, always safe to retry
  433. this.startNewAttempt();
  434. break;
  435. case 'REFUSED':
  436. // RPC reaches the server library, but not the server application logic
  437. if (this.transparentRetryUsed) {
  438. this.handleProcessedStatus(status, callIndex, pushback);
  439. }
  440. else {
  441. this.transparentRetryUsed = true;
  442. this.startNewAttempt();
  443. }
  444. break;
  445. case 'DROP':
  446. this.commitCall(callIndex);
  447. this.reportStatus(status);
  448. break;
  449. case 'PROCESSED':
  450. this.handleProcessedStatus(status, callIndex, pushback);
  451. break;
  452. }
  453. }
  454. maybeStartHedgingAttempt() {
  455. if (this.state !== 'HEDGING') {
  456. return;
  457. }
  458. if (!this.callConfig.methodConfig.hedgingPolicy) {
  459. return;
  460. }
  461. if (this.attempts >= this.maxAttempts) {
  462. return;
  463. }
  464. this.attempts += 1;
  465. this.startNewAttempt();
  466. this.maybeStartHedgingTimer();
  467. }
  468. maybeStartHedgingTimer() {
  469. var _a, _b, _c;
  470. if (this.hedgingTimer) {
  471. clearTimeout(this.hedgingTimer);
  472. }
  473. if (this.state !== 'HEDGING') {
  474. return;
  475. }
  476. if (!this.callConfig.methodConfig.hedgingPolicy) {
  477. return;
  478. }
  479. const hedgingPolicy = this.callConfig.methodConfig.hedgingPolicy;
  480. if (this.attempts >= this.maxAttempts) {
  481. return;
  482. }
  483. const hedgingDelayString = (_a = hedgingPolicy.hedgingDelay) !== null && _a !== void 0 ? _a : '0s';
  484. const hedgingDelaySec = Number(hedgingDelayString.substring(0, hedgingDelayString.length - 1));
  485. this.hedgingTimer = setTimeout(() => {
  486. this.maybeStartHedgingAttempt();
  487. }, hedgingDelaySec * 1000);
  488. (_c = (_b = this.hedgingTimer).unref) === null || _c === void 0 ? void 0 : _c.call(_b);
  489. }
  490. startNewAttempt() {
  491. const child = this.channel.createLoadBalancingCall(this.callConfig, this.methodName, this.host, this.credentials, this.deadline);
  492. this.trace('Created child call [' +
  493. child.getCallNumber() +
  494. '] for attempt ' +
  495. this.attempts);
  496. const index = this.underlyingCalls.length;
  497. this.underlyingCalls.push({
  498. state: 'ACTIVE',
  499. call: child,
  500. nextMessageToSend: 0,
  501. startTime: new Date()
  502. });
  503. const previousAttempts = this.attempts - 1;
  504. const initialMetadata = this.initialMetadata.clone();
  505. if (previousAttempts > 0) {
  506. initialMetadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`);
  507. }
  508. let receivedMetadata = false;
  509. child.start(initialMetadata, {
  510. onReceiveMetadata: metadata => {
  511. this.trace('Received metadata from child [' + child.getCallNumber() + ']');
  512. this.commitCall(index);
  513. receivedMetadata = true;
  514. if (previousAttempts > 0) {
  515. metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`);
  516. }
  517. if (this.underlyingCalls[index].state === 'ACTIVE') {
  518. this.listener.onReceiveMetadata(metadata);
  519. }
  520. },
  521. onReceiveMessage: message => {
  522. this.trace('Received message from child [' + child.getCallNumber() + ']');
  523. this.commitCall(index);
  524. if (this.underlyingCalls[index].state === 'ACTIVE') {
  525. this.listener.onReceiveMessage(message);
  526. }
  527. },
  528. onReceiveStatus: status => {
  529. this.trace('Received status from child [' + child.getCallNumber() + ']');
  530. if (!receivedMetadata && previousAttempts > 0) {
  531. status.metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`);
  532. }
  533. this.handleChildStatus(status, index);
  534. },
  535. });
  536. this.sendNextChildMessage(index);
  537. if (this.readStarted) {
  538. child.startRead();
  539. }
  540. }
  541. start(metadata, listener) {
  542. this.trace('start called');
  543. this.listener = listener;
  544. this.initialMetadata = metadata;
  545. this.attempts += 1;
  546. this.startNewAttempt();
  547. this.maybeStartHedgingTimer();
  548. }
  549. handleChildWriteCompleted(childIndex) {
  550. var _a, _b;
  551. const childCall = this.underlyingCalls[childIndex];
  552. const messageIndex = childCall.nextMessageToSend;
  553. (_b = (_a = this.getBufferEntry(messageIndex)).callback) === null || _b === void 0 ? void 0 : _b.call(_a);
  554. this.clearSentMessages();
  555. childCall.nextMessageToSend += 1;
  556. this.sendNextChildMessage(childIndex);
  557. }
  558. sendNextChildMessage(childIndex) {
  559. const childCall = this.underlyingCalls[childIndex];
  560. if (childCall.state === 'COMPLETED') {
  561. return;
  562. }
  563. if (this.getBufferEntry(childCall.nextMessageToSend)) {
  564. const bufferEntry = this.getBufferEntry(childCall.nextMessageToSend);
  565. switch (bufferEntry.entryType) {
  566. case 'MESSAGE':
  567. childCall.call.sendMessageWithContext({
  568. callback: error => {
  569. // Ignore error
  570. this.handleChildWriteCompleted(childIndex);
  571. },
  572. }, bufferEntry.message.message);
  573. break;
  574. case 'HALF_CLOSE':
  575. childCall.nextMessageToSend += 1;
  576. childCall.call.halfClose();
  577. break;
  578. case 'FREED':
  579. // Should not be possible
  580. break;
  581. }
  582. }
  583. }
  584. sendMessageWithContext(context, message) {
  585. var _a;
  586. this.trace('write() called with message of length ' + message.length);
  587. const writeObj = {
  588. message,
  589. flags: context.flags,
  590. };
  591. const messageIndex = this.getNextBufferIndex();
  592. const bufferEntry = {
  593. entryType: 'MESSAGE',
  594. message: writeObj,
  595. allocated: this.bufferTracker.allocate(message.length, this.callNumber),
  596. };
  597. this.writeBuffer.push(bufferEntry);
  598. if (bufferEntry.allocated) {
  599. (_a = context.callback) === null || _a === void 0 ? void 0 : _a.call(context);
  600. for (const [callIndex, call] of this.underlyingCalls.entries()) {
  601. if (call.state === 'ACTIVE' &&
  602. call.nextMessageToSend === messageIndex) {
  603. call.call.sendMessageWithContext({
  604. callback: error => {
  605. // Ignore error
  606. this.handleChildWriteCompleted(callIndex);
  607. },
  608. }, message);
  609. }
  610. }
  611. }
  612. else {
  613. this.commitCallWithMostMessages();
  614. // commitCallWithMostMessages can fail if we are between ping attempts
  615. if (this.committedCallIndex === null) {
  616. return;
  617. }
  618. const call = this.underlyingCalls[this.committedCallIndex];
  619. bufferEntry.callback = context.callback;
  620. if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) {
  621. call.call.sendMessageWithContext({
  622. callback: error => {
  623. // Ignore error
  624. this.handleChildWriteCompleted(this.committedCallIndex);
  625. },
  626. }, message);
  627. }
  628. }
  629. }
  630. startRead() {
  631. this.trace('startRead called');
  632. this.readStarted = true;
  633. for (const underlyingCall of this.underlyingCalls) {
  634. if ((underlyingCall === null || underlyingCall === void 0 ? void 0 : underlyingCall.state) === 'ACTIVE') {
  635. underlyingCall.call.startRead();
  636. }
  637. }
  638. }
  639. halfClose() {
  640. this.trace('halfClose called');
  641. const halfCloseIndex = this.getNextBufferIndex();
  642. this.writeBuffer.push({
  643. entryType: 'HALF_CLOSE',
  644. allocated: false,
  645. });
  646. for (const call of this.underlyingCalls) {
  647. if ((call === null || call === void 0 ? void 0 : call.state) === 'ACTIVE' &&
  648. call.nextMessageToSend === halfCloseIndex) {
  649. call.nextMessageToSend += 1;
  650. call.call.halfClose();
  651. }
  652. }
  653. }
  654. setCredentials(newCredentials) {
  655. throw new Error('Method not implemented.');
  656. }
  657. getMethod() {
  658. return this.methodName;
  659. }
  660. getHost() {
  661. return this.host;
  662. }
  663. }
  664. exports.RetryingCall = RetryingCall;
  665. //# sourceMappingURL=retrying-call.js.map