mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
fix: serialize goal progress accounting (#26155)
## Why Goal progress accounting can be reached from multiple completion paths for the same thread. Each path takes a progress snapshot, writes the usage delta, and then marks that snapshot as accounted. When two tool-completion hooks run at the same time, they can both observe the same unaccounted delta and charge it twice. ## What changed - Added a per-thread progress-accounting permit to `GoalAccountingState`. - Held that permit across the snapshot/write/mark-accounted critical section for active-turn, idle, and tool-finish accounting. - Added regression coverage for parallel tool-finish hooks so a shared token delta is charged once and only one progress event is emitted. ## Testing - Not run locally. - Added `parallel_tool_finish_accounts_active_goal_progress_once`.
This commit is contained in:
@@ -348,6 +348,10 @@ impl GoalRuntimeHandle {
|
||||
budget_limited_goal_disposition: BudgetLimitedGoalDisposition,
|
||||
) -> Result<Option<AccountedGoalProgress>, String> {
|
||||
let accounting = self.accounting_state();
|
||||
let _accounting_permit = accounting
|
||||
.progress_accounting_permit()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
let Some(snapshot) = accounting.progress_snapshot(turn_id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -398,6 +402,10 @@ impl GoalRuntimeHandle {
|
||||
budget_limited_goal_disposition: BudgetLimitedGoalDisposition,
|
||||
) -> Result<Option<AccountedGoalProgress>, String> {
|
||||
let accounting = self.accounting_state();
|
||||
let _accounting_permit = accounting
|
||||
.progress_accounting_permit()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
let Some(snapshot) = accounting.idle_progress_snapshot() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user