Skip to content

Commit

Permalink
Added retries
Browse files Browse the repository at this point in the history
Signed-off-by: Marcos Candeia <[email protected]>
  • Loading branch information
mcandeia committed Nov 16, 2024
1 parent 0bb5eff commit 1574111
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 1 deletion.
7 changes: 6 additions & 1 deletion src/actors/proxyutil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
makeDuplexChannelWith,
makeWebSocket,
} from "./util/channels/channel.ts";
import { retry } from "./util/retry.ts";

export const ACTOR_ID_HEADER_NAME = "x-deno-isolate-instance-id";
export const ACTOR_ID_QS_NAME = "deno_isolate_instance_id";
Expand Down Expand Up @@ -112,7 +113,11 @@ export class ActorAwaiter<
true,
);
const nextConnection = async () => {
const ch = await connect();
const ch = await retry(connect, {
initialDelay: 1e3, // one second of initial delay
maxAttempts: 30, // 30 attempts
maxDelay: 10e3, // 10 seconds max delay
});
const recvLoop = async () => {
for await (const val of ch.recv(reliableCh.signal)) {
recvChan.send(val);
Expand Down
72 changes: 72 additions & 0 deletions src/actors/util/retry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// deno-lint-ignore-file no-explicit-any
interface RetryOptions<T> {
maxAttempts?: number;
initialDelay?: number;
maxDelay?: number;
shouldRetry?: (error: any) => boolean;
onRetry?: (error: any, attempt: number) => void;
retryableErrors?: Array<new (...args: any[]) => Error>;
}

/**
* Retries a function with exponential backoff
* @param f - The function to retry
* @param options - Optional configuration
* @returns Promise with the function result
* @throws Last error encountered if all retries fail
*/
export async function retry<T>(
f: () => Promise<T>,
options: RetryOptions<T> = {},
): Promise<T> {
const {
maxAttempts = 3,
initialDelay = 1000,
maxDelay = 30000,
shouldRetry = () => true,
onRetry = () => {},
retryableErrors = [],
} = options;

let attempts = 0;
let lastError: Error;

while (attempts < maxAttempts) {
try {
return await f();
} catch (error) {
attempts++;
lastError = error as Error;

// Check if error is retryable based on error types
const isRetryableError = retryableErrors.length === 0 ||
retryableErrors.some((errorType) => error instanceof errorType);

// If we've used all attempts or shouldn't retry, throw the error
if (
attempts >= maxAttempts ||
!shouldRetry(error) ||
!isRetryableError
) {
throw error;
}

// Notify about retry attempt
onRetry(error, attempts);

// Calculate delay with exponential backoff
const delay = Math.min(
initialDelay * Math.pow(2, attempts - 1),
maxDelay,
);

// Add some jitter to prevent thundering herd
const jitter = Math.random() * 100;

// Wait before next attempt
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
}
}

throw lastError!;
}

0 comments on commit 1574111

Please sign in to comment.