-
Notifications
You must be signed in to change notification settings - Fork 696
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix race condition in EnhancedHttpRetryHelper #5905
Conversation
When multiple threads try to access the `RetryCount` property we sometimes hit a case where `RetryCount` returned 0 (*). This caused the loop in `ProcessHttpSourceResultAsync()` to be skipped completely because `retry <= maxRetries`: https://github.com/NuGet/NuGet.Client/blob/6c642c2d63717acd4bf92050a42691928020eb89/src/NuGet.Core/NuGet.Protocol/Utility/FindPackagesByIdNupkgDownloader.cs#L258-L328 Fix this by using `Lazy<T>` for initializing the fields which is thread-safe. Fixes NuGet/Home#13545 (*) The reason is that code like `int RetryCount => _retryCount ??= 6;` gets turned into: ```csharp int valueOrDefault = _retryCount.GetValueOrDefault(); if (!_retryCount.HasValue) { valueOrDefault = 6; _retryCount = valueOrDefault; return valueOrDefault; } return valueOrDefault; ``` Suppose Thread A arrives first and calls `GetValueOrDefault()` (which is 0 for int) but then Thread B interjects and sets the value, now when Thread A resumes `_retryCount.HasValue` is true so we skip the if block and return valueOrDefault i.e. 0.
fe62a67
to
41d7bfc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the contribution! I'm running our tests pipeline now...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤝
_delayInMilliseconds = new Lazy<int>(() => GetIntFromEnvironmentVariable(DelayInMillisecondsEnvironmentVariableName, defaultValue: DefaultDelayMilliseconds, _environmentVariableReader)); | ||
_retry429 = new Lazy<bool>(() => GetBoolFromEnvironmentVariable(Retry429EnvironmentVariableName, defaultValue: true, _environmentVariableReader)); | ||
_observeRetryAfter = new Lazy<bool>(() => GetBoolFromEnvironmentVariable(ObserveRetryAfterEnvironmentVariableName, defaultValue: DefaultObserveRetryAfter, _environmentVariableReader)); | ||
_maxRetyAfterDelay = new Lazy<TimeSpan>(() => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_maxRetyAfterDelay
-> _maxRetryAfterDelay
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks: #6025
Bug
Fixes: NuGet/Home#13545
Regression? Last working version: 9.0-preview5
Description
When multiple threads try to access the RetryCount property we sometimes hit a case where RetryCount returned 0 (*).
This caused the loop in
ProcessHttpSourceResultAsync()
to be skipped completely becauseretry <= maxRetries
:NuGet.Client/src/NuGet.Core/NuGet.Protocol/Utility/FindPackagesByIdNupkgDownloader.cs
Lines 258 to 328 in 6c642c2
Fix this by using
Lazy<T>
for initializing the fields which is thread-safe.(*) The reason is that code like
int RetryCount => _retryCount ??= 6;
gets turned into:Suppose Thread A arrives first and calls
GetValueOrDefault()
(which is 0 for int) but then Thread B interjects and sets the value, now when Thread A resumes_retryCount.HasValue
is true so we skip the if block and return valueOrDefault i.e. 0.PR Checklist
PR has a meaningful title
PR has a linked issue.
Described changes
Tests
Documentation