Skip to content

Race Condition in StaticWebAssets with Multi-Targeting and TypeScript Compilation #52014

@loop8ack

Description

@loop8ack

Description

When using multi-targeting (net8.0;net9.0) in a Razor Class Library project that compiles TypeScript files via Microsoft.TypeScript.MSBuild, a race condition occurs during dotnet build. The ResolveProjectStaticWebAssets target sometimes attempts to resolve static web assets before the TypeScript compiler has finished writing the compiled .js files to disk, resulting in an InvalidOperationException.

The error only manifests when multi-targeting is enabled. With a single target framework, the build succeeds consistently.

Reproduction Steps

  1. Create a Razor Class Library project with multi-targeting:
    <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
  2. Add TypeScript compilation via Microsoft.TypeScript.MSBuild package
  3. Configure tsconfig.json to output compiled files to wwwroot/js/dist:
    {
      "compilerOptions": {
        "rootDir": "wwwroot/ts",
        "outDir": "wwwroot/js/dist"
      }
    }
  4. Add a TypeScript file in wwwroot/ts/ (e.g., test.ts)
  5. Run dotnet build multiple times on Windows
  6. The error occurs non-deterministically (race condition)

Sample repository: https://github.com/loop8ack/Dotnet.Issue120620.Reproduction

Expected behavior

The build should succeed consistently.

Actual behavior

The build fails intermittently with:

Microsoft.NET.Sdk.StaticWebAssets.targets(679,5): Error  : System.InvalidOperationException: No file exists for the asset at either location 'D:\Source\...\wwwroot\js\dist\test.js' or 'wwwroot\js\dist\test.js'.
   at Microsoft.AspNetCore.StaticWebAssets.Tasks.StaticWebAsset.ResolveFile(String identity, String originalItemSpec)
   at Microsoft.AspNetCore.StaticWebAssets.Tasks.DefineStaticWebAssets.ResolveFileDetails(String originalItemSpec, String identity)
   at Microsoft.AspNetCore.StaticWebAssets.Tasks.DefineStaticWebAssets.Execute()

Regression?

Unknown. I have not tested whether this occurs in earlier SDK versions.

Known Workarounds

I have implemented a workaround using file-based locking to serialize access to the ResolveProjectStaticWebAssets target:

<Target Name="__RaceConditionFix_AcquireLock_BeforeStaticAssets" BeforeTargets="ResolveProjectStaticWebAssets">
  <AcquireLock LockFile="$(__LockFile)" Timeout="$(__LockTimeout)" />
</Target>

<Target Name="__RaceConditionFix_ReleaseLock_AfterStaticAssets" AfterTargets="ResolveProjectStaticWebAssets">
  <ReleaseLock LockFile="$(__LockFile)" />
</Target>

This forces sequential execution of 'ResolveProjectStaticWebAssets' across target frameworks and appears to work reliably. However:

  • This is a hack that shouldn't be necessary
  • It adds build time overhead
  • It requires custom MSBuild task implementations

Alternative workaround: Remove multi-targeting (use only a single <TargetFramework>) - this eliminates the race condition but is not always feasible.

Configuration

  • SDK Version: 9.0.304
  • OS: Windows 11 (issue does not occur under Linux)
  • Project Type: Razor Class Library (Microsoft.NET.Sdk.Razor)
  • Packages:
    • Microsoft.AspNetCore.Components.Web (8.0.* and 9.0.* depending on framework)
    • Microsoft.TypeScript.MSBuild 5.8.3

Project file excerpt:

<Project Sdk="Microsoft.NET.Sdk.Razor">
    <PropertyGroup>
        <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
    </PropertyGroup>
    <ItemGroup>
        <SupportedPlatform Include="browser"/>
    </ItemGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.8.3" PrivateAssets="all"/>
        <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.*" Condition="'$(TargetFramework)' == 'net8.0'"/>
        <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.*" Condition="'$(TargetFramework)' == 'net9.0'"/>
    </ItemGroup>
</Project>

Other information

The issue appears to be a timing/ordering problem in the MSBuild target execution graph when multiple target frameworks are being built:

  1. With multi-targeting, MSBuild builds each target framework in parallel or in rapid succession
  2. The TypeScript compilation (CompileTypeScript target) runs as part of the build process
  3. The ResolveProjectStaticWebAssets target attempts to enumerate and validate static web assets
  4. There is insufficient dependency declaration or synchronization between these targets
  5. When building multiple frameworks, ResolveProjectStaticWebAssets from one framework build can race with CompileTypeScript from another, or the file system operations are not fully synchronized

The race is confirmed by the non-deterministic nature of the failure — it does not occur on every build, only sometimes.

The race condition appears to be Windows-specific. Testing under WSL does not reproduce the issue; conversely, the file-locking workaround causes problems on WSL/Linux.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions