Skip to content

Type subsumption cache causes sustained ~150% CPU in IDE with generative type providers (hash code not memoized) #19368

@OnurGumus

Description

@OnurGumus

Description

The type subsumption cache introduced in PR #18875 and partially fixed in PR #18926 causes sustained high CPU usage (~150%) in IDE/FSAC when a project uses generative type providers (e.g. custom HTML type provider with ~15 template instantiations).

PR #18926 memoized TypeStructure creation via a ConditionalWeakTable, but the GetHashCode of TTypeCacheKey still calls GenericHashArbArray on the underlying ImmutableArray<TypeToken> on every cache lookup. Since ImmutableArray<T> is a struct wrapping a T[], F# structural hashing delegates to the underlying array, making each hash call O(n) over the token array. In IDE mode with repeated type checks, this dominates CPU.

A 10-second dotnet-trace CPU sample on the FSAC process (166% CPU) shows:

Samples Function
2414 Thread.PollGC
1170 RuntimeTypeHandle.InternalAllocNoChecks
820 GenericEqualityArbArray
696 TTypeCacheKey.GetHashCodeGenericHashArbArray
96 ConcurrentDictionary.GrowTable
12 Cache.rebuildStore

Repro steps

  1. Create an F# solution targeting net10.0 (LangVersion 10) with 5+ projects
  2. Add a generative type provider (e.g. one producing ~15 provided types with a few methods each)
  3. Reference the type provider from 2+ projects in the solution
  4. Open the solution in VS Code with Ionide (FSAC)
  5. Observe sustained ~150% CPU on the dotnet fsautocomplete.dll process that does not settle down

A minimal repro with 30 template instantiations in a single project did not reproduce the issue. The combination of multiple projects, generative type provider types, and other type providers (e.g. FSharp.Data JsonProvider) in the same solution appears necessary to trigger it.

Expected behavior

CPU usage should settle to near-zero after initial type checking completes. The subsumption cache should improve performance, not degrade it.

Actual behavior

CPU stays at ~150% indefinitely. The CPU is spent almost entirely in TTypeCacheKey.GetHashCodeGenericHashArbArray, hashing ImmutableArray<TypeToken> arrays on every cache lookup. The cache appears to continuously grow and rebuild, each rebuild re-hashing all existing entries at O(n) per entry.

Diagnostic logging in the type provider confirmed:

  • FCS re-creates provider instances every ~10 seconds (observed 12 instances in ~60 seconds)
  • Even with static caches returning identical ProvidedTypeDefinition objects (all cache hits), CPU remains high
  • The provider itself does negligible work — the CPU is entirely in FCS internal processing

Known workarounds

Set <LangVersion>9.0</LangVersion> in Directory.Build.props to disable the UseTypeSubsumptionCache language feature. This eliminates the CPU issue entirely. Requires replacing any F# 10-only syntax (e.g. let! x: T =let! (x: T) =).

Related information

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Status

    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions