Skip to content

Conversation

@jasonmalinowski
Copy link
Member

@jasonmalinowski jasonmalinowski commented Oct 22, 2025

We're seeing a performance issue in Razor projects in cohosting scenarios. The first run of the Razor generator is relatively slow, and so ideally we wouldn't have that happen more than once. However, since multiple features might request compilations for a Razor project during startup in parallel, it's possible that all of those parallel invocations might initialize the generator multiple times if they happen to be different Solution instances. This change tries to ensure that when we're creating a GeneratorDriver for a project when we don't already have one, we'll do that computation just once, and try to share that across the forks. This ensures we do the expensive initialization only once.

// HACK: we probably shouldn't have this be a static instance, but this is easier than threading it through from the Workspace layer.
internal static readonly GeneratorDriverCreationCache Instance = new();

private ImmutableDictionary<ProjectId, TaskCompletionSource<GeneratorDriver>> _driverCache = ImmutableDictionary<ProjectId, TaskCompletionSource<GeneratorDriver>>.Empty;
Copy link
Member

Choose a reason for hiding this comment

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

Haven't looked at the whole pr, but we rarely cache TCS. We cache AsyncLazy instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes we can probably move this on; this changed a bit as I wrote it and since it's draft I didn't give a re-think.

// There is already some other work in process to create a driver for this project; we'll want to await that work and then
try
{
// TODO: we don't have a WithCancellation() that could be used here, so we should use some alternate pattern to get the
Copy link
Member

Choose a reason for hiding this comment

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

I think AsyncLazy would handle that

{
data.onAfterUpdate?.Invoke(oldSolution, newSolution);

newSolution.CompilationState.ClearGeneratorDriverCaches();
Copy link
Member

Choose a reason for hiding this comment

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

Doc

We're seeing a performnace issue in Razor projects in cohosting
scenarios. The first run of the Razor generator is relatively slow,
and so ideally we wouldn't have that happen more than once. However,
since multiple features might request compilations for a Razor project
during startup in parallel, it's possible that all of those parallel
invocations might initialize the generator multiple times if they
happen to be different Solution instances. This change tries to ensure
that when we're creating a GeneratorDriver for a project when we don't
already have one, we'll do that computation just once, and try to share
that across the forks. This ensures we do the expensive initialization
only once.
@jasonmalinowski jasonmalinowski force-pushed the try-to-reuse-initialized-generatordrivers branch from 5ce64cf to 931eb2f Compare October 22, 2025 23:35
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

Any reason this is a TCS and not an AsyncLazy?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants