This PR fixes issues with flaky cancellation_empty_by test. The original test gated the runner's `waitFor: blocked` on `t1`'s `wait_for_cancel_once_async`, which had no causal relationship to `tracerSuggestion` having actually run inside the empty-`by` snapshot task. On CI under load the runner could trigger the insert before `tracerSuggestion` registered its `onSet` callback, leading to intermittent timeouts. Add a label-keyed `IO.Promise` registry (`syncPromisesRef`) plus `getSyncPromise` / `resolveSyncPromise` primitives and a `wait_for_sync <label>` tactic to `Lean.Server.Test.Cancel`. The empty-`by` test's `tracerSuggestion` now resolves a sync promise after registering its onSet, and `t1` waits on that promise before emitting `blocked`. The empty-`by` example must precede `t1` because `try?` inside the snapshot task synchronously waits on prior pending async theorem bodies during library search (likely a separate upstream issue); with that ordering the test is fully deterministic. `t1` also drops `wait_for_cancel_once_async` in favor of plain `trace "blocked"`. The test now also `dbg_trace`s at each sync point (`tracerSuggestion ready`, `sync received`, `cancelTokenSet`) so the .out.expected captures the deterministic execution sequence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
2.5 KiB
Text
73 lines
2.5 KiB
Text
import Lean.Server.Test.Cancel
|
|
import Lean.Elab.Tactic.Try
|
|
open Lean Lean.Meta Lean.Elab Lean.Elab.Term Lean.Elab.Tactic
|
|
open Lean.Server.Test.Cancel
|
|
|
|
/-!
|
|
Test that `cancelRec` reaches the snapshot task spawned by
|
|
`elabEmptyByAsTry` on re-elaboration.
|
|
|
|
Chronological flow:
|
|
1. Empty-`by` example elaborates; `elabEmptyByAsTry` spawns a snapshot
|
|
task with its own cancel token and returns.
|
|
2. The snapshot task's `try?` calls `tracerSuggestion`, which:
|
|
- registers test task `"cancelTokenSet"`,
|
|
- registers `cancelTk.onSet` to resolve it,
|
|
- resolves sync promise `"tracerSuggestion"`,
|
|
- returns `wait_for_test_task "cancelTokenSet"` as the candidate.
|
|
3. `try?` evaluates the candidate; it blocks on the test task.
|
|
4. `t1` elaborates: `wait_for_sync "tracerSuggestion"` returns
|
|
immediately, `trace "blocked"` emits the diagnostic.
|
|
5. Runner inserts `; skip`, triggers re-elab; `cancelRec` sets the
|
|
cancel token; `onSet` resolves the test task; the candidate's wait
|
|
returns; the snapshot task body completes.
|
|
|
|
Failure modes:
|
|
- `cancelTk? := none` to `Core.logSnapshotTask`: `cancelRec` cannot
|
|
reach the cancel token, the wait blocks, runner times out.
|
|
- `cancelTk? := none` to `wrapAsyncAsSnapshot`: `tracerSuggestion`
|
|
sees no cancel token, doesn't register `onSet`; the promise drops;
|
|
`wait_for_test_task` surfaces a `task dropped` diagnostic.
|
|
|
|
Ordering note: the empty-`by` example must precede `t1` because `try?`
|
|
inside the snapshot task library-searches the environment in a way
|
|
that synchronously waits on prior pending async theorem bodies. With
|
|
`t1` first, its `wait_for_sync` would block the snapshot's `try?`,
|
|
deadlocking. Likely a separate upstream issue.
|
|
-/
|
|
|
|
namespace TestEmptyBy
|
|
|
|
opaque UnsolvableProp : Prop
|
|
|
|
@[try_suggestion]
|
|
def tracerSuggestion (_goal : MVarId) (_info : Try.Info) :
|
|
MetaM (Array (TSyntax `tactic)) := do
|
|
if let some prom ← mkTestTask "cancelTokenSet" then
|
|
if let some cancelTk := (← readThe Core.Context).cancelTk? then
|
|
cancelTk.onSet (do
|
|
dbg_trace "cancelTokenSet"
|
|
prom.resolve ())
|
|
dbg_trace "tracerSuggestion ready"
|
|
resolveSyncPromise "tracerSuggestion"
|
|
return #[← `(tactic| wait_for_test_task "cancelTokenSet")]
|
|
|
|
end TestEmptyBy
|
|
|
|
set_option tactic.tryOnEmptyBy true
|
|
|
|
example : True := by
|
|
trivial
|
|
--^ waitFor: blocked
|
|
--^ insert: "; skip"
|
|
--^ sync
|
|
|
|
example : TestEmptyBy.UnsolvableProp := by
|
|
|
|
theorem t1 : True := by
|
|
wait_for_sync "tracerSuggestion"
|
|
dbg_trace "sync received"
|
|
trace "blocked"
|
|
trivial
|
|
|
|
|