Zulip Chat Archive

Stream: lean4

Topic: Working with IO.CancelToken


Eric Wieser (Sep 10 2024 at 14:50):

Here's some code that ought to cancel a tactic after a certain duration:

import Lean

set_option linter.all false

open Lean Elab Tactic

/-! versions of try/catch that catch `interrupted` too  -/
section catch_interrupted
attribute [-instance]
  Lean.instMonadExceptOfExceptionCoreM Lean.Elab.Tactic.instMonadExceptExceptionTacticM
variable {α}

def Meta.tryCatchAll (m : MetaM α) (h : Exception  MetaM α) : MetaM α := tryCatch m h
def Term.tryCatchAll (m : TermElabM α) (h : Exception  TermElabM α) : TermElabM α := tryCatch m h
def Tactic.tryCatchAll (x : TacticM α) (h : Exception  TacticM α) : TacticM α := do
  let b  saveState
  try x catch ex => b.restore; h ex

end catch_interrupted

elab "with_timeout " ms:num "=>" tac:tacticSeq : tactic => do
  let ms := ms.getNat.toUInt32
  let tk  IO.CancelToken.new
  let t  withTheReader Core.Context (fun s => { s with cancelTk? := some tk }) do
    let t  IO.asTask do
      IO.println "Task is running"
      IO.sleep ms
      tk.set
      IO.println "Task is cancelled"
    Tactic.tryCatchAll
      (evalTactic tac)
      (fun e =>
        Lean.logInfo e.toMessageData)
      return t
  IO.cancel t

theorem foo : True := by
  with_timeout 500 =>
    sleep 1000
    run_tac Lean.logInfo "I wasn't cancelled!"
    trivial

theorem bar : True := by
  with_timeout 500 =>
    sleep 100
    run_tac Lean.logInfo "I wasn't cancelled!"
    trivial

#print foo
#print bar

This works when run with lake lean this_file.lean, but in vscode the server stops responding. Is this use of a custom cancel token legal?

Sebastian Ullrich (Sep 10 2024 at 15:14):

You're writing invalid data to the language server output. Try IO.eprintln

Sebastian Ullrich (Sep 10 2024 at 15:41):

(We redirect stdout writes during elaboration to diagnostics but it's not clear how to do that in arbitrary tasks. Perhaps we should at least redirect them to stderr.)

Eric Wieser (Sep 10 2024 at 15:45):

Can I use control to somehow stay in CoreM in the task?

Sebastian Ullrich (Sep 10 2024 at 16:50):

I'm in the process of developing API for this in context of parallel elaboration, but here is a sneak peek that may already be helpful

/-- Runs the given action in a separate task, discarding its final state. -/
def runAsync (act : TermElabM α) : TermElabM (Task (Except Exception α)) := do
  let mut coreSt  getThe Core.State
  let metaSt  getThe Meta.State
  let st  get
  let coreCtx  readThe Core.Context
  let metaCtx  readThe Meta.Context
  let ctx  read
  let heartbeats := ( IO.getNumHeartbeats) - coreCtx.initHeartbeats
  withCurrHeartbeats (do
      IO.addHeartbeats heartbeats.toUInt64
      act : TermElabM _)
    |>.run' ctx st
    |>.run' metaCtx metaSt
    |>.run' coreCtx coreSt
    |> EIO.asTask

Eric Wieser (Sep 10 2024 at 17:19):

I assume

def runAsync' (act : TermElabM α) : TermElabM (Task (Except Exception α)) := do
  controlAt (EIO Exception) fun run => EIO.asTask (run act)

doesn't behave correctly?

Sebastian Ullrich (Sep 10 2024 at 17:41):

I believe it doesn't because it would share the state IO.Ref across threads, sounds like a potential source of extremely fun bugs!

Leni Aniva (Sep 10 2024 at 18:06):

so does this work?


Last updated: May 02 2025 at 03:31 UTC