{
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Get, serverUri) { Version = httpVersion })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req))
{
ValidateResponse(m, httpVersion);
ValidateContent(contentSource, await m.Content.ReadAsStringAsync());
{
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Get, serverUri + "/slow") { Version = httpVersion })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req, HttpCompletionOption.ResponseHeadersRead))
{
ValidateResponse(m, httpVersion);
using (Stream s = await m.Content.ReadAsStreamAsync())
{
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Get, serverUri + "/headers") { Version = httpVersion })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req))
{
ValidateResponse(m, httpVersion);
ValidateContent(contentSource, await m.Content.ReadAsStringAsync());
Version httpVersion = ctx.GetRandomVersion(httpVersions);
(string query, string expected) variables = GetGetQueryParameters(contentSource, ctx, numParameters);
using (var req = new HttpRequestMessage(HttpMethod.Get, serverUri + "/variables" + variables.query) { Version = httpVersion })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req))
{
ValidateResponse(m, httpVersion);
ValidateContent(variables.expected, await m.Content.ReadAsStringAsync());
{
using (var req = new HttpRequestMessage(HttpMethod.Get, serverUri + "/abort") { Version = httpVersion })
{
- await ctx.HttpClient.SendAsync(req, ctx.CancellationToken);
+ await ctx.SendAsync(req);
}
throw new Exception("Completed unexpectedly");
}
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Post, serverUri) { Version = httpVersion, Content = new StringDuplexContent(content) })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req))
{
ValidateResponse(m, httpVersion);
ValidateContent(content, await m.Content.ReadAsStringAsync());;
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Post, serverUri) { Version = httpVersion, Content = formData.formDataContent })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req))
+ using (HttpResponseMessage m = await ctx.SendAsync(req))
{
ValidateResponse(m, httpVersion);
ValidateContent($"{formData.expected}", await m.Content.ReadAsStringAsync());;
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Post, serverUri + "/duplex") { Version = httpVersion, Content = new StringDuplexContent(content) })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req, HttpCompletionOption.ResponseHeadersRead))
{
ValidateResponse(m, httpVersion);
ValidateContent(content, await m.Content.ReadAsStringAsync());
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Post, serverUri + "/duplexSlow") { Version = httpVersion, Content = new ByteAtATimeNoLengthContent(Encoding.ASCII.GetBytes(content)) })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req, HttpCompletionOption.ResponseHeadersRead))
{
ValidateResponse(m, httpVersion);
ValidateContent(content, await m.Content.ReadAsStringAsync());
using (var req = new HttpRequestMessage(HttpMethod.Post, serverUri) { Version = httpVersion, Content = new StringContent(content) })
{
req.Headers.ExpectContinue = true;
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req, HttpCompletionOption.ResponseHeadersRead))
{
ValidateResponse(m, httpVersion);
ValidateContent(content, await m.Content.ReadAsStringAsync());
{
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Head, serverUri) { Version = httpVersion })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req))
{
ValidateResponse(m, httpVersion);
if (m.Content.Headers.ContentLength != maxContentLength)
Version httpVersion = ctx.GetRandomVersion(httpVersions);
using (var req = new HttpRequestMessage(HttpMethod.Put, serverUri) { Version = httpVersion, Content = new StringContent(content) })
- using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req, ctx.CancellationToken))
+ using (HttpResponseMessage m = await ctx.SendAsync(req))
{
ValidateResponse(m, httpVersion);
string r = await m.Content.ReadAsStringAsync();
{
long opIndex = i % clientOperations.Length;
(string operation, Func<RequestContext, Task> func) = clientOperations[opIndex];
- using (var requestContext = new RequestContext(client, random, taskNum, cancellationProbability))
+ var requestContext = new RequestContext(client, random, taskNum, cancellationProbability);
+ try
{
- try
- {
- await func(requestContext);
+ await func(requestContext);
- Increment(ref success[opIndex]);
- }
- catch (OperationCanceledException) when (requestContext.CancellationToken.IsCancellationRequested)
+ Increment(ref success[opIndex]);
+ }
+ catch (OperationCanceledException) when (requestContext.IsCancellationRequested)
+ {
+ Increment(ref cancel[opIndex]);
+ }
+ catch (Exception e)
+ {
+ Increment(ref fail[opIndex]);
+
+ if (e is HttpRequestException hre && hre.InnerException is SocketException se && se.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
- Increment(ref cancel[opIndex]);
+ Interlocked.Increment(ref reuseAddressFailure);
}
- catch (Exception e)
+ else
{
- Increment(ref fail[opIndex]);
-
- if (e is HttpRequestException hre && hre.InnerException is SocketException se && se.SocketErrorCode == SocketError.AddressAlreadyInUse)
- {
- Interlocked.Increment(ref reuseAddressFailure);
- }
- else
+ lock (Console.Out)
{
- lock (Console.Out)
- {
- Console.ForegroundColor = ConsoleColor.Yellow;
- Console.WriteLine($"Error from iteration {i} ({operation}) in task {taskNum} with {success.Sum()} successes / {fail.Sum()} fails:");
- Console.ResetColor();
- Console.WriteLine(e);
- Console.WriteLine();
- }
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine($"Error from iteration {i} ({operation}) in task {taskNum} with {success.Sum()} successes / {fail.Sum()} fails:");
+ Console.ResetColor();
+ Console.WriteLine(e);
+ Console.WriteLine();
}
}
}
}
/// <summary>Client context containing information pertaining to a single request.</summary>
- private sealed class RequestContext : IDisposable
+ private sealed class RequestContext
{
private readonly Random _random;
- private readonly CancellationTokenSource _cts;
+ private readonly HttpClient _client;
+ private readonly double _cancellationProbability;
public RequestContext(HttpClient httpClient, Random random, int taskNum, double cancellationProbability)
{
_random = random;
+ _client = httpClient;
+ _cancellationProbability = cancellationProbability;
+
TaskNum = taskNum;
- HttpClient = httpClient;
+ IsCancellationRequested = false;
+ }
+
+ public int TaskNum { get; }
+ public bool IsCancellationRequested { get; set; }
- if(GetRandomBoolean(cancellationProbability))
+ // HttpClient.SendAsync() wrapper that wires randomized cancellation
+ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption httpCompletion = HttpCompletionOption.ResponseContentRead, CancellationToken? token = null)
+ {
+ if (token != null)
+ {
+ // user-supplied cancellation token overrides random cancellation
+ return await _client.SendAsync(request, httpCompletion, token.Value);
+ }
+ else if (GetRandomBoolean(_cancellationProbability))
{
- var delay = TimeSpan.FromMilliseconds(GetRandomInt(maxValue: 5));
- _cts = new CancellationTokenSource(delay);
- CancellationToken = _cts.Token;
+ // trigger a random cancellation
+ using(var cts = new CancellationTokenSource())
+ {
+ var delayMs = _random.Next(0, 2);
+ var task = _client.SendAsync(request, httpCompletion, cts.Token);
+ if (delayMs > 0)
+ await Task.Delay(delayMs);
+
+ cts.Cancel();
+ IsCancellationRequested = true;
+ return await task;
+ }
}
else
{
- CancellationToken = CancellationToken.None;
+ // no cancellation
+ return await _client.SendAsync(request, httpCompletion);
}
}
- public int TaskNum { get; }
-
- public HttpClient HttpClient { get; }
-
- public CancellationToken CancellationToken { get; }
public string GetRandomSubstring(string input)
{
public Version GetRandomVersion(Version[] versions) =>
versions[_random.Next(0, versions.Length)];
-
- public void Dispose() => _cts?.Dispose();
}
/// <summary>HttpContent that partially serializes and then waits for cancellation to be requested.</summary>