-
-
Notifications
You must be signed in to change notification settings - Fork 2
Handle root query client lifetimes #471
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
Handle root query client lifetimes #471
Conversation
|
Claude finished @PrzemyslawKlys's task —— View job Pull Request ReviewReview Tasks:
Code Quality and Best Practices ✅DnsClientX.QueryDns.cs (lines 97-118):
ResolveFromRootTests.cs:
AssemblyInfo.cs:
Potential Issues ✅No significant issues found. The implementation correctly addresses the lifetime management concern:
Performance Considerations ✅Positive performance impacts:
DnsClientX.QueryDns.cs:98-118 - The implementation is efficient:
Security Concerns ✅No security issues identified:
Test Coverage ✅Excellent test coverage for the specific issue: ResolveFromRootTests.cs:23-72 provides comprehensive regression test:
Minor suggestions:
Overall Assessment ⭐This is a high-quality fix that:
The implementation demonstrates good understanding of async resource management patterns in .NET and includes the necessary infrastructure for testing internal behavior. Recommendation: ✅ Approve and merge |
|
Claude finished @PrzemyslawKlys's task —— View job Pull Request ReviewReview Tasks:
Code Quality and Best Practices ✅DnsClientX.QueryDns.cs (lines 97-118):
ResolveFromRootTests.cs:
AssemblyInfo.cs:
Potential Issues ✅No significant issues found. The implementation correctly addresses the lifetime management concern:
Performance Considerations ✅Positive performance impacts:
DnsClientX.QueryDns.cs:98-118 - The implementation is efficient:
Security Concerns ✅No security issues identified:
Test Coverage ✅Excellent test coverage for the specific issue: ResolveFromRootTests.cs:23-72 provides comprehensive regression test:
Minor suggestions:
Overall Assessment ⭐This is a high-quality fix that:
The implementation demonstrates good understanding of async resource management patterns in .NET and includes the necessary infrastructure for testing internal behavior. Recommendation: ✅ Approve and merge |
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.
Pull request overview
This PR addresses a critical lifetime management issue in the root DNS server query functionality. Previously, ClientX instances were being disposed prematurely within the LINQ Select operation, potentially causing tasks to fail when accessing disposed resources.
Key Changes:
- Refactored
QueryDnsarray overload to maintain client references until all tasks complete - Added test hooks (
RootClientFactoryandRootResolveOverride) to enable isolated testing - Introduced regression test to verify clients remain alive during parallel root lookups
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| DnsClientX/DnsClientX.QueryDns.cs | Refactored root server query logic to use list-based lifetime management with try/finally disposal pattern; added internal test hooks for mocking client creation and resolution |
| DnsClientX/AssemblyInfo.cs | Added blank line for formatting consistency |
| DnsClientX.Tests/ResolveFromRootTests.cs | Added comprehensive regression test using mock clients to verify lifetime management fix; test validates clients remain undisposed during task execution and are properly cleaned up afterward |
| return await Task.WhenAll(tasks).ConfigureAwait(false); | ||
| } finally { | ||
| foreach (var client in clients) { | ||
| client.Dispose(); |
Copilot
AI
Dec 9, 2025
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.
If any Dispose() call throws an exception in the finally block, it will suppress exceptions from Task.WhenAll(). Consider wrapping the disposal loop in a try-catch to prevent disposal exceptions from masking task failures:
} finally {
foreach (var client in clients) {
try {
client.Dispose();
} catch {
// Log or ignore disposal failures
}
}
}| client.Dispose(); | |
| try { | |
| client.Dispose(); | |
| } catch { | |
| // Log or ignore disposal failures | |
| } |
| /// <returns>A task that represents the asynchronous operation. The task result contains the DNS response.</returns> | ||
| public static async Task<DnsResponse[]> QueryDns(string[] name, DnsRecordType recordType, DnsEndpoint dnsEndpoint = DnsEndpoint.System, DnsSelectionStrategy dnsSelectionStrategy = DnsSelectionStrategy.First, int timeOutMilliseconds = Configuration.DefaultTimeout, bool retryOnTransient = true, int maxRetries = 3, int retryDelayMs = 200, bool requestDnsSec = false, bool validateDnsSec = false, bool typedRecords = false, bool parseTypedTxtRecords = false, CancellationToken cancellationToken = default) { |
Copilot
AI
Dec 9, 2025
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.
[nitpick] The method doesn't validate if the name array is null or empty. When an empty array is passed, the method will create an empty clients list and return an empty array of DnsResponse, which may be the intended behavior. However, consider adding explicit null/empty validation or documenting this behavior.
| /// <returns>A task that represents the asynchronous operation. The task result contains the DNS response.</returns> | |
| public static async Task<DnsResponse[]> QueryDns(string[] name, DnsRecordType recordType, DnsEndpoint dnsEndpoint = DnsEndpoint.System, DnsSelectionStrategy dnsSelectionStrategy = DnsSelectionStrategy.First, int timeOutMilliseconds = Configuration.DefaultTimeout, bool retryOnTransient = true, int maxRetries = 3, int retryDelayMs = 200, bool requestDnsSec = false, bool validateDnsSec = false, bool typedRecords = false, bool parseTypedTxtRecords = false, CancellationToken cancellationToken = default) { | |
| /// <returns>A task that represents the asynchronous operation. The task result contains the DNS response.</returns> | |
| /// <exception cref="ArgumentNullException">Thrown if <paramref name="name"/> is null.</exception> | |
| /// <remarks> | |
| /// If <paramref name="name"/> is an empty array, the method returns an empty <see cref="DnsResponse"/> array. | |
| /// </remarks> | |
| public static async Task<DnsResponse[]> QueryDns(string[] name, DnsRecordType recordType, DnsEndpoint dnsEndpoint = DnsEndpoint.System, DnsSelectionStrategy dnsSelectionStrategy = DnsSelectionStrategy.First, int timeOutMilliseconds = Configuration.DefaultTimeout, bool retryOnTransient = true, int maxRetries = 3, int retryDelayMs = 200, bool requestDnsSec = false, bool validateDnsSec = false, bool typedRecords = false, bool parseTypedTxtRecords = false, CancellationToken cancellationToken = default) { | |
| if (name == null) { | |
| throw new ArgumentNullException(nameof(name)); | |
| } | |
| if (name.Length == 0) { | |
| return Array.Empty<DnsResponse>(); | |
| } |
| [Fact] | ||
| /// <summary> | ||
| /// Ensures root lookups created in parallel keep their clients alive until completion. | ||
| /// </summary> |
Copilot
AI
Dec 9, 2025
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.
The XML documentation comment should be placed before the [Fact] attribute, not after it. The standard convention is to place documentation comments immediately before attributes.
| [Fact] | |
| /// <summary> | |
| /// Ensures root lookups created in parallel keep their clients alive until completion. | |
| /// </summary> | |
| /// <summary> | |
| /// Ensures root lookups created in parallel keep their clients alive until completion. | |
| /// </summary> | |
| [Fact] |
| /// <param name="cancellationToken">Token used to cancel the operation.</param> | ||
| /// <returns>A task that represents the asynchronous operation. The task result contains the DNS response.</returns> | ||
| public static async Task<DnsResponse[]> QueryDns(string[] name, DnsRecordType recordType, DnsEndpoint dnsEndpoint = DnsEndpoint.System, DnsSelectionStrategy dnsSelectionStrategy = DnsSelectionStrategy.First, int timeOutMilliseconds = Configuration.DefaultTimeout, bool retryOnTransient = true, int maxRetries = 3, int retryDelayMs = 200, bool requestDnsSec = false, bool validateDnsSec = false, bool typedRecords = false, bool parseTypedTxtRecords = false, CancellationToken cancellationToken = default) { | ||
| if (dnsEndpoint == DnsEndpoint.RootServer) { |
Copilot
AI
Dec 9, 2025
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.
[nitpick] Consider checking cancellationToken.IsCancellationRequested before the LINQ query (line 100) to avoid creating and disposing clients unnecessarily when the operation is already cancelled.
| if (dnsEndpoint == DnsEndpoint.RootServer) { | |
| if (dnsEndpoint == DnsEndpoint.RootServer) { | |
| if (cancellationToken.IsCancellationRequested) { | |
| return await Task.FromCanceled<DnsResponse[]>(cancellationToken).ConfigureAwait(false); | |
| } |
| try { | ||
| var tasks = name.Select(n => { | ||
| var client = RootClientFactory(); | ||
| clients.Add(client); | ||
| if (cancellationToken.IsCancellationRequested) { | ||
| return Task.FromCanceled<DnsResponse>(cancellationToken); | ||
| } | ||
|
|
||
| var resolver = RootResolveOverride; | ||
| return resolver != null | ||
| ? resolver(client, n, recordType, cancellationToken) | ||
| : client.ResolveFromRoot(n, recordType, cancellationToken: cancellationToken); | ||
| }).ToArray(); | ||
|
|
Copilot
AI
Dec 9, 2025
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.
This variable is manually disposed in a finally block - consider a C# using statement as a preferable resource management technique.
| try { | |
| var tasks = name.Select(n => { | |
| var client = RootClientFactory(); | |
| clients.Add(client); | |
| if (cancellationToken.IsCancellationRequested) { | |
| return Task.FromCanceled<DnsResponse>(cancellationToken); | |
| } | |
| var resolver = RootResolveOverride; | |
| return resolver != null | |
| ? resolver(client, n, recordType, cancellationToken) | |
| : client.ResolveFromRoot(n, recordType, cancellationToken: cancellationToken); | |
| }).ToArray(); | |
| var tasks = name.Select(n => { | |
| var client = RootClientFactory(); | |
| clients.Add(client); | |
| if (cancellationToken.IsCancellationRequested) { | |
| return Task.FromCanceled<DnsResponse>(cancellationToken); | |
| } | |
| var resolver = RootResolveOverride; | |
| return resolver != null | |
| ? resolver(client, n, recordType, cancellationToken) | |
| : client.ResolveFromRoot(n, recordType, cancellationToken: cancellationToken); | |
| }).ToArray(); | |
| try { |
Summary
Testing
dotnet test DnsClientX.sln(fails: numerous DNS tests require external network access; environment reports network unreachable)Codex Task