UNPKG

@zaplify/gcp-pubsub

Version:

Cloud Pub/Sub Client Library for Node.js

148 lines 5.2 kB
"use strict"; // Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. Object.defineProperty(exports, "__esModule", { value: true }); exports.ExponentialRetry = void 0; const heap_js_1 = require("heap-js"); const temporal_1 = require("./temporal"); // Compare function for Heap so we can use it as a priority queue. function comparator(a, b) { return a.retryInfo.nextRetry - b.retryInfo.nextRetry; } /** * Provides a helper that will manage your retries using the "truncated * exponential backoff" strategy. * * Most of the pieces of this library are doing retries via gax, but for * exactly-once delivery, we have some things where gRPC failures won't * take care of it. * * @private */ class ExponentialRetry { constructor(backoff, maxBackoff) { this._items = new heap_js_1.default((comparator)); this._backoffMs = backoff.totalOf('millisecond'); this._maxBackoffMs = maxBackoff.totalOf('millisecond'); } /** * Shut down all operations/timers/etc and return a list of * items that were still pending retry. * * @private */ close() { if (this._timer) { clearTimeout(this._timer); } const leftovers = this._items.toArray(); this._items.clear(); return leftovers; } /** * Place an item on the retry queue. It's important that it's the * same exact item that was already on the queue, if it's being retried * more than once. * * @private */ retryLater(item, callback) { const retried = item; const retryInfo = retried.retryInfo; if (!retryInfo) { // This item's first time through. retried.retryInfo = { firstRetry: Date.now(), nextRetry: Date.now() + this.randomizeDelta(this._backoffMs), multiplier: 1, callback, }; } else { // Not the first time - handle backoff. const nextMultiplier = retryInfo.multiplier * 2; let delta = this.randomizeDelta(nextMultiplier * this._backoffMs); if (delta > this._maxBackoffMs) { delta = this.randomizeDelta(this._maxBackoffMs); } else { retryInfo.multiplier = nextMultiplier; } retryInfo.nextRetry = Date.now() + delta; } // Re-sort it into the heap with the correct position. // It's my assumption here that any item that is being retried is // very likely near or at the top. this._items.remove(retried); this._items.push(retried); // Schedule the next retry. this.scheduleRetry(); } /** * Resets an item that was previously retried. This is useful if you have * persistent items that just need to be retried occasionally. * * @private */ reset(item) { const retried = item; delete retried.retryInfo; } // Takes a time delta and adds fuzz. randomizeDelta(durationMs) { // The fuzz distance should never exceed one second, but in the // case of smaller things, don't end up with a negative delta. const magnitude = durationMs < 1000 ? durationMs : 1000; const offset = Math.random() * magnitude - magnitude / 2.0; return durationMs + offset; } // Looks through the queue to see if there's anything to handle. doRetries() { const now = Date.now(); while (!this._items.isEmpty()) { const next = this._items.peek(); // Within 10msec is close enough. if (next.retryInfo.nextRetry - now < 10) { this._items.pop(); next.retryInfo.callback(next, temporal_1.Duration.from({ millis: now - next.retryInfo.firstRetry })); } else { break; } } // Is there stuff to retry still? if (!this._items.isEmpty()) { this.scheduleRetry(); } } // If there are items to retry, schedule the next timer event. scheduleRetry() { // What's next? const next = this._items.peek(); if (next) { let delta = next.retryInfo.nextRetry - Date.now(); if (delta < 0) { delta = 0; } if (this._timer) { clearTimeout(this._timer); } this._timer = setTimeout(() => { this.doRetries(); }, delta); } } } exports.ExponentialRetry = ExponentialRetry; //# sourceMappingURL=exponential-retry.js.map