未验证 提交 4cc8ca3f 编写于 作者: B Brett V. Forsgren 提交者: GitHub

allow fsi evaluations to be cancelled (#7736)

上级 e6309b0b
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace FSharp.Compiler.Scripting namespace FSharp.Compiler.Scripting
open System open System
open System.Threading
open FSharp.Compiler.Interactive.Shell open FSharp.Compiler.Interactive.Shell
type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: string[]) as this = type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: string[]) as this =
...@@ -42,8 +43,9 @@ type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: st ...@@ -42,8 +43,9 @@ type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: st
member __.ErrorProduced = errorProduced.Publish member __.ErrorProduced = errorProduced.Publish
member __.Eval(code: string) = member __.Eval(code: string, ?cancellationToken: CancellationToken) =
let ch, errors = fsi.EvalInteractionNonThrowing code let cancellationToken = defaultArg cancellationToken CancellationToken.None
let ch, errors = fsi.EvalInteractionNonThrowing(code, cancellationToken)
match ch with match ch with
| Choice1Of2 v -> Ok(v), errors | Choice1Of2 v -> Ok(v), errors
| Choice2Of2 ex -> Error(ex), errors | Choice2Of2 ex -> Error(ex), errors
......
...@@ -1888,7 +1888,8 @@ type internal FsiInteractionProcessor ...@@ -1888,7 +1888,8 @@ type internal FsiInteractionProcessor
/// ///
/// #directive comes through with other definitions as a SynModuleDecl.HashDirective. /// #directive comes through with other definitions as a SynModuleDecl.HashDirective.
/// We split these out for individual processing. /// We split these out for individual processing.
let rec execParsedInteractions (ctok, tcConfig, istate, action, errorLogger: ErrorLogger, lastResult:option<FsiInteractionStepStatus>) = let rec execParsedInteractions (ctok, tcConfig, istate, action, errorLogger: ErrorLogger, lastResult:option<FsiInteractionStepStatus>, cancellationToken: CancellationToken) =
cancellationToken.ThrowIfCancellationRequested()
let action,nextAction,istate = let action,nextAction,istate =
match action with match action with
| None -> None,None,istate | None -> None,None,istate
...@@ -1935,7 +1936,7 @@ type internal FsiInteractionProcessor ...@@ -1935,7 +1936,7 @@ type internal FsiInteractionProcessor
| Some action, _ -> | Some action, _ ->
let istate,cont = ExecInteraction (ctok, tcConfig, istate, action, errorLogger) let istate,cont = ExecInteraction (ctok, tcConfig, istate, action, errorLogger)
match cont with match cont with
| Completed _ -> execParsedInteractions (ctok, tcConfig, istate, nextAction, errorLogger, Some cont) | Completed _ -> execParsedInteractions (ctok, tcConfig, istate, nextAction, errorLogger, Some cont, cancellationToken)
| CompletedWithReportedError e -> istate,CompletedWithReportedError e (* drop nextAction on error *) | CompletedWithReportedError e -> istate,CompletedWithReportedError e (* drop nextAction on error *)
| EndOfFile -> istate,defaultArg lastResult (Completed None) (* drop nextAction on EOF *) | EndOfFile -> istate,defaultArg lastResult (Completed None) (* drop nextAction on EOF *)
| CtrlC -> istate,CtrlC (* drop nextAction on CtrlC *) | CtrlC -> istate,CtrlC (* drop nextAction on CtrlC *)
...@@ -1962,9 +1963,9 @@ type internal FsiInteractionProcessor ...@@ -1962,9 +1963,9 @@ type internal FsiInteractionProcessor
stopProcessingRecovery e range0; stopProcessingRecovery e range0;
istate, CompletedWithReportedError e istate, CompletedWithReportedError e
let mainThreadProcessParsedInteractions ctok errorLogger (action, istate) = let mainThreadProcessParsedInteractions ctok errorLogger (action, istate) cancellationToken =
istate |> mainThreadProcessAction ctok (fun ctok tcConfig istate -> istate |> mainThreadProcessAction ctok (fun ctok tcConfig istate ->
execParsedInteractions (ctok, tcConfig, istate, action, errorLogger, None)) execParsedInteractions (ctok, tcConfig, istate, action, errorLogger, None, cancellationToken))
let parseExpression (tokenizer:LexFilter.LexFilter) = let parseExpression (tokenizer:LexFilter.LexFilter) =
reusingLexbufForParsing tokenizer.LexBuffer (fun () -> reusingLexbufForParsing tokenizer.LexBuffer (fun () ->
...@@ -1997,8 +1998,8 @@ type internal FsiInteractionProcessor ...@@ -1997,8 +1998,8 @@ type internal FsiInteractionProcessor
/// During processing of startup scripts, this runs on the main thread. /// During processing of startup scripts, this runs on the main thread.
/// ///
/// This is blocking: it reads until one chunk of input have been received, unless IsPastEndOfStream is true /// This is blocking: it reads until one chunk of input have been received, unless IsPastEndOfStream is true
member __.ParseAndExecOneSetOfInteractionsFromLexbuf (runCodeOnMainThread, istate:FsiDynamicCompilerState, tokenizer:LexFilter.LexFilter, errorLogger) = member __.ParseAndExecOneSetOfInteractionsFromLexbuf (runCodeOnMainThread, istate:FsiDynamicCompilerState, tokenizer:LexFilter.LexFilter, errorLogger, ?cancellationToken: CancellationToken) =
let cancellationToken = defaultArg cancellationToken CancellationToken.None
if tokenizer.LexBuffer.IsPastEndOfStream then if tokenizer.LexBuffer.IsPastEndOfStream then
let stepStatus = let stepStatus =
if fsiInterruptController.FsiInterruptStdinState = StdinEOFPermittedBecauseCtrlCRecentlyPressed then if fsiInterruptController.FsiInterruptStdinState = StdinEOFPermittedBecauseCtrlCRecentlyPressed then
...@@ -2022,7 +2023,7 @@ type internal FsiInteractionProcessor ...@@ -2022,7 +2023,7 @@ type internal FsiInteractionProcessor
// After we've unblocked and got something to run we switch // After we've unblocked and got something to run we switch
// over to the run-thread (e.g. the GUI thread) // over to the run-thread (e.g. the GUI thread)
let res = istate |> runCodeOnMainThread (fun ctok istate -> mainThreadProcessParsedInteractions ctok errorLogger (action, istate)) let res = istate |> runCodeOnMainThread (fun ctok istate -> mainThreadProcessParsedInteractions ctok errorLogger (action, istate) cancellationToken)
if !progress then fprintfn fsiConsoleOutput.Out "Just called runCodeOnMainThread, res = %O..." res; if !progress then fprintfn fsiConsoleOutput.Out "Just called runCodeOnMainThread, res = %O..." res;
res) res)
...@@ -2093,7 +2094,8 @@ type internal FsiInteractionProcessor ...@@ -2093,7 +2094,8 @@ type internal FsiInteractionProcessor
member __.LoadDummyInteraction(ctok, errorLogger) = member __.LoadDummyInteraction(ctok, errorLogger) =
setCurrState (currState |> InteractiveCatch errorLogger (fun istate -> fsiDynamicCompiler.EvalParsedDefinitions (ctok, errorLogger, istate, true, false, []) |> fst, Completed None) |> fst) setCurrState (currState |> InteractiveCatch errorLogger (fun istate -> fsiDynamicCompiler.EvalParsedDefinitions (ctok, errorLogger, istate, true, false, []) |> fst, Completed None) |> fst)
member __.EvalInteraction(ctok, sourceText, scriptFileName, errorLogger) = member __.EvalInteraction(ctok, sourceText, scriptFileName, errorLogger, ?cancellationToken) =
let cancellationToken = defaultArg cancellationToken CancellationToken.None
use _unwind1 = ErrorLogger.PushThreadBuildPhaseUntilUnwind(ErrorLogger.BuildPhase.Interactive) use _unwind1 = ErrorLogger.PushThreadBuildPhaseUntilUnwind(ErrorLogger.BuildPhase.Interactive)
use _unwind2 = ErrorLogger.PushErrorLoggerPhaseUntilUnwind(fun _ -> errorLogger) use _unwind2 = ErrorLogger.PushErrorLoggerPhaseUntilUnwind(fun _ -> errorLogger)
use _scope = SetCurrentUICultureForThread fsiOptions.FsiLCID use _scope = SetCurrentUICultureForThread fsiOptions.FsiLCID
...@@ -2102,7 +2104,7 @@ type internal FsiInteractionProcessor ...@@ -2102,7 +2104,7 @@ type internal FsiInteractionProcessor
currState currState
|> InteractiveCatch errorLogger (fun istate -> |> InteractiveCatch errorLogger (fun istate ->
let expr = ParseInteraction tokenizer let expr = ParseInteraction tokenizer
mainThreadProcessParsedInteractions ctok errorLogger (expr, istate) ) mainThreadProcessParsedInteractions ctok errorLogger (expr, istate) cancellationToken)
|> commitResult |> commitResult
member this.EvalScript (ctok, scriptPath, errorLogger) = member this.EvalScript (ctok, scriptPath, errorLogger) =
...@@ -2592,25 +2594,26 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i ...@@ -2592,25 +2594,26 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i
fsiInteractionProcessor.EvalExpression(ctok, sourceText, dummyScriptFileName, errorLogger) fsiInteractionProcessor.EvalExpression(ctok, sourceText, dummyScriptFileName, errorLogger)
|> commitResultNonThrowing errorOptions dummyScriptFileName errorLogger |> commitResultNonThrowing errorOptions dummyScriptFileName errorLogger
member x.EvalInteraction(sourceText) : unit = member x.EvalInteraction(sourceText, ?cancellationToken) : unit =
// Explanation: When the user of the FsiInteractiveSession object calls this method, the // Explanation: When the user of the FsiInteractiveSession object calls this method, the
// code is parsed, checked and evaluated on the calling thread. This means EvalExpression // code is parsed, checked and evaluated on the calling thread. This means EvalExpression
// is not safe to call concurrently. // is not safe to call concurrently.
let ctok = AssumeCompilationThreadWithoutEvidence() let ctok = AssumeCompilationThreadWithoutEvidence()
let cancellationToken = defaultArg cancellationToken CancellationToken.None
fsiInteractionProcessor.EvalInteraction(ctok, sourceText, dummyScriptFileName, errorLogger) fsiInteractionProcessor.EvalInteraction(ctok, sourceText, dummyScriptFileName, errorLogger, cancellationToken)
|> commitResult |> commitResult
|> ignore |> ignore
member x.EvalInteractionNonThrowing(sourceText) = member x.EvalInteractionNonThrowing(sourceText, ?cancellationToken) =
// Explanation: When the user of the FsiInteractiveSession object calls this method, the // Explanation: When the user of the FsiInteractiveSession object calls this method, the
// code is parsed, checked and evaluated on the calling thread. This means EvalExpression // code is parsed, checked and evaluated on the calling thread. This means EvalExpression
// is not safe to call concurrently. // is not safe to call concurrently.
let ctok = AssumeCompilationThreadWithoutEvidence() let ctok = AssumeCompilationThreadWithoutEvidence()
let cancellationToken = defaultArg cancellationToken CancellationToken.None
let errorOptions = TcConfig.Create(tcConfigB,validate = false).errorSeverityOptions let errorOptions = TcConfig.Create(tcConfigB,validate = false).errorSeverityOptions
let errorLogger = CompilationErrorLogger("EvalInteraction", errorOptions) let errorLogger = CompilationErrorLogger("EvalInteraction", errorOptions)
fsiInteractionProcessor.EvalInteraction(ctok, sourceText, dummyScriptFileName, errorLogger) fsiInteractionProcessor.EvalInteraction(ctok, sourceText, dummyScriptFileName, errorLogger, cancellationToken)
|> commitResultNonThrowing errorOptions "input.fsx" errorLogger |> commitResultNonThrowing errorOptions "input.fsx" errorLogger
member x.EvalScript(scriptPath) : unit = member x.EvalScript(scriptPath) : unit =
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
module public FSharp.Compiler.Interactive.Shell module public FSharp.Compiler.Interactive.Shell
open System.IO open System.IO
open System.Threading
open FSharp.Compiler open FSharp.Compiler
open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.SourceCodeServices
...@@ -146,7 +147,7 @@ type FsiEvaluationSession = ...@@ -146,7 +147,7 @@ type FsiEvaluationSession =
/// ///
/// Due to a current limitation, it is not fully thread-safe to run this operation concurrently with evaluation triggered /// Due to a current limitation, it is not fully thread-safe to run this operation concurrently with evaluation triggered
/// by input from 'stdin'. /// by input from 'stdin'.
member EvalInteraction : code: string -> unit member EvalInteraction : code: string * ?cancellationToken: CancellationToken -> unit
/// Execute the code as if it had been entered as one or more interactions, with an /// Execute the code as if it had been entered as one or more interactions, with an
/// implicit termination at the end of the input. Stop on first error, discarding the rest /// implicit termination at the end of the input. Stop on first error, discarding the rest
...@@ -155,7 +156,7 @@ type FsiEvaluationSession = ...@@ -155,7 +156,7 @@ type FsiEvaluationSession =
/// ///
/// Due to a current limitation, it is not fully thread-safe to run this operation concurrently with evaluation triggered /// Due to a current limitation, it is not fully thread-safe to run this operation concurrently with evaluation triggered
/// by input from 'stdin'. /// by input from 'stdin'.
member EvalInteractionNonThrowing : code: string -> Choice<FsiValue option, exn> * FSharpErrorInfo[] member EvalInteractionNonThrowing : code: string * ?cancellationToken: CancellationToken -> Choice<FsiValue option, exn> * FSharpErrorInfo[]
/// Execute the given script. Stop on first error, discarding the rest /// Execute the given script. Stop on first error, discarding the rest
/// of the script. Errors are sent to the output writer, a 'true' return value indicates there /// of the script. Errors are sent to the output writer, a 'true' return value indicates there
......
...@@ -3,8 +3,10 @@ ...@@ -3,8 +3,10 @@
namespace FSharp.Compiler.Scripting.UnitTests namespace FSharp.Compiler.Scripting.UnitTests
open System open System
open System.Diagnostics
open System.IO open System.IO
open System.Threading open System.Threading
open System.Threading.Tasks
open FSharp.Compiler.Interactive.Shell open FSharp.Compiler.Interactive.Shell
open FSharp.Compiler.Scripting open FSharp.Compiler.Scripting
open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.SourceCodeServices
...@@ -107,3 +109,26 @@ type InteractiveTests() = ...@@ -107,3 +109,26 @@ type InteractiveTests() =
match result with match result with
| Ok(_) -> Assert.Fail("expected a failure") | Ok(_) -> Assert.Fail("expected a failure")
| Error(ex) -> Assert.IsInstanceOf<FileNotFoundException>(ex) | Error(ex) -> Assert.IsInstanceOf<FileNotFoundException>(ex)
[<Test>]
member _.``Evaluation can be cancelled``() =
use script = new FSharpScript()
let sleepTime = 10000
let mutable result = None
let mutable wasCancelled = false
use tokenSource = new CancellationTokenSource()
let eval () =
try
result <- Some(script.Eval(sprintf "System.Threading.Thread.Sleep(%d)\n2" sleepTime, tokenSource.Token))
// if execution gets here (which it shouldn't), the value `2` will be returned
with
| :? OperationCanceledException -> wasCancelled <- true
let sw = Stopwatch.StartNew()
let evalTask = Task.Run(eval)
// cancel and wait for finish
tokenSource.Cancel()
evalTask.GetAwaiter().GetResult()
// ensure we cancelled and didn't complete the sleep or evaluation
Assert.True(wasCancelled)
Assert.LessOrEqual(sw.ElapsedMilliseconds, sleepTime)
Assert.AreEqual(None, result)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册