Skip to content

Commit 0607aed

Browse files
committed
feat(musicbrainz): Handle rate limiting within api calls
Instead of using staggerMap with MB transformer settings, handle at api call level for each host
1 parent 1f16509 commit 0607aed

File tree

3 files changed

+30
-7
lines changed

3 files changed

+30
-7
lines changed

src/backend/common/infrastructure/Atomic.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ export interface CacheConfigOptions {
291291

292292
export interface MusicbrainzApiConfigData {
293293
url?: string
294-
rateLimit?: [number, number]
295294
contact: string,
296295
apiKey?: string
297296
requestTimeout?: number

src/backend/common/transforms/MusicbrainzTransformer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,8 @@ export default class MusicbrainzTransformer extends AtomicPartsTransformer<Exter
356356
super(config, options);
357357
this.clientCache = options.clientCache;
358358
this.staggerOpts = {
359-
initialInterval: 300,
360-
maxRandomStagger: 300
359+
initialInterval: 0,
360+
maxRandomStagger: 100
361361
}
362362
}
363363

src/backend/common/vendor/musicbrainz/MusicbrainzApiClient.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import { nanoid } from "nanoid";
1818
import { stripIndents } from "common-tags";
1919
import { getNodeNetworkException, hasNodeNetworkException, isNodeNetworkException } from '../../errors/NodeErrors.js';
2020
import { SimpleError } from '../../errors/MSErrors.js';
21-
import { resourceLimits } from 'worker_threads';
2221
import { baseFormatPlayObj } from '../../../utils/PlayTransformUtils.js';
2322
import { IRecordingMSList } from '../../transforms/MusicbrainzTransformer.js';
23+
import dayjs, { Dayjs } from 'dayjs';
2424

2525
export interface SubmitResponse {
2626
payload?: {
@@ -33,6 +33,8 @@ export interface SubmitResponse {
3333
export interface MusicbrainzApiConfig extends MusicbrainzApiConfigData {
3434
api: MusicBrainzApi,
3535
hostname: string
36+
minRequestIntervalDuration: number
37+
lastRequest: Dayjs
3638
}
3739

3840
export interface MusicbrainzApiClientConfig {
@@ -66,13 +68,18 @@ export class MusicbrainzApiClient extends AbstractApiClient {
6668
for(const mbConfig of this.config.apis) {
6769
const u = normalizeWebAddress(mbConfig.url ?? MUSICBRAINZ_URL);
6870
let mb = mbMap.get(u.url.hostname);
71+
const mbApiConfig: Omit<MusicbrainzApiConfig, 'api'> = {
72+
...mbConfig,
73+
hostname: u.url.hostname,
74+
minRequestIntervalDuration: 1000,
75+
lastRequest: dayjs().subtract(1000 + 1000, 'ms')
76+
}
6977
if(mb === undefined) {
7078
const api = new MusicBrainzApi({
7179
appName: 'multi-scrobbler',
7280
appVersion: version,
7381
appContactInfo: mbConfig.contact,
7482
baseUrl: u.url.toString(),
75-
rateLimit: mbConfig.rateLimit ?? [1,1],
7683
preRequest: options.logUrl === true || isDebugMode() ? (method, url, headers) => {
7784
const cacheKey = this.asyncStore.getStore() ?? nanoid();
7885
this.cache.set(`${cacheKey}-url`, `${method} - ${url}`);
@@ -84,11 +91,17 @@ export class MusicbrainzApiClient extends AbstractApiClient {
8491
requestTimeout: mbConfig.requestTimeout ?? 6000,
8592
retryLimit: 2
8693
});
87-
mbApis[u.url.hostname] = {api, ...mbConfig, hostname: u.url.hostname};
94+
mbApis[u.url.hostname] = {
95+
...mbApiConfig,
96+
api,
97+
};
8898
mbMap.set(u.url.hostname, api);
8999
mb = api;
90100
} else if(mbApis[u.url.hostname] === undefined) {
91-
mbApis[u.url.hostname] = {api: mb, ...mbConfig, hostname: u.url.hostname};
101+
mbApis[u.url.hostname] = {
102+
...mbApiConfig,
103+
api: mb,
104+
};
92105
}
93106
}
94107

@@ -127,6 +140,17 @@ export class MusicbrainzApiClient extends AbstractApiClient {
127140
const triedHosts: string[] = [];
128141
while(!triedHosts.includes(apiConfig.hostname)) {
129142

143+
// keep track of last request init at and wait until at least 1 second since that
144+
// to help prevent rate limiting
145+
let waitTime = 0;
146+
const sinceLast = dayjs().diff(apiConfig.lastRequest, 'ms');
147+
waitTime = Math.max(0, apiConfig.minRequestIntervalDuration - sinceLast);
148+
apiConfig.lastRequest = dayjs().add(waitTime, 'ms');
149+
//this.logger.trace(`Waiting ${waitTime}ms to call ${apiConfig.hostname} request at ${apiConfig.lastRequest.toISOString()}`)
150+
if(waitTime > 0) {
151+
await sleep(waitTime);
152+
}
153+
130154
try {
131155
const res = await this.callApiEndpoint(apiConfig.api, func, options);
132156
if(cacheKey !== undefined) {

0 commit comments

Comments
 (0)