Ensure that awaits do not continue on the captured context. (#370)

* Ensure that awaits do not continue on the captured context.

* Make functions async for maintainability.

* Add documentation detailing the use of UIFact.
This commit is contained in:
Andy Kernahan
2020-03-19 04:54:44 +00:00
committed by GitHub
parent af741302de
commit 3e6815ad4c
8 changed files with 32 additions and 25 deletions

View File

@@ -273,7 +273,7 @@ namespace k8s
{
// Copy the default (credential-related) request headers from the HttpClient to the WebSocket
HttpRequestMessage message = new HttpRequestMessage();
await this.Credentials.ProcessHttpRequestAsync(message, cancellationToken);
await this.Credentials.ProcessHttpRequestAsync(message, cancellationToken).ConfigureAwait(false);
foreach (var _header in message.Headers)
{

View File

@@ -100,7 +100,7 @@ namespace k8s
throw new NullReferenceException(nameof(kubeconfig));
}
var k8SConfig = await LoadKubeConfigAsync(kubeconfig, useRelativePaths);
var k8SConfig = await LoadKubeConfigAsync(kubeconfig, useRelativePaths).ConfigureAwait(false);
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
return k8SConfiguration;
@@ -139,7 +139,7 @@ namespace k8s
kubeconfig.Position = 0;
var k8SConfig = await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfig);
var k8SConfig = await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfig).ConfigureAwait(false);
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
return k8SConfiguration;
@@ -486,7 +486,7 @@ namespace k8s
{
var fileInfo = new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation);
return await LoadKubeConfigAsync(fileInfo, useRelativePaths);
return await LoadKubeConfigAsync(fileInfo, useRelativePaths).ConfigureAwait(false);
}
/// <summary>
@@ -517,7 +517,7 @@ namespace k8s
using (var stream = kubeconfig.OpenRead())
{
var config = await Yaml.LoadFromStreamAsync<K8SConfiguration>(stream);
var config = await Yaml.LoadFromStreamAsync<K8SConfiguration>(stream).ConfigureAwait(false);
if (useRelativePaths)
{
@@ -547,7 +547,7 @@ namespace k8s
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
public static async Task<K8SConfiguration> LoadKubeConfigAsync(Stream kubeconfigStream)
{
return await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfigStream);
return await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfigStream).ConfigureAwait(false);
}
/// <summary>

View File

@@ -57,7 +57,7 @@ namespace k8s
/// </summary>
public void Start()
{
this.runLoop = this.RunLoop(this.cts.Token);
this.runLoop = Task.Run(async () => await this.RunLoop(this.cts.Token));
}
/// <inheritdoc/>
@@ -193,9 +193,6 @@ namespace k8s
protected async Task RunLoop(CancellationToken cancellationToken)
{
// This is a background task. Immediately yield to the caller.
await Task.Yield();
// Get a 1KB buffer
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024 * 1024);
// This maps remembers bytes skipped for each stream.

View File

@@ -57,7 +57,7 @@ namespace k8s
OnClosed += onClosed;
_cts = new CancellationTokenSource();
_watcherLoop = this.WatcherLoop(_cts.Token);
_watcherLoop = Task.Run(async () => await this.WatcherLoop(_cts.Token));
}
/// <inheritdoc/>
@@ -91,14 +91,11 @@ namespace k8s
private async Task WatcherLoop(CancellationToken cancellationToken)
{
// Make sure we run async
await Task.Yield();
try
{
Watching = true;
string line;
_streamReader = await _streamReaderCreator();
_streamReader = await _streamReaderCreator().ConfigureAwait(false);
// ReadLineAsync will return null when we've reached the end of the stream.
while ((line = await _streamReader.ReadLineAsync().ConfigureAwait(false)) != null)
@@ -164,7 +161,7 @@ namespace k8s
Action onClosed = null)
{
return new Watcher<T>(async () => {
var response = await responseTask;
var response = await responseTask.ConfigureAwait(false);
if (!(response.Response.Content is WatcherDelegatingHandler.LineSeparatedHttpContent content))
{

View File

@@ -19,7 +19,7 @@ namespace k8s
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var originResponse = await base.SendAsync(request, cancellationToken);
var originResponse = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (originResponse.IsSuccessStatusCode)
{
@@ -47,18 +47,18 @@ namespace k8s
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
_originStream = await _originContent.ReadAsStreamAsync();
_originStream = await _originContent.ReadAsStreamAsync().ConfigureAwait(false);
StreamReader = new PeekableStreamReader(_originStream);
var firstLine = await StreamReader.PeekLineAsync();
var firstLine = await StreamReader.PeekLineAsync().ConfigureAwait(false);
var writer = new StreamWriter(stream);
// using (writer) // leave open
{
await writer.WriteAsync(firstLine);
await writer.FlushAsync();
await writer.WriteAsync(firstLine).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
}
}
@@ -94,7 +94,7 @@ namespace k8s
}
public async Task<string> PeekLineAsync()
{
var line = await ReadLineAsync();
var line = await ReadLineAsync().ConfigureAwait(false);
_buffer.Enqueue(line);
return line;
}

View File

@@ -29,7 +29,7 @@ namespace k8s
/// </param>
public static async Task<List<object>> LoadAllFromStreamAsync(Stream stream, Dictionary<String, Type> typeMap) {
var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
var content = await reader.ReadToEndAsync().ConfigureAwait(false);
return LoadAllFromString(content, typeMap);
}
@@ -95,13 +95,13 @@ namespace k8s
public static async Task<T> LoadFromStreamAsync<T>(Stream stream) {
var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
var content = await reader.ReadToEndAsync().ConfigureAwait(false);
return LoadFromString<T>(content);
}
public static async Task<T> LoadFromFileAsync<T> (string file) {
using (FileStream fs = File.OpenRead(file)) {
return await LoadFromStreamAsync<T>(fs);
return await LoadFromStreamAsync<T>(fs).ConfigureAwait(false);
}
}

View File

@@ -33,6 +33,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="Xunit.StaFact" Version="0.3.18" />
<PackageReference Include="Moq" Version="4.13.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />

View File

@@ -1,5 +1,6 @@
using System.IO;
using System.Linq;
using System.Threading;
using k8s.Exceptions;
using k8s.KubeConfigModels;
using Xunit;
@@ -406,6 +407,17 @@ namespace k8s.Tests
AssertConfigEqual(expectedCfg, cfg);
}
/// <summary>
/// Ensures Kube config file can be loaded from within a non-default <see cref="SynchronizationContext"/>.
/// The use of <see cref="UIFactAttribute"/> ensures the test is run from within a UI-like <see cref="SynchronizationContext"/>.
/// </summary>
[UIFact]
public void BuildConfigFromConfigFileInfoOnNonDefaultSynchronizationContext()
{
var fi = new FileInfo("assets/kubeconfig.yml");
KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "federal-context", useRelativePaths: false);
}
private void AssertConfigEqual(K8SConfiguration expected, K8SConfiguration actual)
{
Assert.Equal(expected.ApiVersion, actual.ApiVersion);