Skip to content

Shared RelaxVersioner_Properties.xml in pack targets can cause MSB4018 in parallel builds #28

Description

@iwizsophy

Summary

RelaxVersioner 3.21.0 can fail in CI with a file-lock error while running pack-related targets.

The failing file is typically:

obj/Release/RelaxVersioner_Properties.xml

Example error:

RelaxVersioner.targets(327,5): error MSB4018: The "DumpPropertiesTask" task failed unexpectedly.
System.IO.IOException: The process cannot access the file
'obj/Release/RelaxVersioner_Properties.xml'
because it is being used by another process.

at System.IO.File.Create(String path)
at RelaxVersioner.DumpPropertiesTask.Execute()

Expected behavior

RelaxVersioner should work correctly when the same project is evaluated or built multiple times in CI, including scenarios such as:

  • inner builds
  • graph builds
  • pack-related build requests
  • multi-target SDK-style builds

Actual behavior

DumpPropertiesTask may attempt to create the same RelaxVersioner_Properties.xml file concurrently, which results in an MSB4018 failure.

This behavior was observed consistently when pack-related targets were executed in parallel.

Why this seems to happen

From the 3.21.0 sources:

  • RelaxVersionerPackCoreGenerate calls DumpPropertiesTask.
  • In the pack path, RelaxVersionerOutputDir is derived from $(NuspecOutputPath).

As a result, the dump path becomes something like:

obj/Release/RelaxVersioner_Properties.xml

This path is shared and does not include a TFM-specific directory.

In contrast, normal build-time outputs usually go under:

$(IntermediateOutputPath)

which for SDK-style multi-target builds typically resolves to:

obj/<Configuration>/<TFM>/

Based on observation:

  • pack-related targets may resolve multiple TFMs to the same shared path
  • normal build-time targets resolve to separate TFM-scoped directories

Important detail

In 3.21.0, DumpPropertiesTask.Execute() uses:

File.Create(OutputPath)

without retry or locking, and the exception is not handled in that release.

As a result, the IOException propagates as MSB4018.

Broader concern

The issue may affect more than DumpPropertiesTask.

The same dump base path is used for files such as:

  • RelaxVersioner_Result.xml
  • RelaxVersioner_Version.txt
  • RelaxVersioner_ShortVersion.txt
  • RelaxVersioner_SafeVersion.txt
  • RelaxVersioner_CommitId.txt
  • RelaxVersioner_Branch.txt
  • RelaxVersioner_Tags.txt

Therefore, protecting only DumpPropertiesTask might prevent the immediate exception but still leave race conditions where one build request observes another request's files.

Example scenario:

  1. Build request A writes RelaxVersioner_Properties.xml
  2. Build request B overwrites it
  3. Build request A runs rv --propertiesPath ...
  4. A reads B's data instead of its own

Suggested direction

A robust fix might be one of the following:

  1. Preferred
    Make the properties/result/helper output paths unique per build request or inner build.

  2. Alternative
    Serialize the whole pipeline per dump base path:

    • dump properties
    • run rv
    • read back results
    • write helper files
  3. Minimal mitigation
    Add exception handling or retry logic around DumpPropertiesTask.

    This would mitigate the current MSB4018, but it would not fully solve the shared-path race.

Environment

  • RelaxVersioner: 3.21.0
  • CI: GitHub Actions
  • OS: Linux runner
  • Project type: SDK-style .NET project
  • Observed during pack-related processing

Due to repository constraints I cannot share a minimal reproduction project,
but the behavior was consistently observed in CI when pack-related targets
were executed in parallel in a multi-target SDK-style build.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions