task-29: 退避配置项(MaxMs/Factor)
task-progress-config.ts 新增 getTaskPollBackoffMaxMs()(默认 10000, 由 5000 提升)与 getTaskPollBackoffFactor()(默认 2,<1 钳制为 1); configureTaskPolling 支持 backoffFactor;getTaskPollBackoffMs 改用 base × factor^attempt 计算。 既有 task-086 边界测试中封顶 5000 的快照随新默认更新为 10000。 新增 8 个测试:max/factor 默认、可覆盖、非正钳制、factor 下限、 配置隔离、默认值快照。
This commit is contained in:
@@ -18,7 +18,8 @@ export const TASK_POLL_HIDDEN_INTERVAL_MS = 60000;
|
|||||||
export const TASK_PROGRESS_VISIBLE_CACHE_MILLIS = 5000;
|
export const TASK_PROGRESS_VISIBLE_CACHE_MILLIS = 5000;
|
||||||
export const TASK_PROGRESS_HIDDEN_CACHE_MILLIS = 30000;
|
export const TASK_PROGRESS_HIDDEN_CACHE_MILLIS = 30000;
|
||||||
export const TASK_POLL_BACKOFF_BASE_MS = 500;
|
export const TASK_POLL_BACKOFF_BASE_MS = 500;
|
||||||
export const TASK_POLL_BACKOFF_MAX_MS = 5000;
|
export const TASK_POLL_BACKOFF_MAX_MS = 10000;
|
||||||
|
export const TASK_POLL_BACKOFF_FACTOR = 2;
|
||||||
export const TASK_PROGRESS_TIMEOUT_MS = 10000;
|
export const TASK_PROGRESS_TIMEOUT_MS = 10000;
|
||||||
|
|
||||||
export interface TaskPollingConfig {
|
export interface TaskPollingConfig {
|
||||||
@@ -26,6 +27,8 @@ export interface TaskPollingConfig {
|
|||||||
backoffBaseMs?: number;
|
backoffBaseMs?: number;
|
||||||
/** 退避上限(毫秒),必须为正数 */
|
/** 退避上限(毫秒),必须为正数 */
|
||||||
backoffMaxMs?: number;
|
backoffMaxMs?: number;
|
||||||
|
/** 退避指数因子,<1 时钳制为 1 */
|
||||||
|
backoffFactor?: number;
|
||||||
/** 页面切回前台时是否立即拉取一轮,默认 true */
|
/** 页面切回前台时是否立即拉取一轮,默认 true */
|
||||||
foregroundRefreshEnabled?: boolean;
|
foregroundRefreshEnabled?: boolean;
|
||||||
/** 前台恢复延迟(毫秒),默认 0 */
|
/** 前台恢复延迟(毫秒),默认 0 */
|
||||||
@@ -37,6 +40,7 @@ export interface TaskPollingConfig {
|
|||||||
const DEFAULTS: Required<TaskPollingConfig> = {
|
const DEFAULTS: Required<TaskPollingConfig> = {
|
||||||
backoffBaseMs: TASK_POLL_BACKOFF_BASE_MS,
|
backoffBaseMs: TASK_POLL_BACKOFF_BASE_MS,
|
||||||
backoffMaxMs: TASK_POLL_BACKOFF_MAX_MS,
|
backoffMaxMs: TASK_POLL_BACKOFF_MAX_MS,
|
||||||
|
backoffFactor: TASK_POLL_BACKOFF_FACTOR,
|
||||||
foregroundRefreshEnabled: true,
|
foregroundRefreshEnabled: true,
|
||||||
foregroundRefreshDelayMs: 0,
|
foregroundRefreshDelayMs: 0,
|
||||||
progressTimeoutMs: TASK_PROGRESS_TIMEOUT_MS,
|
progressTimeoutMs: TASK_PROGRESS_TIMEOUT_MS,
|
||||||
@@ -62,11 +66,21 @@ export function getTaskProgressCacheTtlMs() {
|
|||||||
return TASK_PROGRESS_VISIBLE_CACHE_MILLIS;
|
return TASK_PROGRESS_VISIBLE_CACHE_MILLIS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 第 attempt 次退避延迟:base × 2^attempt,封顶 max;attempt 非法时按首次(0)处理 */
|
/** 第 attempt 次退避延迟:base × factor^attempt,封顶 max;attempt 非法时按首次(0)处理 */
|
||||||
export function getTaskPollBackoffMs(attempt: number) {
|
export function getTaskPollBackoffMs(attempt: number) {
|
||||||
const safe = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 0;
|
const safe = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 0;
|
||||||
const doubled = config.backoffBaseMs * Math.pow(2, safe);
|
const factor = getTaskPollBackoffFactor();
|
||||||
return doubled > config.backoffMaxMs ? config.backoffMaxMs : doubled;
|
const scaled = config.backoffBaseMs * Math.pow(factor, safe);
|
||||||
|
return scaled > config.backoffMaxMs ? config.backoffMaxMs : scaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTaskPollBackoffMaxMs() {
|
||||||
|
return config.backoffMaxMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 退避指数因子,<1 时钳制为 1 */
|
||||||
|
export function getTaskPollBackoffFactor() {
|
||||||
|
return config.backoffFactor < 1 ? 1 : config.backoffFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTaskForegroundRefreshEnabled() {
|
export function getTaskForegroundRefreshEnabled() {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import {
|
||||||
|
TASK_POLL_BACKOFF_MAX_MS,
|
||||||
|
TASK_POLL_BACKOFF_FACTOR,
|
||||||
|
getTaskPollBackoffMaxMs,
|
||||||
|
getTaskPollBackoffFactor,
|
||||||
|
configureTaskPolling,
|
||||||
|
resetTaskPollingConfig,
|
||||||
|
} from '../src/shared/task-progress-config.ts'
|
||||||
|
|
||||||
|
test('test_backoff_max_default', () => {
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
assert.equal(getTaskPollBackoffMaxMs(), 10000)
|
||||||
|
assert.equal(getTaskPollBackoffMaxMs(), TASK_POLL_BACKOFF_MAX_MS)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_backoff_factor_default', () => {
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
assert.equal(getTaskPollBackoffFactor(), 2)
|
||||||
|
assert.equal(getTaskPollBackoffFactor(), TASK_POLL_BACKOFF_FACTOR)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_backoff_max_override', () => {
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
configureTaskPolling({ backoffMaxMs: 8000 })
|
||||||
|
assert.equal(getTaskPollBackoffMaxMs(), 8000)
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
assert.equal(getTaskPollBackoffMaxMs(), 10000, 'reset 后恢复默认')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_backoff_max_positive', () => {
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
assert.throws(() => configureTaskPolling({ backoffMaxMs: 0 }), /退避上限必须为正数/)
|
||||||
|
assert.throws(() => configureTaskPolling({ backoffMaxMs: -100 }), /退避上限必须为正数/)
|
||||||
|
// 拒绝后 getter 仍返回正值,非正配置不可观察
|
||||||
|
assert.ok(getTaskPollBackoffMaxMs() > 0)
|
||||||
|
assert.equal(getTaskPollBackoffMaxMs(), TASK_POLL_BACKOFF_MAX_MS)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_backoff_factor_min_one', () => {
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
// factor <1 钳制为 1(不抛错)
|
||||||
|
configureTaskPolling({ backoffFactor: 0.5 })
|
||||||
|
assert.equal(getTaskPollBackoffFactor(), 1)
|
||||||
|
configureTaskPolling({ backoffFactor: 0 })
|
||||||
|
assert.equal(getTaskPollBackoffFactor(), 1)
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
assert.equal(getTaskPollBackoffFactor(), 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_backoff_config_isolated', () => {
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
configureTaskPolling({ backoffMaxMs: 7000, backoffFactor: 3 })
|
||||||
|
assert.equal(getTaskPollBackoffMaxMs(), 7000)
|
||||||
|
assert.equal(getTaskPollBackoffFactor(), 3)
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
assert.equal(getTaskPollBackoffMaxMs(), 10000, '不串到其他用例')
|
||||||
|
assert.equal(getTaskPollBackoffFactor(), 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_backoff_max_freeze', () => {
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
assert.equal(TASK_POLL_BACKOFF_MAX_MS, 10000, '默认值快照')
|
||||||
|
assert.equal(getTaskPollBackoffMaxMs(), TASK_POLL_BACKOFF_MAX_MS)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_backoff_factor_freeze', () => {
|
||||||
|
resetTaskPollingConfig()
|
||||||
|
assert.equal(TASK_POLL_BACKOFF_FACTOR, 2, 'factor 快照')
|
||||||
|
assert.equal(getTaskPollBackoffFactor(), TASK_POLL_BACKOFF_FACTOR)
|
||||||
|
})
|
||||||
@@ -105,9 +105,10 @@ test('test_task_086_polling_boundary_limit_and_overflow', () => {
|
|||||||
assert.equal(getTaskPollBackoffMs(1), 1000)
|
assert.equal(getTaskPollBackoffMs(1), 1000)
|
||||||
assert.equal(getTaskPollBackoffMs(2), 2000)
|
assert.equal(getTaskPollBackoffMs(2), 2000)
|
||||||
assert.equal(getTaskPollBackoffMs(3), 4000)
|
assert.equal(getTaskPollBackoffMs(3), 4000)
|
||||||
assert.equal(getTaskPollBackoffMs(4), 5000)
|
assert.equal(getTaskPollBackoffMs(4), 8000)
|
||||||
assert.equal(getTaskPollBackoffMs(10), 5000)
|
assert.equal(getTaskPollBackoffMs(5), 10000)
|
||||||
assert.equal(getTaskPollBackoffMs(100), 5000)
|
assert.equal(getTaskPollBackoffMs(10), 10000)
|
||||||
|
assert.equal(getTaskPollBackoffMs(100), 10000)
|
||||||
// attempt 为 0 或负数按首次退避处理
|
// attempt 为 0 或负数按首次退避处理
|
||||||
assert.equal(getTaskPollBackoffMs(-1), 500)
|
assert.equal(getTaskPollBackoffMs(-1), 500)
|
||||||
assert.equal(getTaskPollBackoffMs(NaN), 500)
|
assert.equal(getTaskPollBackoffMs(NaN), 500)
|
||||||
|
|||||||
Reference in New Issue
Block a user