Skip to content

Commit fb207da

Browse files
committed
fix #1857: throttle update requests for same site
1 parent 01e384b commit fb207da

File tree

3 files changed

+25
-7
lines changed

3 files changed

+25
-7
lines changed

src/background/update-manager.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import {API} from '/js/msg';
44
import * as prefs from '/js/prefs';
55
import {calcStyleDigest, styleSectionsEqual} from '/js/sections-util';
66
import {chromeLocal} from '/js/storage-util';
7-
import {extractUsoaId, isLocalhost, usoApi} from '/js/urls';
8-
import {debounce, deepMerge} from '/js/util';
7+
import {extractUsoaId, isCdnUrl, isLocalhost, usoApi} from '/js/urls';
8+
import {debounce, deepMerge, getHost, resolvedPromise, sleep} from '/js/util';
99
import {ignoreChromeError} from '/js/util-webext';
10-
import {bgBusy, safeTimeout} from './common';
10+
import {bgBusy} from './common';
1111
import {db} from './db';
1212
import download from './download';
1313
import * as styleMan from './style-manager';
@@ -27,6 +27,7 @@ const STATES = /** @namespace UpdaterStates */ {
2727
ERROR_VERSION: 'error: version is older than installed style',
2828
};
2929
export const getStates = () => STATES;
30+
const safeSleep = __.MV3 ? ms => __.KEEP_ALIVE(sleep(ms)) : sleep;
3031
const RH_ETAG = {responseHeaders: ['etag']}; // a hashsum of file contents
3132
const RX_DATE2VER = new RegExp([
3233
/^(\d{4})/,
@@ -41,6 +42,8 @@ const RETRY_ERRORS = [
4142
503, // service unavailable
4243
429, // too many requests
4344
];
45+
const HOST_THROTTLE = 1000; // ms
46+
const hostJobs = {};
4447
let lastUpdateTime;
4548
let checkingAll = false;
4649
let logQueue = [];
@@ -227,19 +230,28 @@ export async function checkStyle(opts) {
227230

228231
}
229232

230-
async function tryDownload(url, params, {retryDelay = 1000} = {}) {
233+
async function tryDownload(url, params, {retryDelay = HOST_THROTTLE} = {}) {
231234
while (true) {
235+
let host, job;
232236
try {
233237
params = deepMerge(params || {}, {headers: {'Cache-Control': 'no-cache'}});
234-
return await download(url, params);
238+
host = getHost(url);
239+
job = hostJobs[host];
240+
job = hostJobs[host] = (job
241+
? job.then(() => safeSleep(HOST_THROTTLE / (isCdnUrl(url) ? 4 : 1)))
242+
: resolvedPromise()
243+
).then(() => download(url, params));
244+
return await job;
235245
} catch (code) {
236246
if (!RETRY_ERRORS.includes(code) ||
237247
retryDelay > MIN_INTERVAL_MS) {
238-
return Promise.reject(code);
248+
throw code;
239249
}
250+
} finally {
251+
if (hostJobs[host] === job) delete hostJobs[host];
240252
}
241253
retryDelay *= 1.25;
242-
await new Promise(resolve => safeTimeout(resolve, retryDelay));
254+
await safeSleep(retryDelay);
243255
}
244256
}
245257

src/js/urls.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@ export const supported = /*@__PURE__*/ regExpTest.bind(new RegExp(
5959
export const isLocalhost = /*@__PURE__*/ regExpTest.bind(
6060
/^file:|^https?:\/\/([^/]+@)?(localhost|127\.0\.0\.1)(:\d+)?\//
6161
);
62+
63+
export const isCdnUrl = /*@__PURE__*/ regExpTest.bind(
64+
/^https:\/\/((\w+-)?cdn(js)?(-\w+)?\.[^/]+|[^/]+?\.github(usercontent)?\.(io|com))\//i
65+
);

src/js/util.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ export const clamp = (value, min, max) => value < min ? min : value > max ? max
99
export const clipString = (str, limit = 100) => str.length > limit
1010
? str.substr(0, limit) + '...'
1111
: str;
12+
export const getHost = url => url.split('/', 3)[2];
1213
export const hasOwn = /*@__PURE__*/Object.call.bind({}.hasOwnProperty);
1314
/** FYI, matchMedia's onchange doesn't work in bg context, so we use it in our content script */
1415
export const isCssDarkScheme = () => matchMedia('(prefers-color-scheme:dark)').matches;
1516
export const isObject = val => typeof val === 'object' && val;
17+
export const resolvedPromise = async val => val;
1618
export const sleep = ms => new Promise(ms > 0 ? cb => setTimeout(cb, ms) : setTimeout);
1719
export const stringAsRegExpStr = s => s.replace(/[{}()[\]\\.+*?^$|]/g, '\\$&');
1820
export const stringAsRegExp = (s, flags) => new RegExp(stringAsRegExpStr(s), flags);

0 commit comments

Comments
 (0)