@zaplify/gcp-pubsub
Version:
Cloud Pub/Sub Client Library for Node.js
148 lines • 5.2 kB
JavaScript
"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