Skip to content

Commit

Permalink
fix retry functionality
Browse files Browse the repository at this point in the history
Due to timeout the retry functionality was not working.

It failed for the following scenario:
- 1st attempt fails due to timeout
- 2nd attempt fails immediately because they share the same timeout
signal and it is already expired.
  • Loading branch information
il3ven committed May 31, 2023
1 parent fcd6413 commit 5563e3b
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 68 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 7 additions & 34 deletions src/api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,6 @@ function validate(value) {
return true;
}

// NOTE: `AbortSignal.timeout` isn't yet supported:
// https://github.com/mysticatea/abort-controller/issues/35
export const AbortSignal = {
timeout: function (value) {
const controller = new AbortController();
setTimeout(() => controller.abort(), value);
return controller.signal;
},
};

async function route(message) {
const { type } = message;

Expand All @@ -57,12 +47,7 @@ async function route(message) {
await rateLimiter.removeTokens(1);
}

if (options.timeout || timeoutFromConfig) {
options.signal = AbortSignal.timeout(
options.timeout ?? timeoutFromConfig
);
delete options.timeout;
}
options.timeout = options.timeout ?? timeoutFromConfig;

let results;
try {
Expand All @@ -89,14 +74,10 @@ async function route(message) {
await rateLimiter.removeTokens(1);
}

let signal;
if (timeoutFromMsg || timeoutFromConfig) {
signal = AbortSignal.timeout(timeoutFromMsg ?? timeoutFromConfig);
}

const timeout = timeoutFromMsg ?? timeoutFromConfig;
let data;
try {
data = await request(url, method, body, headers, signal, retryConfig);
data = await request(url, method, body, headers, timeout, retryConfig);
} catch (error) {
return { ...message, error: error.toString() };
}
Expand All @@ -119,16 +100,12 @@ async function route(message) {
await rateLimiter.removeTokens(1);
}

let signal;
if (timeoutFromMsg || timeoutFromConfig) {
signal = AbortSignal.timeout(timeoutFromMsg ?? timeoutFromConfig);
}

const timeout = timeoutFromMsg ?? timeoutFromConfig;
let data;
try {
const method = "GET";
const body = null;
data = await request(url, method, body, headers, signal, retryConfig);
data = await request(url, method, body, headers, timeout, retryConfig);
} catch (error) {
return { ...message, error: error.toString() };
}
Expand Down Expand Up @@ -184,15 +161,11 @@ async function route(message) {
await rateLimiter.removeTokens(1);
}

let signal;
if (timeoutFromMsg || timeoutFromConfig) {
signal = AbortSignal.timeout(timeoutFromMsg ?? timeoutFromConfig);
}

const timeout = timeoutFromMsg ?? timeoutFromConfig;
let data;
try {
const body = null;
data = await request(uri, "GET", body, headers, signal, retryConfig);
data = await request(uri, "GET", body, headers, timeout, retryConfig);
} catch (error) {
return { ...message, error: error.toString() };
}
Expand Down
66 changes: 43 additions & 23 deletions src/eth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,51 @@ import {
getTransactionReceipt,
getLogs,
} from "eth-fun";
import retry from "async-retry";

import { NotImplementedError } from "./errors.mjs";

// NOTE: `AbortSignal.timeout` isn't yet supported:
// https://github.com/mysticatea/abort-controller/issues/35
export const AbortSignal = {
timeout: function (value) {
const controller = new AbortController();
setTimeout(() => controller.abort(), value);
return controller.signal;
},
};

export async function translate(options, method, params) {
if (method === "eth_getTransactionReceipt") {
return await getTransactionReceipt(options, params[0]);
} else if (method === "eth_getBlockByNumber") {
// NOTE: `getBlockByNumber` expects the `blockNumber` input to be an
// hexadecimal (`0x...`) value.
return await getBlockByNumber(options, ...params);
} else if (method === "eth_blockNumber") {
return await blockNumber(options);
} else if (method === "eth_call") {
const { from, to, data } = params[0];
return await call(options, from, to, data, params[1]);
} else if (method === "eth_getLogs") {
const { fromBlock, toBlock, address, topics, limit } = params[0];
return await getLogs(options, {
fromBlock,
toBlock,
address,
topics,
limit,
});
} else {
throw new NotImplementedError();
}
return retry(async (bail) => {
if (options.timeout) {
options.signal = AbortSignal.timeout(
options.timeout ?? timeoutFromConfig
);
delete options.timeout;
}

if (method === "eth_getTransactionReceipt") {
return await getTransactionReceipt(options, params[0]);
} else if (method === "eth_getBlockByNumber") {
// NOTE: `getBlockByNumber` expects the `blockNumber` input to be an
// hexadecimal (`0x...`) value.
return await getBlockByNumber(options, ...params);
} else if (method === "eth_blockNumber") {
return await blockNumber(options);
} else if (method === "eth_call") {
const { from, to, data } = params[0];
return await call(options, from, to, data, params[1]);
} else if (method === "eth_getLogs") {
const { fromBlock, toBlock, address, topics, limit } = params[0];
return await getLogs(options, {
fromBlock,
toBlock,
address,
topics,
limit,
});
} else {
bail(new NotImplementedError());
}
});
}
27 changes: 18 additions & 9 deletions src/request.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import fetch from "cross-fetch";
import retry from "async-retry";

// NOTE: `AbortSignal.timeout` isn't yet supported:
// https://github.com/mysticatea/abort-controller/issues/35
export const AbortSignal = {
timeout: function (value) {
const controller = new AbortController();
setTimeout(() => controller.abort(), value);
return controller.signal;
},
};

export async function request(
url,
method,
body,
headers,
signal,
timeout,
retryConfig = { retries: 0 }
) {
return retry(async (bail) => {
Expand All @@ -20,23 +30,22 @@ export async function request(
if (headers) {
options.headers = headers;
}
if (signal) {
options.signal = signal;
if (timeout) {
options.signal = AbortSignal.timeout(timeout);
}

// NOTE: We let `fetch` throw. Error must be caught on `request` user level.
const results = await fetch(url, options);
const answer = await results.text();

if (results.status === 429) {
throw new Error(
`Request to url "${url}" with method "${method}" and body "${JSON.stringify(
body
)}" unsuccessful with status: ${results.status} and answer: "${answer}"`
);
const err = `Request to url "${url}" with method "${method}" and body "${JSON.stringify(
body
)}" unsuccessful with status: ${results.status} and answer: "${answer}"`;
throw new Error(err);
}

if (results.status >= 400) {
if (results.status >= 400 && request.status < 500) {
bail(
new Error(
`Request to url "${url}" with method "${method}" and body "${JSON.stringify(
Expand Down

0 comments on commit 5563e3b

Please sign in to comment.