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 // Copy the default (credential-related) request headers from the HttpClient to the WebSocket
HttpRequestMessage message = new HttpRequestMessage(); HttpRequestMessage message = new HttpRequestMessage();
await this.Credentials.ProcessHttpRequestAsync(message, cancellationToken); await this.Credentials.ProcessHttpRequestAsync(message, cancellationToken).ConfigureAwait(false);
foreach (var _header in message.Headers) foreach (var _header in message.Headers)
{ {

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,7 @@ namespace k8s
/// </param> /// </param>
public static async Task<List<object>> LoadAllFromStreamAsync(Stream stream, Dictionary<String, Type> typeMap) { public static async Task<List<object>> LoadAllFromStreamAsync(Stream stream, Dictionary<String, Type> typeMap) {
var reader = new StreamReader(stream); var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync(); var content = await reader.ReadToEndAsync().ConfigureAwait(false);
return LoadAllFromString(content, typeMap); return LoadAllFromString(content, typeMap);
} }
@@ -95,13 +95,13 @@ namespace k8s
public static async Task<T> LoadFromStreamAsync<T>(Stream stream) { public static async Task<T> LoadFromStreamAsync<T>(Stream stream) {
var reader = new StreamReader(stream); var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync(); var content = await reader.ReadToEndAsync().ConfigureAwait(false);
return LoadFromString<T>(content); return LoadFromString<T>(content);
} }
public static async Task<T> LoadFromFileAsync<T> (string file) { public static async Task<T> LoadFromFileAsync<T> (string file) {
using (FileStream fs = File.OpenRead(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="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" 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" /> <PackageReference Include="Moq" Version="4.13.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" /> <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />

View File

@@ -1,5 +1,6 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using k8s.Exceptions; using k8s.Exceptions;
using k8s.KubeConfigModels; using k8s.KubeConfigModels;
using Xunit; using Xunit;
@@ -406,6 +407,17 @@ namespace k8s.Tests
AssertConfigEqual(expectedCfg, cfg); 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) private void AssertConfigEqual(K8SConfiguration expected, K8SConfiguration actual)
{ {
Assert.Equal(expected.ApiVersion, actual.ApiVersion); Assert.Equal(expected.ApiVersion, actual.ApiVersion);