Skip to content

Commit 8a7c921

Browse files
updated retry
1 parent c5caafe commit 8a7c921

File tree

2 files changed

+116
-138
lines changed

2 files changed

+116
-138
lines changed

dist/setup/index.js

Lines changed: 43 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -97291,6 +97291,33 @@ const fs_1 = __importDefault(__nccwpck_require__(9896));
9729197291
const utils_1 = __nccwpck_require__(1798);
9729297292
const TOKEN = core.getInput('token');
9729397293
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
97294+
/*
97295+
* Generic retry wrapper
97296+
*/
97297+
async function retry(fn, retries = 3, delayMs = 2000) {
97298+
let lastErr;
97299+
for (let attempt = 1; attempt <= retries; attempt++) {
97300+
try {
97301+
return await fn();
97302+
}
97303+
catch (err) {
97304+
lastErr = err;
97305+
const status = err?.statusCode ||
97306+
err?.httpStatusCode ||
97307+
err?.response?.message?.statusCode;
97308+
const retryable = !status || status >= 500 || status === 429 || status === 403;
97309+
core.warning(`Attempt ${attempt} failed: ${err.message}. ` +
97310+
(retryable && attempt < retries
97311+
? `Retrying in ${delayMs}ms...`
97312+
: `No more retries.`));
97313+
if (!retryable || attempt === retries)
97314+
break;
97315+
await new Promise(res => setTimeout(res, delayMs));
97316+
delayMs *= 2; // exponential backoff
97317+
}
97318+
}
97319+
throw lastErr;
97320+
}
9729497321
async function installGraalPy(graalpyVersion, architecture, allowPreReleases, releases) {
9729597322
let downloadDir;
9729697323
releases = releases ?? (await getAvailableGraalPyVersions());
@@ -97312,15 +97339,15 @@ async function installGraalPy(graalpyVersion, architecture, allowPreReleases, re
9731297339
const downloadUrl = `${foundAsset.browser_download_url}`;
9731397340
core.info(`Downloading GraalPy from "${downloadUrl}" ...`);
9731497341
try {
97315-
const graalpyPath = await tc.downloadTool(downloadUrl, undefined, AUTH);
97342+
// ⭐ Wrapped in retry
97343+
const graalpyPath = await retry(() => tc.downloadTool(downloadUrl, undefined, AUTH), 4, 2000);
9731697344
core.info('Extracting downloaded archive...');
9731797345
if (utils_1.IS_WINDOWS) {
9731897346
downloadDir = await tc.extractZip(graalpyPath);
9731997347
}
9732097348
else {
9732197349
downloadDir = await tc.extractTar(graalpyPath);
9732297350
}
97323-
// folder name in archive is unpredictable
9732497351
const archiveName = fs_1.default.readdirSync(downloadDir)[0];
9732597352
const toolDir = path.join(downloadDir, archiveName);
9732697353
let installDir = toolDir;
@@ -97334,54 +97361,16 @@ async function installGraalPy(graalpyVersion, architecture, allowPreReleases, re
9733497361
}
9733597362
catch (err) {
9733697363
if (err instanceof Error) {
97337-
const isRateLimit = err instanceof tc.HTTPError &&
97338-
(err.httpStatusCode === 403 || err.httpStatusCode === 429);
97339-
if (isRateLimit) {
97340-
core.warning(`Rate limit or restricted access response received: HTTP ${err.httpStatusCode}`);
97341-
let lastStatus;
97342-
for (let attempt = 1; attempt <= 3; attempt++) {
97343-
core.info(`Retry attempt ${attempt} of 3 due to rate limit...`);
97344-
await new Promise(res => setTimeout(res, 2000 * attempt));
97345-
try {
97346-
const retryPath = await tc.downloadTool(downloadUrl, undefined, AUTH);
97347-
core.info(`Retry succeeded.`);
97348-
// Extract retry archive
97349-
let retryExtractDir;
97350-
if (utils_1.IS_WINDOWS) {
97351-
retryExtractDir = await tc.extractZip(retryPath);
97352-
}
97353-
else {
97354-
retryExtractDir = await tc.extractTar(retryPath);
97355-
}
97356-
const archiveName = fs_1.default.readdirSync(retryExtractDir)[0];
97357-
const toolDir = path.join(retryExtractDir, archiveName);
97358-
let installDir = toolDir;
97359-
if (!(0, utils_1.isNightlyKeyword)(resolvedGraalPyVersion)) {
97360-
installDir = await tc.cacheDir(toolDir, 'GraalPy', resolvedGraalPyVersion, architecture);
97361-
}
97362-
const binaryPath = path.join(installDir, 'bin');
97363-
await createGraalPySymlink(binaryPath, resolvedGraalPyVersion);
97364-
await installPip(binaryPath);
97365-
return { installDir, resolvedGraalPyVersion };
97366-
}
97367-
catch (retryErr) {
97368-
if (retryErr instanceof tc.HTTPError) {
97369-
lastStatus = retryErr.httpStatusCode;
97370-
core.warning(`Retry ${attempt} failed. HTTP ${lastStatus}`);
97371-
}
97372-
else {
97373-
core.warning(`Retry ${attempt} failed: ${retryErr}`);
97374-
}
97375-
if (attempt === 3) {
97376-
core.error(`All retries failed. Last HTTP status code: ${lastStatus ?? 'unknown'}`);
97377-
throw retryErr;
97378-
}
97379-
}
97380-
}
97364+
if (err instanceof tc.HTTPError &&
97365+
(err.httpStatusCode === 403 || err.httpStatusCode === 429)) {
97366+
core.info(`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`);
9738197367
}
97382-
core.info(err.message);
97383-
if (err.stack)
97368+
else {
97369+
core.info(err.message);
97370+
}
97371+
if (err.stack !== undefined) {
9738497372
core.debug(err.stack);
97373+
}
9738597374
}
9738697375
throw err;
9738797376
}
@@ -97393,25 +97382,25 @@ async function getAvailableGraalPyVersions() {
9739397382
headers.authorization = AUTH;
9739497383
}
9739597384
/*
97396-
Get releases first.
97397-
*/
97385+
* Stable releases with retry
97386+
*/
9739897387
let url = 'https://api.github.com/repos/oracle/graalpython/releases';
9739997388
const result = [];
9740097389
do {
97401-
const response = await http.getJson(url, headers);
97390+
const response = await retry(() => http.getJson(url, headers), 4, 1500);
9740297391
if (!response.result) {
9740397392
throw new Error(`Unable to retrieve the list of available GraalPy versions from '${url}'`);
9740497393
}
9740597394
result.push(...response.result);
9740697395
url = (0, utils_1.getNextPageUrl)(response);
9740797396
} while (url);
9740897397
/*
97409-
Add pre-release builds.
97410-
*/
97398+
* Pre-release builds with retry
97399+
*/
9741197400
url =
9741297401
'https://api.github.com/repos/graalvm/graal-languages-ea-builds/releases';
9741397402
do {
97414-
const response = await http.getJson(url, headers);
97403+
const response = await retry(() => http.getJson(url, headers), 4, 1500);
9741597404
if (!response.result) {
9741697405
throw new Error(`Unable to retrieve the list of available GraalPy versions from '${url}'`);
9741797406
}
@@ -97490,9 +97479,6 @@ function findAsset(item, architecture, platform) {
9749097479
const graalpyExt = platform == 'win32' ? 'zip' : 'tar.gz';
9749197480
const found = item.assets.filter(file => file.name.startsWith('graalpy') &&
9749297481
file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.${graalpyExt}`));
97493-
/*
97494-
In the future there could be more variants of GraalPy for a single release. Pick the shortest name, that one is the most likely to be the primary variant.
97495-
*/
9749697482
found.sort((f1, f2) => f1.name.length - f2.name.length);
9749797483
return found[0];
9749897484
}

src/install-graalpy.ts

Lines changed: 73 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@ import {
2121
const TOKEN = core.getInput('token');
2222
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
2323

24+
/*
25+
* Generic retry wrapper
26+
*/
27+
async function retry<T>(
28+
fn: () => Promise<T>,
29+
retries = 3,
30+
delayMs = 2000
31+
): Promise<T> {
32+
let lastErr;
33+
for (let attempt = 1; attempt <= retries; attempt++) {
34+
try {
35+
return await fn();
36+
} catch (err: any) {
37+
lastErr = err;
38+
39+
const status =
40+
err?.statusCode ||
41+
err?.httpStatusCode ||
42+
err?.response?.message?.statusCode;
43+
44+
const retryable =
45+
!status || status >= 500 || status === 429 || status === 403;
46+
47+
core.warning(
48+
`Attempt ${attempt} failed: ${err.message}. ` +
49+
(retryable && attempt < retries
50+
? `Retrying in ${delayMs}ms...`
51+
: `No more retries.`)
52+
);
53+
54+
if (!retryable || attempt === retries) break;
55+
56+
await new Promise(res => setTimeout(res, delayMs));
57+
delayMs *= 2; // exponential backoff
58+
}
59+
}
60+
throw lastErr;
61+
}
62+
2463
export async function installGraalPy(
2564
graalpyVersion: string,
2665
architecture: string,
@@ -59,7 +98,12 @@ export async function installGraalPy(
5998
core.info(`Downloading GraalPy from "${downloadUrl}" ...`);
6099

61100
try {
62-
const graalpyPath = await tc.downloadTool(downloadUrl, undefined, AUTH);
101+
// ⭐ Wrapped in retry
102+
const graalpyPath = await retry(
103+
() => tc.downloadTool(downloadUrl, undefined, AUTH),
104+
4,
105+
2000
106+
);
63107

64108
core.info('Extracting downloaded archive...');
65109
if (IS_WINDOWS) {
@@ -68,11 +112,10 @@ export async function installGraalPy(
68112
downloadDir = await tc.extractTar(graalpyPath);
69113
}
70114

71-
// folder name in archive is unpredictable
72115
const archiveName = fs.readdirSync(downloadDir)[0];
116+
73117
const toolDir = path.join(downloadDir, archiveName);
74118
let installDir = toolDir;
75-
76119
if (!isNightlyKeyword(resolvedGraalPyVersion)) {
77120
installDir = await tc.cacheDir(
78121
toolDir,
@@ -89,77 +132,20 @@ export async function installGraalPy(
89132
return {installDir, resolvedGraalPyVersion};
90133
} catch (err) {
91134
if (err instanceof Error) {
92-
const isRateLimit =
135+
if (
93136
err instanceof tc.HTTPError &&
94-
(err.httpStatusCode === 403 || err.httpStatusCode === 429);
95-
96-
if (isRateLimit) {
97-
core.warning(
98-
`Rate limit or restricted access response received: HTTP ${err.httpStatusCode}`
137+
(err.httpStatusCode === 403 || err.httpStatusCode === 429)
138+
) {
139+
core.info(
140+
`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`
99141
);
100-
101-
let lastStatus: number | undefined;
102-
103-
for (let attempt = 1; attempt <= 3; attempt++) {
104-
core.info(`Retry attempt ${attempt} of 3 due to rate limit...`);
105-
await new Promise(res => setTimeout(res, 2000 * attempt));
106-
107-
try {
108-
const retryPath = await tc.downloadTool(
109-
downloadUrl,
110-
undefined,
111-
AUTH
112-
);
113-
core.info(`Retry succeeded.`);
114-
115-
// Extract retry archive
116-
let retryExtractDir;
117-
if (IS_WINDOWS) {
118-
retryExtractDir = await tc.extractZip(retryPath);
119-
} else {
120-
retryExtractDir = await tc.extractTar(retryPath);
121-
}
122-
123-
const archiveName = fs.readdirSync(retryExtractDir)[0];
124-
const toolDir = path.join(retryExtractDir, archiveName);
125-
let installDir = toolDir;
126-
127-
if (!isNightlyKeyword(resolvedGraalPyVersion)) {
128-
installDir = await tc.cacheDir(
129-
toolDir,
130-
'GraalPy',
131-
resolvedGraalPyVersion,
132-
architecture
133-
);
134-
}
135-
136-
const binaryPath = path.join(installDir, 'bin');
137-
await createGraalPySymlink(binaryPath, resolvedGraalPyVersion);
138-
await installPip(binaryPath);
139-
140-
return {installDir, resolvedGraalPyVersion};
141-
} catch (retryErr) {
142-
if (retryErr instanceof tc.HTTPError) {
143-
lastStatus = retryErr.httpStatusCode;
144-
core.warning(`Retry ${attempt} failed. HTTP ${lastStatus}`);
145-
} else {
146-
core.warning(`Retry ${attempt} failed: ${retryErr}`);
147-
}
148-
149-
if (attempt === 3) {
150-
core.error(
151-
`All retries failed. Last HTTP status code: ${lastStatus ?? 'unknown'}`
152-
);
153-
throw retryErr;
154-
}
155-
}
156-
}
142+
} else {
143+
core.info(err.message);
144+
}
145+
if (err.stack !== undefined) {
146+
core.debug(err.stack);
157147
}
158-
159-
core.info(err.message);
160-
if (err.stack) core.debug(err.stack);
161148
}
162-
163149
throw err;
164150
}
165151
}
@@ -173,14 +159,18 @@ export async function getAvailableGraalPyVersions() {
173159
}
174160

175161
/*
176-
Get releases first.
177-
*/
162+
* Stable releases with retry
163+
*/
178164
let url: string | null =
179165
'https://api.github.com/repos/oracle/graalpython/releases';
180166
const result: IGraalPyManifestRelease[] = [];
181167
do {
182-
const response: ifm.TypedResponse<IGraalPyManifestRelease[]> =
183-
await http.getJson(url, headers);
168+
const response: ifm.TypedResponse<IGraalPyManifestRelease[]> = await retry(
169+
() => http.getJson(url!, headers),
170+
4,
171+
1500
172+
);
173+
184174
if (!response.result) {
185175
throw new Error(
186176
`Unable to retrieve the list of available GraalPy versions from '${url}'`
@@ -191,13 +181,17 @@ export async function getAvailableGraalPyVersions() {
191181
} while (url);
192182

193183
/*
194-
Add pre-release builds.
195-
*/
184+
* Pre-release builds with retry
185+
*/
196186
url =
197187
'https://api.github.com/repos/graalvm/graal-languages-ea-builds/releases';
198188
do {
199-
const response: ifm.TypedResponse<IGraalPyManifestRelease[]> =
200-
await http.getJson(url, headers);
189+
const response: ifm.TypedResponse<IGraalPyManifestRelease[]> = await retry(
190+
() => http.getJson(url!, headers),
191+
4,
192+
1500
193+
);
194+
201195
if (!response.result) {
202196
throw new Error(
203197
`Unable to retrieve the list of available GraalPy versions from '${url}'`
@@ -335,9 +329,7 @@ export function findAsset(
335329
file.name.startsWith('graalpy') &&
336330
file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.${graalpyExt}`)
337331
);
338-
/*
339-
In the future there could be more variants of GraalPy for a single release. Pick the shortest name, that one is the most likely to be the primary variant.
340-
*/
332+
341333
found.sort((f1, f2) => f1.name.length - f2.name.length);
342334
return found[0];
343335
}

0 commit comments

Comments
 (0)