dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.38k stars 4.75k forks source link

System.DirectoryServices.Protocols - Linux performance problems with large response sizes #43929

Open null-d3v opened 4 years ago

null-d3v commented 4 years ago

Description

I have observed performance generally being worse on linux, to the point that large requests that succeed on Windows will time out on linux.

When searching for groups using a basic SearchRequest with the following configuration:

var searchRequest = new SearchRequest(
    container,
    "objectCategory=group",
    SearchScope.Subtree,
    new[] { "cn", });

I will get these results:

ou=Company,dc=company,dc=com (715 groups) Windows: ~550ms Linux: timeout

OU=Folder Access,OU=Company Security Groups,ou=Company,dc=company,dc=com (230 groups) Windows: ~230ms Linux: ~23s

OU=Product,ou=Company,dc=company,dc=com (20 groups) Windows: ~90ms Linux: ~2s

This isn't as problematic until reaching higher response sizes where the requests scale to unreasonable response times or just timeout.

I found this when investigating Windows-linux disparity in #43621.

Configuration

joperezr commented 3 years ago

@null-d3v thanks for logging the issue. Where you able to consistently repro this issue when using that amount of groups? Also, do you mind sharing a code snippet so that we can investigate using the same filter and an AD with similar conditions?

null-d3v commented 3 years ago

Definitely consistent response times based on the size of the search results. Here is a highly reduced summarization:

var connection = new LdapConnection(
    new LdapDirectoryIdentifier("company.com:389"),
    new NetworkCredential(
        "user@company.com",
        "password"),
    AuthType.Negotiate);
connection.SessionOptions.ProtocolVersion = 3;

var searchRequest = new SearchRequest(
    "OU=Product,ou=Company,dc=company,dc=com",
    "objectCategory=group",
    SearchScope.Subtree,
    new[] { "cn", });

var options = new SearchOptionsControl(SearchOption.DomainScope);
searchRequest.Controls.Add(options);

var searchResults = await Task.Factory.FromAsync<
    DirectoryRequest, PartialResultProcessing, DirectoryResponse>(
        connection.BeginSendRequest,
        connection.EndSendRequest,
        searchRequest,
        PartialResultProcessing.NoPartialResultSupport,
        null);
joperezr commented 3 years ago

Given this is not a regression and don't have an exact repro, I'll move this issue for us to investigate in 7.0.

jcracknell commented 1 year ago

I ran into this problem trying to run a directory scan on Linux using libldap-2.6, and it appears to be a problem specifically with BeginSendAsync, which takes a minimum of 100ms per received result to complete (you'll notice this in the original report).

I believe this occurs because this section runs the polling interval up to 100 immediately waiting for an initial response. Calling ldap_result via GetResultFromAsyncOperation with 0-valued timeout then causes it to process a single message and then time out, returning 0:

https://github.com/dotnet/runtime/blob/144dbcbaa1a43dd4b3f7dc270afe25099d43104a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs#L1410-L1417

There's a lot of other weirdness here, like how messageId is always 0, and how abandoning the Task produced by ResponseCallback somehow actually works, unlike calls with any other value of PartialResultProcessing which seem to never complete.

I'll also note that the values for ResultAll are not strictly aligned with those expected by ldap_result - LDAP_MSG_ALL just happens to match.