Skip to content

Commit 3e6815a

Browse files
authored
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.
1 parent af74130 commit 3e6815a

File tree

8 files changed

+32
-25
lines changed

8 files changed

+32
-25
lines changed

src/KubernetesClient/Kubernetes.WebSocket.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ public partial class Kubernetes
273273
{
274274
// Copy the default (credential-related) request headers from the HttpClient to the WebSocket
275275
HttpRequestMessage message = new HttpRequestMessage();
276-
await this.Credentials.ProcessHttpRequestAsync(message, cancellationToken);
276+
await this.Credentials.ProcessHttpRequestAsync(message, cancellationToken).ConfigureAwait(false);
277277

278278
foreach (var _header in message.Headers)
279279
{

src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public static async Task<KubernetesClientConfiguration> BuildConfigFromConfigFil
100100
throw new NullReferenceException(nameof(kubeconfig));
101101
}
102102

103-
var k8SConfig = await LoadKubeConfigAsync(kubeconfig, useRelativePaths);
103+
var k8SConfig = await LoadKubeConfigAsync(kubeconfig, useRelativePaths).ConfigureAwait(false);
104104
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
105105

106106
return k8SConfiguration;
@@ -139,7 +139,7 @@ public static async Task<KubernetesClientConfiguration> BuildConfigFromConfigFil
139139

140140
kubeconfig.Position = 0;
141141

142-
var k8SConfig = await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfig);
142+
var k8SConfig = await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfig).ConfigureAwait(false);
143143
var k8SConfiguration = GetKubernetesClientConfiguration(currentContext, masterUrl, k8SConfig);
144144

145145
return k8SConfiguration;
@@ -486,7 +486,7 @@ public static async Task<K8SConfiguration> LoadKubeConfigAsync(string kubeconfig
486486
{
487487
var fileInfo = new FileInfo(kubeconfigPath ?? KubeConfigDefaultLocation);
488488

489-
return await LoadKubeConfigAsync(fileInfo, useRelativePaths);
489+
return await LoadKubeConfigAsync(fileInfo, useRelativePaths).ConfigureAwait(false);
490490
}
491491

492492
/// <summary>
@@ -517,7 +517,7 @@ public static async Task<K8SConfiguration> LoadKubeConfigAsync(FileInfo kubeconf
517517

518518
using (var stream = kubeconfig.OpenRead())
519519
{
520-
var config = await Yaml.LoadFromStreamAsync<K8SConfiguration>(stream);
520+
var config = await Yaml.LoadFromStreamAsync<K8SConfiguration>(stream).ConfigureAwait(false);
521521

522522
if (useRelativePaths)
523523
{
@@ -547,7 +547,7 @@ public static K8SConfiguration LoadKubeConfig(FileInfo kubeconfig, bool useRelat
547547
/// <returns>Instance of the <see cref="K8SConfiguration"/> class</returns>
548548
public static async Task<K8SConfiguration> LoadKubeConfigAsync(Stream kubeconfigStream)
549549
{
550-
return await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfigStream);
550+
return await Yaml.LoadFromStreamAsync<K8SConfiguration>(kubeconfigStream).ConfigureAwait(false);
551551
}
552552

553553
/// <summary>

src/KubernetesClient/StreamDemuxer.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public StreamDemuxer(WebSocket webSocket, StreamType streamType = StreamType.Rem
5757
/// </summary>
5858
public void Start()
5959
{
60-
this.runLoop = this.RunLoop(this.cts.Token);
60+
this.runLoop = Task.Run(async () => await this.RunLoop(this.cts.Token));
6161
}
6262

6363
/// <inheritdoc/>
@@ -193,9 +193,6 @@ public Stream GetStream(byte? inputIndex, byte? outputIndex)
193193

194194
protected async Task RunLoop(CancellationToken cancellationToken)
195195
{
196-
// This is a background task. Immediately yield to the caller.
197-
await Task.Yield();
198-
199196
// Get a 1KB buffer
200197
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024 * 1024);
201198
// This maps remembers bytes skipped for each stream.

src/KubernetesClient/Watcher.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public Watcher(Func<Task<StreamReader>> streamReaderCreator, Action<WatchEventTy
5757
OnClosed += onClosed;
5858

5959
_cts = new CancellationTokenSource();
60-
_watcherLoop = this.WatcherLoop(_cts.Token);
60+
_watcherLoop = Task.Run(async () => await this.WatcherLoop(_cts.Token));
6161
}
6262

6363
/// <inheritdoc/>
@@ -91,14 +91,11 @@ public class WatchEvent
9191

9292
private async Task WatcherLoop(CancellationToken cancellationToken)
9393
{
94-
// Make sure we run async
95-
await Task.Yield();
96-
9794
try
9895
{
9996
Watching = true;
10097
string line;
101-
_streamReader = await _streamReaderCreator();
98+
_streamReader = await _streamReaderCreator().ConfigureAwait(false);
10299

103100
// ReadLineAsync will return null when we've reached the end of the stream.
104101
while ((line = await _streamReader.ReadLineAsync().ConfigureAwait(false)) != null)
@@ -164,7 +161,7 @@ public static Watcher<T> Watch<T, L>(this Task<HttpOperationResponse<L>> respons
164161
Action onClosed = null)
165162
{
166163
return new Watcher<T>(async () => {
167-
var response = await responseTask;
164+
var response = await responseTask.ConfigureAwait(false);
168165

169166
if (!(response.Response.Content is WatcherDelegatingHandler.LineSeparatedHttpContent content))
170167
{

src/KubernetesClient/WatcherDelegatingHandler.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal class WatcherDelegatingHandler : DelegatingHandler
1919
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
2020
CancellationToken cancellationToken)
2121
{
22-
var originResponse = await base.SendAsync(request, cancellationToken);
22+
var originResponse = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
2323

2424
if (originResponse.IsSuccessStatusCode)
2525
{
@@ -47,18 +47,18 @@ public LineSeparatedHttpContent(HttpContent originContent)
4747

4848
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
4949
{
50-
_originStream = await _originContent.ReadAsStreamAsync();
50+
_originStream = await _originContent.ReadAsStreamAsync().ConfigureAwait(false);
5151

5252
StreamReader = new PeekableStreamReader(_originStream);
5353

54-
var firstLine = await StreamReader.PeekLineAsync();
54+
var firstLine = await StreamReader.PeekLineAsync().ConfigureAwait(false);
5555

5656
var writer = new StreamWriter(stream);
5757

5858
// using (writer) // leave open
5959
{
60-
await writer.WriteAsync(firstLine);
61-
await writer.FlushAsync();
60+
await writer.WriteAsync(firstLine).ConfigureAwait(false);
61+
await writer.FlushAsync().ConfigureAwait(false);
6262
}
6363
}
6464

@@ -94,7 +94,7 @@ public override Task<string> ReadLineAsync()
9494
}
9595
public async Task<string> PeekLineAsync()
9696
{
97-
var line = await ReadLineAsync();
97+
var line = await ReadLineAsync().ConfigureAwait(false);
9898
_buffer.Enqueue(line);
9999
return line;
100100
}

src/KubernetesClient/Yaml.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class Yaml {
2929
/// </param>
3030
public static async Task<List<object>> LoadAllFromStreamAsync(Stream stream, Dictionary<String, Type> typeMap) {
3131
var reader = new StreamReader(stream);
32-
var content = await reader.ReadToEndAsync();
32+
var content = await reader.ReadToEndAsync().ConfigureAwait(false);
3333
return LoadAllFromString(content, typeMap);
3434
}
3535

@@ -95,13 +95,13 @@ public static List<object> LoadAllFromString(String content, Dictionary<String,
9595

9696
public static async Task<T> LoadFromStreamAsync<T>(Stream stream) {
9797
var reader = new StreamReader(stream);
98-
var content = await reader.ReadToEndAsync();
98+
var content = await reader.ReadToEndAsync().ConfigureAwait(false);
9999
return LoadFromString<T>(content);
100100
}
101101

102102
public static async Task<T> LoadFromFileAsync<T> (string file) {
103103
using (FileStream fs = File.OpenRead(file)) {
104-
return await LoadFromStreamAsync<T>(fs);
104+
return await LoadFromStreamAsync<T>(fs).ConfigureAwait(false);
105105
}
106106
}
107107

tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
3434
<PackageReference Include="xunit" Version="2.4.1" />
3535
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
36+
<PackageReference Include="Xunit.StaFact" Version="0.3.18" />
3637
<PackageReference Include="Moq" Version="4.13.1" />
3738

3839
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />

tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.IO;
22
using System.Linq;
3+
using System.Threading;
34
using k8s.Exceptions;
45
using k8s.KubeConfigModels;
56
using Xunit;
@@ -406,6 +407,17 @@ public void LoadKubeConfigStream()
406407
AssertConfigEqual(expectedCfg, cfg);
407408
}
408409

410+
/// <summary>
411+
/// Ensures Kube config file can be loaded from within a non-default <see cref="SynchronizationContext"/>.
412+
/// The use of <see cref="UIFactAttribute"/> ensures the test is run from within a UI-like <see cref="SynchronizationContext"/>.
413+
/// </summary>
414+
[UIFact]
415+
public void BuildConfigFromConfigFileInfoOnNonDefaultSynchronizationContext()
416+
{
417+
var fi = new FileInfo("assets/kubeconfig.yml");
418+
KubernetesClientConfiguration.BuildConfigFromConfigFile(fi, "federal-context", useRelativePaths: false);
419+
}
420+
409421
private void AssertConfigEqual(K8SConfiguration expected, K8SConfiguration actual)
410422
{
411423
Assert.Equal(expected.ApiVersion, actual.ApiVersion);

0 commit comments

Comments
 (0)