#703 drop ConnectionLeaseTimeout

This commit is contained in:
Todd 2022-06-10 14:30:13 -05:00
parent 1b7aec690b
commit cc5d181895
4 changed files with 3 additions and 97 deletions

View File

@ -233,34 +233,6 @@ namespace Flurl.Test.Http
Assert.IsFalse(cts.Token.IsCancellationRequested); Assert.IsFalse(cts.Token.IsCancellationRequested);
} }
[Test, Ignore("failing on AppVeyor, holding up bugfix release")]
public async Task connection_lease_timeout_doesnt_disrupt_calls() {
// testing this quickly is tricky. HttpClient will be replaced by a new instance after 1 timeout and disposed
// after another, so the timeout period (typically minutes in real-world scenarios) needs to be long enough
// that we don't dispose before the response from google is received. 1 second seems to work.
var cli = new FlurlClient("http://www.google.com");
cli.Settings.ConnectionLeaseTimeout = TimeSpan.FromMilliseconds(1000);
var httpClients = new List<HttpClient>();
var tasks = new List<Task>();
// ping google for about 2.5 seconds
for (var i = 0; i < 25; i++) {
if (!httpClients.Contains(cli.HttpClient))
httpClients.Add(cli.HttpClient);
tasks.Add(cli.Request().HeadAsync());
await Task.Delay(100);
}
await Task.WhenAll(tasks); // failed HTTP status, etc, would throw here and fail the test.
Assert.AreEqual(3, httpClients.Count);
// only the first one should be disposed, which isn't particularly simple to check
Assert.ThrowsAsync<ObjectDisposedException>(() => httpClients[0].GetAsync("http://www.google.com"));
await httpClients[1].GetAsync("http://www.google.com");
await httpClients[2].GetAsync("http://www.google.com");
}
[Test] [Test]
public async Task test_settings_override_client_settings() { public async Task test_settings_override_client_settings() {
var cli1 = new FlurlClient(); var cli1 = new FlurlClient();

View File

@ -278,20 +278,6 @@ namespace Flurl.Test.Http
Assert.IsInstanceOf<SomeCustomHttpClient>(cli.HttpClient); Assert.IsInstanceOf<SomeCustomHttpClient>(cli.HttpClient);
Assert.IsInstanceOf<SomeCustomMessageHandler>(cli.HttpMessageHandler); Assert.IsInstanceOf<SomeCustomMessageHandler>(cli.HttpMessageHandler);
} }
[Test]
public async Task connection_lease_timeout_creates_new_HttpClient() {
var cli = new FlurlClient("http://api.com");
cli.Settings.ConnectionLeaseTimeout = TimeSpan.FromMilliseconds(50);
var hc = cli.HttpClient;
await Task.Delay(25);
Assert.That(hc == cli.HttpClient);
// exceed the timeout
await Task.Delay(25);
Assert.That(hc != cli.HttpClient);
}
} }
[TestFixture, Parallelizable] [TestFixture, Parallelizable]

View File

@ -174,16 +174,6 @@ namespace Flurl.Http.Configuration
/// </summary> /// </summary>
public class ClientFlurlHttpSettings : FlurlHttpSettings public class ClientFlurlHttpSettings : FlurlHttpSettings
{ {
/// <summary>
/// Specifies the time to keep the underlying HTTP/TCP connection open. When expired, a Connection: close header
/// is sent with the next request, which should force a new connection and DSN lookup to occur on the next call.
/// Default is null, effectively disabling the behavior.
/// </summary>
public TimeSpan? ConnectionLeaseTimeout {
get => Get<TimeSpan?>();
set => Set(value);
}
/// <summary> /// <summary>
/// Gets or sets a factory used to create the HttpClient and HttpMessageHandler used for HTTP calls. /// Gets or sets a factory used to create the HttpClient and HttpMessageHandler used for HTTP calls.
/// Whenever possible, custom factory implementations should inherit from DefaultHttpClientFactory, /// Whenever possible, custom factory implementations should inherit from DefaultHttpClientFactory,

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using Flurl.Http.Configuration; using Flurl.Http.Configuration;
using Flurl.Http.Testing; using Flurl.Http.Testing;
@ -64,7 +62,7 @@ namespace Flurl.Http
/// </summary> /// </summary>
/// <param name="baseUrl">The base URL associated with this client.</param> /// <param name="baseUrl">The base URL associated with this client.</param>
public FlurlClient(string baseUrl = null) { public FlurlClient(string baseUrl = null) {
_httpClient = new Lazy<HttpClient>(CreateHttpClient); _httpClient = new Lazy<HttpClient>(() => Settings.HttpClientFactory.CreateHttpClient(HttpMessageHandler));
_httpMessageHandler = new Lazy<HttpMessageHandler>(() => Settings.HttpClientFactory.CreateMessageHandler()); _httpMessageHandler = new Lazy<HttpMessageHandler>(() => Settings.HttpClientFactory.CreateMessageHandler());
BaseUrl = baseUrl; BaseUrl = baseUrl;
} }
@ -86,7 +84,7 @@ namespace Flurl.Http
/// <inheritdoc /> /// <inheritdoc />
public ClientFlurlHttpSettings Settings { public ClientFlurlHttpSettings Settings {
get => _settings ?? (_settings = new ClientFlurlHttpSettings()); get => _settings ??= new ClientFlurlHttpSettings();
set => _settings = value; set => _settings = value;
} }
@ -94,47 +92,7 @@ namespace Flurl.Http
public INameValueList<string> Headers { get; } = new NameValueList<string>(false); // header names are case-insensitive https://stackoverflow.com/a/5259004/62600 public INameValueList<string> Headers { get; } = new NameValueList<string>(false); // header names are case-insensitive https://stackoverflow.com/a/5259004/62600
/// <inheritdoc /> /// <inheritdoc />
public HttpClient HttpClient => HttpTest.Current?.HttpClient ?? _injectedClient ?? GetHttpClient(); public HttpClient HttpClient => HttpTest.Current?.HttpClient ?? _injectedClient ?? _httpClient.Value;
private DateTime? _clientCreatedAt;
private HttpClient _zombieClient;
private readonly object _connectionLeaseLock = new object();
private HttpClient GetHttpClient() {
if (ConnectionLeaseExpired()) {
lock (_connectionLeaseLock) {
if (ConnectionLeaseExpired()) {
// when the connection lease expires, force a new HttpClient to be created, but don't
// dispose the old one just yet - it might have pending requests. Instead, it becomes
// a zombie and is disposed on the _next_ lease timeout, which should be safe.
_zombieClient?.Dispose();
_zombieClient = _httpClient.Value;
_httpClient = new Lazy<HttpClient>(CreateHttpClient);
_httpMessageHandler = new Lazy<HttpMessageHandler>(() => Settings.HttpClientFactory.CreateMessageHandler());
_clientCreatedAt = DateTime.UtcNow;
}
}
}
return _httpClient.Value;
}
private HttpClient CreateHttpClient() {
var cli = Settings.HttpClientFactory.CreateHttpClient(HttpMessageHandler);
_clientCreatedAt = DateTime.UtcNow;
return cli;
}
private bool ConnectionLeaseExpired() {
// for thread safety, capture these to temp variables
var createdAt = _clientCreatedAt;
var timeout = Settings.ConnectionLeaseTimeout;
return
_httpClient.IsValueCreated &&
createdAt.HasValue &&
timeout.HasValue &&
DateTime.UtcNow - createdAt.Value > timeout.Value;
}
/// <inheritdoc /> /// <inheritdoc />
public HttpMessageHandler HttpMessageHandler => HttpTest.Current?.HttpMessageHandler ?? _httpMessageHandler?.Value; public HttpMessageHandler HttpMessageHandler => HttpTest.Current?.HttpMessageHandler ?? _httpMessageHandler?.Value;