Nerdbank.GitVersioning: IOException when building from mapping pack files into memory
Several developers reporting and at least one CI build failed. Rolled back to 3.3.37 for now.
System.IO.IOException: Not enough memory resources are available to process this command.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.MemoryMappedFiles.MemoryMappedView.CreateView(SafeMemoryMappedFileHandle memMappedFileHandle, MemoryMappedFileAccess access, Int64 offset, Int64 size)
at Nerdbank.GitVersioning.ManagedGit.GitPack..ctor(GetObjectFromRepositoryDelegate getObjectFromRepositoryDelegate, String indexPath, String packPath, GitPackCache cache)
at Nerdbank.GitVersioning.ManagedGit.GitPack..ctor(GitRepository repository, String name)
at Nerdbank.GitVersioning.ManagedGit.GitRepository.LoadPacks()
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at System.Lazy`1.get_Value()
at Nerdbank.GitVersioning.ManagedGit.GitRepository.TryGetObjectBySha(GitObjectId sha, String objectType, Stream& value)
at Nerdbank.GitVersioning.ManagedGit.GitRepository.GetObjectBySha(GitObjectId sha, String objectType)
at Nerdbank.GitVersioning.ManagedGit.GitRepository.GetCommit(GitObjectId sha, Boolean readAuthor)
at Nerdbank.GitVersioning.ManagedGit.GitRepository.GetHeadCommit(Boolean readAuthor)
at Nerdbank.GitVersioning.Managed.ManagedGitContext..ctor(String workingDirectory, String dotGitPath, String committish)
at Nerdbank.GitVersioning.GitContext.Create(String path, String committish, Boolean writable)
at Nerdbank.GitVersioning.Tasks.GetBuildVersion.ExecuteInner()
at MSBuildExtensionTask.ContextAwareTask.Execute()
at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 2
- Comments: 19 (6 by maintainers)
Seems to be working well for us on all platforms, šš» Thanks again
This is now available in https://github.com/dotnet/Nerdbank.GitVersioning/releases/tag/v3.4.228
Thanks @AArnott , sorry I didnāt get around to give feedback in time. Will get on the new version tomorrow and give it a whirl.
Thereās never a problem with 32-bit processes spawning 64-bit ones. Itās just that thatās not what msbuild in VS was designed to do.
@benmccallum If you have the time to dig into this, could you give https://github.com/dotnet/Nerdbank.GitVersioning/pull/626 a try and let us know if this fixes the problem for you? There may be a perf regression n macOS, but since you mention VS, I guess youāre on Windows so you should be good.
Alternatively, set the
NBGV_GitEngine
environment variable toLibGit2
to sidestep the managed implementation altogether.All help is welcome, so Iād be more than happy if @filipnavara wants to invest some time in this š .
I think the main reason was
.Dispose
behavior. Pooling streams had a significant impact on performance. There was an attempt to use the built-in version (0c5a573) which was reverted (61e877a, https://github.com/dotnet/Nerdbank.GitVersioning/pull/521#issuecomment-719987210).Yes, that may be the case. If I remember correctly, the stream pooling and usage of memory mapped files was introduced at about the same time. Thereās probably a real benefit for index files, since we do a binary search there.
It is possible that the memory mapping doesnāt have much of a perf impact on pack files, and the real gain comes from stream pooling.
There are a couple of perf tests which run as part of CI, so it should be fairly easy to tell whether changes regressed performance or not š
Hereās an experiment with mapping only small chunks of the pack file: https://github.com/dotnet/Nerdbank.GitVersioning/compare/master...filipnavara:big-pack?expand=1
The approach is not perfect but it highlights few things:
GitPackPooledStream
.MemoryMappedStream
compared to the built-inMemoryMappedViewStream
. It could be performance or different behaviour when disposing. Either way it doesnāt handle non-alignedoffset
s. The memory mappings have to be aligned to page size boundaries on the OS level APIs. .NET tries to hide this restriction by aligning the mapped block to nearest smaller offset and then making the window large enough. Then it makes the stream point to the actual offset within the first page of the view.In the end I have a feeling that it may be more reasonable to map only the index files and not the pack files. If itās a problem performance wise itās probably fine to do that only as a fallback when certain parameters are exceeded (file size threshold, 32-bit process, the mapping fails, etc.). .NET 6 will have better APIs to handle this kind of random reads (https://github.com/dotnet/runtime/pull/53669) but unfortunately thatās still some months away.
Hereās one more data point:
Mapping the index files is fine but mapping the whole pack files is definitely problematic.