Performance issue with multiple httpclients (since version 1.2.10) #276

Closed
opened 2025-12-29 15:19:32 +01:00 by adam · 6 comments
Owner

Originally created by @tully2003 on GitHub (May 24, 2020).

Originally assigned to: @StefH on GitHub.

I am experiencing a performance issue following an upgrade from 1.2.9 and the issue affects all subsequent versions.

There appears to be a startup cost ~2 seconds for the first request to a mocked endpoint per HttpClient used, in version 1.2.9 this cost only occurred once but in >1.2.9 this cost is there per request.

You can use the following code to replicate the issue

class Program
{
    static async Task Main(string[] args)
    {
        const string AccessToken = "api.token";
        var wiremock = WireMockServer.Start();

        wiremock.Given(Request.Create().WithPath("/connect/token").UsingGet())
            .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK).WithBodyAsJson(new { access_token = AccessToken }));

        wiremock.Given(Request.Create().WithPath("/api/v1/users").WithHeader("Authorization", $"Bearer {AccessToken}").UsingGet())
            .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK).WithBodyAsJson(new { id = Guid.NewGuid(), reference = Guid.NewGuid().ToString() }));

        var client1 = CreateHttpClient(wiremock);
        var client2 = CreateHttpClient(wiremock);
        for (int i = 0; i < 5; i++)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();
            var response = await client1.GetAsync("/connect/token");
            Console.WriteLine($"/connect/token {stopwatch.Elapsed}");
            var token = JsonSerializer.Deserialize<TokenResponse>(await response.Content.ReadAsStringAsync());

            var request = new HttpRequestMessage(HttpMethod.Get, "/api/v1/users");
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.access_token);
            stopwatch = Stopwatch.StartNew();
            await client2.SendAsync(request);
            Console.WriteLine($"/api/v1/user {stopwatch.Elapsed}");
            Console.WriteLine("=========================");
        }
    }

    private static HttpClient CreateHttpClient(WireMockServer wiremock)
    {
        return new HttpClient()
        {
            BaseAddress = new Uri(wiremock.Urls[0])
        };
    }

    private class TokenResponse
    {
        public string access_token { get; set; }
    }
}

output 1.2.9

Now listening on: http://0.0.0.0:51927
/connect/token 00:00:02.1927065
/api/v1/user 00:00:02.0459082
=========================
/connect/token 00:00:00.0022588
/api/v1/user 00:00:00.0009971
=========================
/connect/token 00:00:00.0008681
/api/v1/user 00:00:00.0009190
=========================
/connect/token 00:00:00.0008236
/api/v1/user 00:00:00.0007929
=========================
/connect/token 00:00:00.0008217
/api/v1/user 00:00:00.0007583
=========================

output 1.2.12

Now listening on: http://0.0.0.0:51932
/connect/token 00:00:02.2017247
/api/v1/user 00:00:02.0179940
=========================
/connect/token 00:00:02.0076275
/api/v1/user 00:00:02.0140512
=========================
/connect/token 00:00:02.0137766
/api/v1/user 00:00:02.0177099
=========================
/connect/token 00:00:02.0103903
/api/v1/user 00:00:02.0079146
=========================
/connect/token 00:00:02.0080497
/api/v1/user 00:00:02.0064984
=========================

Note: you'll need to make sure you clear any existing assemblies between builds (as least that's what i needed to do in VS2019)

Running on

  • .net core 3.1 (3.1.300)
  • VS 2019 (16.6.0)
  • windows 10
Originally created by @tully2003 on GitHub (May 24, 2020). Originally assigned to: @StefH on GitHub. I am experiencing a performance issue following an upgrade from 1.2.9 and the issue affects all subsequent versions. There appears to be a startup cost ~2 seconds for the first request to a mocked endpoint per HttpClient used, in version 1.2.9 this cost only occurred once but in >1.2.9 this cost is there per request. You can use the following code to replicate the issue ```c# class Program { static async Task Main(string[] args) { const string AccessToken = "api.token"; var wiremock = WireMockServer.Start(); wiremock.Given(Request.Create().WithPath("/connect/token").UsingGet()) .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK).WithBodyAsJson(new { access_token = AccessToken })); wiremock.Given(Request.Create().WithPath("/api/v1/users").WithHeader("Authorization", $"Bearer {AccessToken}").UsingGet()) .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK).WithBodyAsJson(new { id = Guid.NewGuid(), reference = Guid.NewGuid().ToString() })); var client1 = CreateHttpClient(wiremock); var client2 = CreateHttpClient(wiremock); for (int i = 0; i < 5; i++) { Stopwatch stopwatch = Stopwatch.StartNew(); var response = await client1.GetAsync("/connect/token"); Console.WriteLine($"/connect/token {stopwatch.Elapsed}"); var token = JsonSerializer.Deserialize<TokenResponse>(await response.Content.ReadAsStringAsync()); var request = new HttpRequestMessage(HttpMethod.Get, "/api/v1/users"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.access_token); stopwatch = Stopwatch.StartNew(); await client2.SendAsync(request); Console.WriteLine($"/api/v1/user {stopwatch.Elapsed}"); Console.WriteLine("========================="); } } private static HttpClient CreateHttpClient(WireMockServer wiremock) { return new HttpClient() { BaseAddress = new Uri(wiremock.Urls[0]) }; } private class TokenResponse { public string access_token { get; set; } } } ``` output 1.2.9 ``` Now listening on: http://0.0.0.0:51927 /connect/token 00:00:02.1927065 /api/v1/user 00:00:02.0459082 ========================= /connect/token 00:00:00.0022588 /api/v1/user 00:00:00.0009971 ========================= /connect/token 00:00:00.0008681 /api/v1/user 00:00:00.0009190 ========================= /connect/token 00:00:00.0008236 /api/v1/user 00:00:00.0007929 ========================= /connect/token 00:00:00.0008217 /api/v1/user 00:00:00.0007583 ========================= ``` output 1.2.12 ``` Now listening on: http://0.0.0.0:51932 /connect/token 00:00:02.2017247 /api/v1/user 00:00:02.0179940 ========================= /connect/token 00:00:02.0076275 /api/v1/user 00:00:02.0140512 ========================= /connect/token 00:00:02.0137766 /api/v1/user 00:00:02.0177099 ========================= /connect/token 00:00:02.0103903 /api/v1/user 00:00:02.0079146 ========================= /connect/token 00:00:02.0080497 /api/v1/user 00:00:02.0064984 ========================= ``` > Note: you'll need to make sure you clear any existing assemblies between builds (as least that's what i needed to do in VS2019) Running on - .net core 3.1 (3.1.300) - VS 2019 (16.6.0) - windows 10
adam added the bug label 2025-12-29 15:19:32 +01:00
adam closed this issue 2025-12-29 15:19:32 +01:00
Author
Owner

@StefH commented on GitHub (May 24, 2020):

Thanks for the analysis.

Can you please make a GitHub project where you have 2 projects:

  • one includes WireMock 1.2.9
  • one includes WireMock latests

The performance issue could maybe be related to "Handlebars.Net.Helpers", I need to check.

@StefH commented on GitHub (May 24, 2020): Thanks for the analysis. Can you please make a GitHub project where you have 2 projects: - one includes WireMock 1.2.9 - one includes WireMock latests The performance issue could maybe be related to "Handlebars.Net.Helpers", I need to check.
Author
Owner

@StefH commented on GitHub (May 24, 2020):

I did quick test for just Handlebars.Net.Helpers:

for (int i = 0; i < 5; i++)
            {
                var stopwatch = Stopwatch.StartNew();
                HandlebarsHelpers.Register(handlebars, options => { options.UseCategoryPrefix = false; });
                Console.WriteLine($"HandlebarsHelpers.Register {stopwatch.Elapsed}");
            }
HandlebarsHelpers.Register 00:00:00.0182456
HandlebarsHelpers.Register 00:00:00.0007973
HandlebarsHelpers.Register 00:00:00.0004377
HandlebarsHelpers.Register 00:00:00.0004186
HandlebarsHelpers.Register 00:00:00.0004251

That does not seem to be the issue.

@StefH commented on GitHub (May 24, 2020): I did quick test for just Handlebars.Net.Helpers: ``` cs for (int i = 0; i < 5; i++) { var stopwatch = Stopwatch.StartNew(); HandlebarsHelpers.Register(handlebars, options => { options.UseCategoryPrefix = false; }); Console.WriteLine($"HandlebarsHelpers.Register {stopwatch.Elapsed}"); } ``` ``` HandlebarsHelpers.Register 00:00:00.0182456 HandlebarsHelpers.Register 00:00:00.0007973 HandlebarsHelpers.Register 00:00:00.0004377 HandlebarsHelpers.Register 00:00:00.0004186 HandlebarsHelpers.Register 00:00:00.0004251 ``` That does not seem to be the issue.
Author
Owner

@tully2003 commented on GitHub (May 24, 2020):

Thanks for the analysis.

Can you please make a GitHub project where you have 2 projects:

  • one includes WireMock 1.2.9
  • one includes WireMock latests

The performance issue could maybe be related to "Handlebars.Net.Helpers", I need to check.

sure, added a repo here => https://github.com/tully2003/issues-wiremock-474.perf

@tully2003 commented on GitHub (May 24, 2020): > Thanks for the analysis. > > Can you please make a GitHub project where you have 2 projects: > > * one includes WireMock 1.2.9 > * one includes WireMock latests > > The performance issue could maybe be related to "Handlebars.Net.Helpers", I need to check. sure, added a repo here => https://github.com/tully2003/issues-wiremock-474.perf
Author
Owner

@StefH commented on GitHub (May 24, 2020):

I can see the delay from 2 second in your test program.
When running on WSL Ubuntu, there's no delay.

The difference in the code which is the cause of the error is:

options.Limits.KeepAliveTimeout = TimeSpan.MaxValue;

// and maybe also

options.Limits.RequestHeadersTimeout = TimeSpan.MaxValue;

I've fixed it, and a new version will be uploaded to NuGet.

Thank you.

@StefH commented on GitHub (May 24, 2020): I can see the delay from 2 second in your test program. When running on WSL Ubuntu, there's no delay. The difference in the code which is the cause of the error is: ``` cs options.Limits.KeepAliveTimeout = TimeSpan.MaxValue; // and maybe also options.Limits.RequestHeadersTimeout = TimeSpan.MaxValue; ``` I've fixed it, and a new version will be uploaded to NuGet. Thank you.
Author
Owner

@StefH commented on GitHub (May 24, 2020):

https://github.com/WireMock-Net/WireMock.Net/pull/475

@StefH commented on GitHub (May 24, 2020): https://github.com/WireMock-Net/WireMock.Net/pull/475
Author
Owner

@tully2003 commented on GitHub (May 25, 2020):

Awesome, thanks for fixing so quickly 😀

@tully2003 commented on GitHub (May 25, 2020): Awesome, thanks for fixing so quickly 😀
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/WireMock.Net-wiremock#276