Skip to content

Cancellable: fix leaking cancellation token#18295

Merged
psfinaki merged 8 commits into
dotnet:mainfrom
auduchinok:cancellable-useToken
Feb 11, 2025
Merged

Cancellable: fix leaking cancellation token#18295
psfinaki merged 8 commits into
dotnet:mainfrom
auduchinok:cancellable-useToken

Conversation

@auduchinok

@auduchinok auduchinok commented Feb 6, 2025

Copy link
Copy Markdown
Member

The previous implementation could lead to the cancellation leaking to another async, leading to unexpected exceptions about disposed cancellation token source.

Thanks a lot to @ForNeVeR for the help with the investigation!

cc @majocha

@auduchinok auduchinok requested a review from a team as a code owner February 6, 2025 12:33
@github-actions

github-actions Bot commented Feb 6, 2025

Copy link
Copy Markdown
Contributor

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/9.0.300.md

@majocha

majocha commented Feb 6, 2025

Copy link
Copy Markdown
Contributor

Yes, good catch. Apparently, I forgot how AsyncLocal works.

@auduchinok

Copy link
Copy Markdown
Member Author

Could somebody update the ILVerify baselines, please?

@ForNeVeR

ForNeVeR commented Feb 6, 2025

Copy link
Copy Markdown
Contributor

Yes, good catch. Apparently, I forgot how AsyncLocal works.

@majocha you kinda didn't forget and made similar changes in #18285 :)

@majocha

majocha commented Feb 6, 2025

Copy link
Copy Markdown
Contributor

This is beyond scope of this PR, but it occurred to me now:
The two mechanisms of cancellation of synchronous code, explicit CheckAndThrow and implicit cancellable workflow are now quite independent of each other. The token for CheckAndThrow can be captured without the intermediary cancellable.
It would be good to have some comment in the code explaining how CheckAndThrow works and it's usage, to make it easier to grasp for future readers.

Comment thread docs/release-notes/.FSharp.Compiler.Service/9.0.300.md Outdated
@psfinaki

psfinaki commented Feb 7, 2025

Copy link
Copy Markdown
Contributor

@auduchinok @majocha - can you think of any way to test such things?

@auduchinok

Copy link
Copy Markdown
Member Author

@auduchinok @majocha - can you think of any way to test such things?

Some of the possibly similar cases are tested in the module reader cancellation tests in this repo. They do catch regressions in PRs like this one (e.g. the transparent compiler changes here)

And we also have tests in the Rider plugin. But it's somewhat difficult to come up with test cases for some of the cases, as, for example, this particular case was related to the implementation details of the cancellation token sources handling inside Async.parallel.

This particular leak was reliably reproduced in the tests in the Rider plugin, but unfortunately only when run in the product monorepo. I was not able to reproduce it in the plugin repo or find another repro so far :( Good news are there are tests and environments that can catch such things :)

@majocha

majocha commented Feb 8, 2025

Copy link
Copy Markdown
Contributor

Something is still wrong, I can repro this error locally
https://dev.azure.com/dnceng-public/public/_build/results?buildId=944083&view=logs&j=09cc464c-89cd-5758-006d-ba9e3958cfa9&t=098678ff-b2f3-5c88-44d3-4c7be2f47e75&l=10832
by simply running all service tests in VS, but it happens randomly.

    CHECKANDTHROW STACKTRACE

   at FSharp.Compiler.Cancellable.clo@15-19.Invoke(Unit unitVar0)
   at FSharp.Compiler.Cancellable.CheckAndThrow()
   at FSharp.Compiler.Service.Tests.ModuleReaderCancellationTests.runCancelFirstTime@24.Invoke(Unit unitVar0)
   at FSharp.Compiler.Import.entities@784-1.Invoke(Tuple`2 tupledArg)
   at FSharp.Compiler.Import.multisetDiscriminateAndMap[Key,Value,a](FSharpFunc`2 nodef, FSharpFunc`2 tipf, FSharpList`1 items)
   at FSharp.Compiler.Import.ImportILTypeDefList(FSharpFunc`2 amap, Range m, CompilationPath cpath, FSharpList`1 enc, FSharpList`1 items)
   at FSharp.Compiler.Import.modty@782.Invoke(Unit _arg2)
   at Internal.Utilities.Library.InterruptibleLazy`1.get_Value()
   at Internal.Utilities.Library.Extras.MaybeLazy`1.Force()
   at FSharp.Compiler.TypedTree.Entity.get_IsModule()
   at FSharp.Compiler.CompilerImports.addCxsToModule@503.Invoke(String name, ModuleOrNamespaceType m)
   at FSharp.Compiler.CompilerImports.addCxsToModule@503.Invoke(String name, ModuleOrNamespaceType m)
   at FSharp.Compiler.CompilerImports.addConstraintSources(ImportedAssembly ia)
   at FSharp.Compiler.CompilerImports.TcImports.RegisterAndImportReferencedAssemblies@2356-12.Invoke(ImportedAssembly ia)
   at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc`2 action, IEnumerable`1 source)
   at FSharp.Compiler.CompilerImports.TcImports.RegisterAndImportReferencedAssemblies@2351-9.Invoke(FSharpOption`1[] results)
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, b result1, FSharpFunc`2 userCode)
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction)
   at <StartupCode$FSharp-Core>.$Async.clo@193-15.Invoke(Object o)
   at System.Threading.QueueUserWorkItemCallback.Execute()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

It's somehow happening in RegisterAndImportReferencedAssemblies.

@majocha

majocha commented Feb 8, 2025

Copy link
Copy Markdown
Contributor

Yes, now I remember I dealt with it in this experiment:
https://github.com/dotnet/fsharp/pull/18285/files#
It needs yet another UseToken() in RegisterAndImportReferencedAssemblies (around line 2330)
I'm not sure why this is non-deterministic, though.

Comment thread src/Compiler/Service/TransparentCompiler.fs Outdated
Comment thread src/Compiler/Service/TransparentCompiler.fs Outdated
Comment thread src/Compiler/Driver/CompilerImports.fs Outdated
@auduchinok

Copy link
Copy Markdown
Member Author

This is ready 🙂

@T-Gro T-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do! Cancellable.UseToken() its not used anywhere after this PR, right?

@auduchinok

Copy link
Copy Markdown
Member Author

do! Cancellable.UseToken() its not used anywhere after this PR, right?

Yes, that's correct

@psfinaki psfinaki left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Eugene!

@psfinaki psfinaki merged commit 5af63aa into dotnet:main Feb 11, 2025
@auduchinok auduchinok deleted the cancellable-useToken branch February 11, 2025 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants