Await outstanding async work in .NET. (#6993)

The Pulumi .NET SDK does not currently await all outstanding asynchronous
work associated with a Pulumi program. Because all relevant asynchronous
work is created via the Pulumi SDK, we can track this asynchronous work
and ensure that it has all completed prior to returning from
`Deployment.RunAsync`.

The implementation here is simpler than that in #6983, and re-uses the
existing support for tracking outstanding RPCs. If this proves to
negatively impact performance (which is a very real possibility for
programs that create many `Output` instances), we can simplify this
using a semaphore and a counter (essentially Go's `sync.WaitGroup`).

This fixes the .NET portion of #3991.
This commit is contained in:
Pat Gavlin 2021-05-12 13:23:47 -07:00 committed by GitHub
parent be8183180d
commit bd18384038
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 70 additions and 14 deletions

View file

@ -12,17 +12,21 @@
### Bug Fixes
- [auto/dotnet] - Disable Language Server Host logging and checking appsettings.json config
[#7023](https://github.com/pulumi/pulumi/pull/7023)
- [auto/dotnet] - Disable Language Server Host logging and checking appsettings.json config
[#7023](https://github.com/pulumi/pulumi/pull/7023)
- [auto/python] - Export missing `ProjectBackend` type
[#6984](https://github.com/pulumi/pulumi/pull/6984)
- [auto/python] - Export missing `ProjectBackend` type
[#6984](https://github.com/pulumi/pulumi/pull/6984)
- [sdk/nodejs] - Fix noisy errors.
[#6995](https://github.com/pulumi/pulumi/pull/6995)
- [sdk/nodejs] - Fix noisy errors.
[#6995](https://github.com/pulumi/pulumi/pull/6995)
- Config: Avoid emitting integers in objects using exponential notation.
[#7005](https://github.com/pulumi/pulumi/pull/7005)
- Config: Avoid emitting integers in objects using exponential notation.
[#7005](https://github.com/pulumi/pulumi/pull/7005)
- [codegen/python] - Fix issue with lazy_import affecting pulumi-eks
[#7024](https://github.com/pulumi/pulumi/pull/7024)
- [codegen/python] - Fix issue with lazy_import affecting pulumi-eks
[#7024](https://github.com/pulumi/pulumi/pull/7024)
- Ensure that all outstanding asynchronous work is awaited before returning from a .NET
Pulumi program.
[#6993](https://github.com/pulumi/pulumi/pull/6993)

View file

@ -1,6 +1,7 @@
// Copyright 2016-2021, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Pulumi.Testing;
using Pulumi.Tests.Mocks;
@ -85,5 +86,24 @@ namespace Pulumi.Tests
tcs.SetResult(1);
await runTaskOne;
}
[Fact]
public async Task RunWaitsForOrphanedOutput()
{
var result = 0;
var tcs = new TaskCompletionSource<int>();
var runTaskOne = Deployment.CreateRunnerAndRunAsync(
() => new Deployment(new MockEngine(), new MockMonitor(new MyMocks()), null),
runner => runner.RunAsync(() =>
{
Output.Create(tcs.Task).Apply(i => result = i);
return Task.FromResult((IDictionary<string, object?>)new Dictionary<string, object?>());
}, null));
tcs.SetResult(42);
await runTaskOne;
Assert.Equal(42, result);
}
}
}

View file

@ -17,8 +17,12 @@ namespace Pulumi.Tests
private static async Task Run(Func<Task> func, bool dryRun)
{
var mock = new Mock<IDeployment>(MockBehavior.Strict);
var runner = new Mock<IRunner>(MockBehavior.Strict);
runner.Setup(r => r.RegisterTask(It.IsAny<string>(), It.IsAny<Task>()));
var mock = new Mock<IDeploymentInternal>(MockBehavior.Strict);
mock.Setup(d => d.IsDryRun).Returns(dryRun);
mock.Setup(d => d.Runner).Returns(runner.Object);
Deployment.Instance = new DeploymentInstance(mock.Object);
await func().ConfigureAwait(false);

View file

@ -89,9 +89,13 @@ namespace Pulumi.Tests
// Arrange
Output<IDictionary<string, object?>>? outputs = null;
var runner = new Mock<IRunner>(MockBehavior.Strict);
runner.Setup(r => r.RegisterTask(It.IsAny<string>(), It.IsAny<Task>()));
var mock = new Mock<IDeploymentInternal>(MockBehavior.Strict);
mock.Setup(d => d.ProjectName).Returns("TestProject");
mock.Setup(d => d.StackName).Returns("TestStack");
mock.Setup(d => d.Runner).Returns(runner.Object);
mock.SetupSet(content => content.Stack = It.IsAny<Stack>());
mock.Setup(d => d.ReadOrRegisterResource(It.IsAny<Stack>(), It.IsAny<bool>(),
It.IsAny<Func<string, Resource>>(), It.IsAny<ResourceArgs>(), It.IsAny<ResourceOptions>()));

View file

@ -138,8 +138,14 @@ namespace Pulumi
{
internal Task<OutputData<T>> DataTask { get; private set; }
internal Output(Task<OutputData<T>> dataTask)
=> DataTask = dataTask;
internal Output(Task<OutputData<T>> dataTask) {
this.DataTask = dataTask;
if (Deployment.TryGetInternalInstance(out var instance))
{
instance.Runner.RegisterTask("Output<>", dataTask);
}
}
internal async Task<T> GetValueAsync()
{

View file

@ -2,6 +2,7 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Pulumi.Testing;
@ -57,6 +58,18 @@ namespace Pulumi
}
}
internal static bool TryGetInternalInstance([NotNullWhen(true)] out IDeploymentInternal? instance)
{
if (_instance.Value != null)
{
instance = _instance.Value.Internal;
return true;
}
instance = null;
return false;
}
internal static IDeploymentInternal InternalInstance
=> Instance.Internal;

View file

@ -6,6 +6,7 @@ using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Serilog;
using Pulumi.Testing;
namespace Pulumi
@ -188,7 +189,11 @@ namespace Pulumi
Func<Deployment> deploymentFactory,
Func<IRunner, Task<int>> runAsync)
{
// Serilog.Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger();
var enableVerboseLogging = Environment.GetEnvironmentVariable("PULUMI_DOTNET_LOG_VERBOSE");
if (enableVerboseLogging != null && enableVerboseLogging != "")
{
Serilog.Log.Logger = new Serilog.LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger();
}
Serilog.Log.Debug("Deployment.Run called.");
Serilog.Log.Debug("Creating new Deployment.");