Skip to content

Fix cold-start codegen races without requiring -m:1. - #402

Open
hahn-kev wants to merge 1 commit into
chore/updateAgentDocsfrom
fix/serialize-inner-codegen
Open

Fix cold-start codegen races without requiring -m:1.#402
hahn-kev wants to merge 1 commit into
chore/updateAgentDocsfrom
fix/serialize-inner-codegen

Conversation

@hahn-kev

@hahn-kev hahn-kev commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Looking at #364 I figured we could do better than requiring first builds to run on just a single core.

Cursor ran a test which reproduced the clean build issue, then tested out various scenarios to confirm this works correctly.

AI level 7: Human specced, bots coded


AI summary

Summary

  • Serialize inner TFM builds on SIL.LCModel and SIL.LCModel.Core so parallel dotnet build does not race on shared generated sources (LcmGenerate / IdlImp).
  • Stop nested GenerateKernelCs.proj from skipping when FwKernelTlb.json already exists, which left Kernel.cs missing after deleting generated sources.
  • Drop the -m:1 cold-start workaround from README.md and AGENTS.md.

Test plan

  • Delete gitignored generated sources (Generated*.cs, KernelInterfaces/Kernel.cs) but leave artifacts/**/FwKernelTlb.json; dotnet build (no -m:1) should regenerate Kernel.cs and succeed.
  • Delete generated sources and the Debug FwKernelTlb.json files; dotnet build should succeed on a cold tree.
  • Incremental dotnet build still no-ops generation when sources are up to date.
  • dotnet build -f net8.0 still generates and compiles.

This change is Reviewable

Serialize inner TFM builds for projects that write shared generated sources, and always run IdlImp once the parent target decides generation is needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

LCM Tests

    16 files  ±0      16 suites  ±0   2m 11s ⏱️ +2s
 2 880 tests ±0   2 860 ✅ ±0   20 💤 ±0  0 ❌ ±0 
11 468 runs  ±0  11 300 ✅ ±0  168 💤 ±0  0 ❌ ±0 

Results for commit 2d14657. ± Comparison against base commit af8c5c9.

@jasonleenaylor jasonleenaylor 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.

I don't think the InnerBuildInParallel property does anything. It isn't in the SDK, it isn't in this repository, and I can't find it on the internet. Microsoft.Common.CrossTargeting.targets only reads BuildInParallel, and Directory.Build.props line 29 has set that to false since well before this pull request, so the inner targets were not running at the same time before your change either.

The real concern here is that the file we fail on isn't written by two targets, it is written by two processes. GenerateModel in SIL.LCModel.csproj line 112 runs BeforeCompile, so it runs once for each of netstandard2.0, net462 and net8.0. Line 115 runs it with Exec, which starts a new dotnet build. GenerateModel.proj line 6 passes OutputDir=".", so all three write DomainImpl/GeneratedClasses.cs. Only OutDir changes for each target framework, the generated file does not.

This is what I get on a clean build of your branch:

Error processing template The process cannot access the file
'src\SIL.LCModel\DomainImpl\GeneratedClasses.cs' because it is being used by another process.
GenerateModel.proj(6,3): error MSB4181: The "LcmGenerate" task returned false but did not log an error.

That is a lock, not a missing file, and nothing named BuildInParallel can help with it. Exec starts a process, and MSBuild doesn't schedule what it can't see.

I also ran clean builds on your branch and on the one it is based on. The base failed 2 of 4 and yours failed 2 of 9. That is the same problem at a lower rate. I had 3 passes in a row before my first failure, so a handful of good runs doesn't tell us much either way, and I don't think we should remove the -m:1 instructions on this evidence.

Three things:

  1. Keep the GenerateKernelCs.proj change. Removing Inputs and Outputs there is a real fix for a different problem, and your comment explains it well.
  2. Take out the two InnerBuildInParallel lines.
  3. Put the -m:1 instructions back for now.

To actually fix it I think GenerateModel has to run once for the whole project instead of once for each target framework, or the generator needs a named lock. Either one is more than this pull request should have to carry.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants