mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
fix(core): Add timeout to external secrets provider refresh (#29679)
This commit is contained in:
@@ -2,6 +2,7 @@ import { mockLogger } from '@n8n/backend-test-utils';
|
||||
|
||||
import { AnotherDummyProvider, DummyProvider } from '@test/external-secrets/utils';
|
||||
|
||||
import { EXTERNAL_SECRETS_REFRESH_TIMEOUT_MS } from '../constants';
|
||||
import { ExternalSecretsProviderRegistry } from '../provider-registry.service';
|
||||
import { ExternalSecretsSecretsCache } from '../secrets-cache.service';
|
||||
|
||||
@@ -53,6 +54,22 @@ describe('SecretsCache', () => {
|
||||
|
||||
await expect(cache.refreshProvider('dummy', dummyProvider)).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should not hang when update exceeds refresh timeout', async () => {
|
||||
jest.useFakeTimers();
|
||||
try {
|
||||
jest
|
||||
.spyOn(dummyProvider, 'update')
|
||||
.mockImplementation(async () => await new Promise(() => {}));
|
||||
|
||||
const refreshPromise = cache.refreshProvider('dummy', dummyProvider);
|
||||
await jest.advanceTimersByTimeAsync(EXTERNAL_SECRETS_REFRESH_TIMEOUT_MS);
|
||||
|
||||
await expect(refreshPromise).resolves.toBeUndefined();
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshAll', () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { INodeProperties } from 'n8n-workflow';
|
||||
export const EXTERNAL_SECRETS_DB_KEY = 'feature.externalSecrets';
|
||||
export const EXTERNAL_SECRETS_INITIAL_BACKOFF = 10 * 1000;
|
||||
export const EXTERNAL_SECRETS_MAX_BACKOFF = 5 * 60 * 1000;
|
||||
export const EXTERNAL_SECRETS_REFRESH_TIMEOUT_MS = 20_000;
|
||||
|
||||
export const DOCS_HELP_NOTICE: INodeProperties = {
|
||||
displayName:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Logger } from '@n8n/backend-common';
|
||||
import { Service } from '@n8n/di';
|
||||
import { ensureError } from 'n8n-workflow';
|
||||
import { ensureError, OperationalError } from 'n8n-workflow';
|
||||
|
||||
import { EXTERNAL_SECRETS_REFRESH_TIMEOUT_MS } from './constants';
|
||||
import { ExternalSecretsProviderRegistry } from './provider-registry.service';
|
||||
import type { SecretsProvider } from './types';
|
||||
|
||||
@@ -41,7 +42,7 @@ export class ExternalSecretsSecretsCache {
|
||||
}
|
||||
|
||||
try {
|
||||
await provider.update();
|
||||
await this.refreshProviderWithTimeout(provider);
|
||||
this.logger.debug(`Refreshed secrets from provider ${name}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error refreshing secrets from provider ${name}`, {
|
||||
@@ -50,6 +51,27 @@ export class ExternalSecretsSecretsCache {
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshProviderWithTimeout(provider: SecretsProvider): Promise<void> {
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
reject(
|
||||
new OperationalError(
|
||||
`Timed out refreshing secrets after ${EXTERNAL_SECRETS_REFRESH_TIMEOUT_MS}ms`,
|
||||
),
|
||||
);
|
||||
}, EXTERNAL_SECRETS_REFRESH_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.race([provider.update(), timeoutPromise]);
|
||||
} finally {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a secret from a specific provider
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user