-
Notifications
You must be signed in to change notification settings - Fork 354
refactor(scheduler): attempt 阶段机显式化——统一转移入口 + 单一出口清单 #1060
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MagicLizi
merged 7 commits into
makecindy:main
from
fico-hub:refactor/issue-1016-attempt-state-machine
Jul 31, 2026
Merged
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
8fb7ad8
refactor(scheduler): attempt 阶段机显式化——统一转移入口 + 单一出口清单
fico-hub b8007db
fix(scheduler): 强制收口后的迟到排队回调按 no-op 处理
fico-hub 96c9472
fix(maker-scheduler): 迟到 onTurnActive/onSessionBound 守卫与不变量补全
fico-hub 9df95c3
test(maker-scheduler): 去掉迟到排队回调用例中未使用的 sch 绑定
fico-hub 2982346
fix(maker-scheduler): stop() 一并清空 silencedRuns
fico-hub b59b144
fix(maker-scheduler): stop() 竞态下前置 await 恢复的 continuation 不再登记
fico-hub e57b26c
test(maker-scheduler): 修 stop 竞态用例的 mock 返回类型与 describe 作用域
fico-hub File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
54 changes: 54 additions & 0 deletions
54
packages/maker-scheduler/src/__tests__/attemptLifecycle.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| /** | ||
| * attemptLifecycle.test.ts — attempt 阶段机转移表(#1016)。 | ||
| * 穷举矩阵钉死:合法边恰为表列(含幂等重入),其余任意相对一律非法—— | ||
| * 转移表一旦被无意扩宽/收窄,这里立刻失败,而不是等运行期抛错或静默漏收口。 | ||
| */ | ||
|
|
||
| import { describe, expect, it } from 'vitest'; | ||
|
|
||
| import type { ScheduleRunPhase } from '../types.js'; | ||
| import { LEGAL_PHASE_TRANSITIONS, isLegalPhaseTransition } from '../engine/attemptLifecycle.js'; | ||
|
|
||
| const ALL_PHASES: readonly ScheduleRunPhase[] = [ | ||
| 'loading', | ||
| 'claiming', | ||
| 'persisting', | ||
| 'running', | ||
| 'queued', | ||
| 'cancelling', | ||
| 'finalizing', | ||
| ]; | ||
|
|
||
| describe('isLegalPhaseTransition', () => { | ||
| it('穷举矩阵:合法边 = 表列 ∪ 幂等重入,其余全部非法', () => { | ||
| for (const from of ALL_PHASES) { | ||
| for (const to of ALL_PHASES) { | ||
| const expected = from === to || LEGAL_PHASE_TRANSITIONS[from].includes(to); | ||
| expect(isLegalPhaseTransition(from, to), `${from} -> ${to}`).toBe(expected); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| it('关键语义边逐条钉死(防表被误改)', () => { | ||
| // 两个入口相只能进 persisting。 | ||
| expect(isLegalPhaseTransition('claiming', 'persisting')).toBe(true); | ||
| expect(isLegalPhaseTransition('loading', 'persisting')).toBe(true); | ||
| expect(isLegalPhaseTransition('claiming', 'running')).toBe(false); | ||
| expect(isLegalPhaseTransition('loading', 'running')).toBe(false); | ||
| // 排队往返与撤项。 | ||
| expect(isLegalPhaseTransition('running', 'queued')).toBe(true); | ||
| expect(isLegalPhaseTransition('queued', 'running')).toBe(true); | ||
| expect(isLegalPhaseTransition('queued', 'cancelling')).toBe(true); | ||
| expect(isLegalPhaseTransition('cancelling', 'queued')).toBe(false); | ||
| expect(isLegalPhaseTransition('cancelling', 'running')).toBe(false); | ||
| // 排队中被 interrupt:runner 直接抛错,不经过 endQueueWait。 | ||
| expect(isLegalPhaseTransition('queued', 'finalizing')).toBe(true); | ||
| // controller 注册后、running 置位前的守卫窗口。 | ||
| expect(isLegalPhaseTransition('persisting', 'finalizing')).toBe(true); | ||
| // finalizing 是吸收相:幂等重入放行,任何离开都非法。 | ||
| expect(isLegalPhaseTransition('finalizing', 'finalizing')).toBe(true); | ||
| for (const to of ALL_PHASES) { | ||
| if (to !== 'finalizing') expect(isLegalPhaseTransition('finalizing', to)).toBe(false); | ||
| } | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /** | ||
| * attemptLifecycle —— InflightAttempt 阶段机的合法转移表(纯逻辑,#1016)。 | ||
| * | ||
| * 背景:PR #944 的 review 里「某条出口分支漏做收口动作」同型缺陷出现了四次—— | ||
| * attempt 的生命周期有多个隐式出口,每个出口都靠手工记得做全套收口。本表把 | ||
| * 阶段转移显式化:所有 phase 写入统一走 Scheduler.transitionAttempt,非法转移 | ||
| * **抛错**而不是静默容忍(多数漏项的表现正是「静默少做一件事」)。 | ||
| * | ||
| * 表由现网全部 7 处写点穷举推导(fireOneInner / runNowInner / updateInflightAttempt / | ||
| * buildOnQueueWaitStart / buildEndQueueWait ×2 / forceReleaseStalledRun): | ||
| * | ||
| * claiming ──→ persisting ──→ running ⇄ queued ──→ cancelling | ||
| * loading ───↗ │ │ │ | ||
| * └────→ finalizing ←────┘ | ||
| * | ||
| * - 'claiming'(自动 fire)与 'loading'(runNow)是两个入口相,只能进 'persisting'; | ||
| * 认领失败 / 行读不到等早退不经过任何转移,直接删除(删除 = 任意相合法出口, | ||
| * 出口清单由 finishInflightAttempt 的单一出口统一执行)。 | ||
| * - 'finalizing' 是吸收相:强制收口与迟到 settle 会各自尝试置一次,幂等重入 | ||
| * (from === to)按 no-op 放行,其余任何离开 'finalizing' 的转移都非法。 | ||
| * - 'queued' → 'finalizing' 合法:排队中的 turn 被 interrupt 时 runner 直接抛错, | ||
| * 不经过 endQueueWait。 | ||
| * - 'persisting' → 'finalizing' 合法:controller 在 registerInflight 后、 | ||
| * 'running' 置位前的窗口内就可能被卡死守卫强制收口。 | ||
| */ | ||
|
|
||
| import type { ScheduleRunPhase } from '../types.js'; | ||
|
|
||
| export const LEGAL_PHASE_TRANSITIONS: Readonly< | ||
| Record<ScheduleRunPhase, readonly ScheduleRunPhase[]> | ||
| > = Object.freeze({ | ||
| claiming: ['persisting'], | ||
| loading: ['persisting'], | ||
| persisting: ['running', 'finalizing'], | ||
| running: ['queued', 'finalizing'], | ||
| queued: ['running', 'cancelling', 'finalizing'], | ||
| cancelling: ['finalizing'], | ||
| finalizing: [], | ||
| }); | ||
|
|
||
| /** 幂等重入(from === to)合法;其余按表判定。 */ | ||
| export function isLegalPhaseTransition(from: ScheduleRunPhase, to: ScheduleRunPhase): boolean { | ||
| if (from === to) return true; | ||
| return (LEGAL_PHASE_TRANSITIONS[from] ?? []).includes(to); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.