Skip to content

Commit 199467e

Browse files
Deadlock on application exit when an error occurs in case of asynchronous Program.main()
1 parent b68095e commit 199467e

File tree

6 files changed

+257
-4
lines changed

6 files changed

+257
-4
lines changed

.github/workflows/main.yml

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
name: CSharpInteractive check
1+
name: CSharpInteractive build
22

33
on: [ push, pull_request ]
44

55
jobs:
6-
build:
7-
6+
7+
ubuntu_build:
8+
89
runs-on: ubuntu-latest
910

1011
steps:
@@ -32,3 +33,63 @@ jobs:
3233

3334
- name: Build
3435
run: dotnet run --project ./Build
36+
37+
mac_build:
38+
39+
runs-on: macos-latest
40+
41+
steps:
42+
- uses: actions/checkout@v4
43+
44+
- name: Setup .NET 6
45+
uses: actions/setup-dotnet@v3
46+
with:
47+
dotnet-version: '6.0.x'
48+
49+
- name: Setup .NET 7
50+
uses: actions/setup-dotnet@v3
51+
with:
52+
dotnet-version: '7.0.x'
53+
54+
- name: Setup .NET 8
55+
uses: actions/setup-dotnet@v3
56+
with:
57+
dotnet-version: '8.0.x'
58+
59+
- name: Setup .NET 9
60+
uses: actions/setup-dotnet@v3
61+
with:
62+
dotnet-version: '9.0.x'
63+
64+
- name: Build
65+
run: dotnet run --project ./Build
66+
67+
windows_build:
68+
69+
runs-on: windows-latest
70+
71+
steps:
72+
- uses: actions/checkout@v4
73+
74+
- name: Setup .NET 6
75+
uses: actions/setup-dotnet@v3
76+
with:
77+
dotnet-version: '6.0.x'
78+
79+
- name: Setup .NET 7
80+
uses: actions/setup-dotnet@v3
81+
with:
82+
dotnet-version: '7.0.x'
83+
84+
- name: Setup .NET 8
85+
uses: actions/setup-dotnet@v3
86+
with:
87+
dotnet-version: '8.0.x'
88+
89+
- name: Setup .NET 9
90+
uses: actions/setup-dotnet@v3
91+
with:
92+
dotnet-version: '9.0.x'
93+
94+
- name: Build
95+
run: dotnet run --project ./Build

CSharpInteractive.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MySampleLib.Tests", "Sample
4444
EndProject
4545
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build", "Samples\MySampleLib\Build\Build.csproj", "{A1213ADD-4AAC-47D6-B96F-111E2650C2D5}"
4646
EndProject
47+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestBuild", "TestBuild\TestBuild.csproj", "{6FDE6A8C-9B88-4EF1-837A-2037B4721E7D}"
48+
EndProject
4749
Global
4850
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4951
Debug|Any CPU = Debug|Any CPU
@@ -90,12 +92,17 @@ Global
9092
{A1213ADD-4AAC-47D6-B96F-111E2650C2D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
9193
{A1213ADD-4AAC-47D6-B96F-111E2650C2D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
9294
{A1213ADD-4AAC-47D6-B96F-111E2650C2D5}.Release|Any CPU.Build.0 = Release|Any CPU
95+
{6FDE6A8C-9B88-4EF1-837A-2037B4721E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
96+
{6FDE6A8C-9B88-4EF1-837A-2037B4721E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
97+
{6FDE6A8C-9B88-4EF1-837A-2037B4721E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
98+
{6FDE6A8C-9B88-4EF1-837A-2037B4721E7D}.Release|Any CPU.Build.0 = Release|Any CPU
9399
EndGlobalSection
94100
GlobalSection(NestedProjects) = preSolution
95101
{4FA6CCBD-6656-41B5-A480-AFCFACB265F3} = {CE315710-ACE8-4CAC-B030-4778257652DB}
96102
{C83ECC4E-3E9F-42DC-AFF2-6DF1BF926DAA} = {CE315710-ACE8-4CAC-B030-4778257652DB}
97103
{74267567-C96C-4A38-A404-C493338DC012} = {C83ECC4E-3E9F-42DC-AFF2-6DF1BF926DAA}
98104
{D1436234-FD1E-430A-8545-7279193F8BD1} = {C83ECC4E-3E9F-42DC-AFF2-6DF1BF926DAA}
99105
{A1213ADD-4AAC-47D6-B96F-111E2650C2D5} = {C83ECC4E-3E9F-42DC-AFF2-6DF1BF926DAA}
106+
{6FDE6A8C-9B88-4EF1-837A-2037B4721E7D} = {CE315710-ACE8-4CAC-B030-4778257652DB}
100107
EndGlobalSection
101108
EndGlobal

CSharpInteractive.sln.DotSettings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@
309309
<s:Boolean x:Key="/Default/UserDictionary/Words/=Enumerables/@EntryIndexedValue">True</s:Boolean>
310310
<s:Boolean x:Key="/Default/UserDictionary/Words/=get_0027s/@EntryIndexedValue">True</s:Boolean>
311311
<s:Boolean x:Key="/Default/UserDictionary/Words/=immutype/@EntryIndexedValue">True</s:Boolean>
312+
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializable/@EntryIndexedValue">True</s:Boolean>
313+
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializables/@EntryIndexedValue">True</s:Boolean>
312314
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializers/@EntryIndexedValue">True</s:Boolean>
313315
<s:Boolean x:Key="/Default/UserDictionary/Words/=inversionofcontrol/@EntryIndexedValue">True</s:Boolean>
314316
<s:Boolean x:Key="/Default/UserDictionary/Words/=multithreading/@EntryIndexedValue">True</s:Boolean>
@@ -331,6 +333,7 @@
331333
<s:Boolean x:Key="/Default/UserDictionary/Words/=testignored/@EntryIndexedValue">True</s:Boolean>
332334
<s:Boolean x:Key="/Default/UserDictionary/Words/=tfms/@EntryIndexedValue">True</s:Boolean>
333335
<s:Boolean x:Key="/Default/UserDictionary/Words/=timestamper/@EntryIndexedValue">True</s:Boolean>
336+
<s:Boolean x:Key="/Default/UserDictionary/Words/=ucrtbase/@EntryIndexedValue">True</s:Boolean>
334337
<s:Boolean x:Key="/Default/UserDictionary/Words/=unlists/@EntryIndexedValue">True</s:Boolean>
335338
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unregister/@EntryIndexedValue">True</s:Boolean>
336339
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unregisters/@EntryIndexedValue">True</s:Boolean>

CSharpInteractive/Core/Environment.cs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public void Exit(int exitCode)
8686
return;
8787
}
8888

89-
System.Environment.Exit(exitCode);
89+
NativeExit(exitCode);
9090
}
9191

9292
public IEnumerable<Text> Trace
@@ -158,4 +158,53 @@ private string GetScriptDirectory()
158158
var scriptDirectory = Path.GetDirectoryName(script);
159159
return !string.IsNullOrWhiteSpace(scriptDirectory) ? scriptDirectory : script;
160160
}
161+
162+
[DllImport("ucrtbase.dll", EntryPoint = "exit")]
163+
private static extern void WindowsNativeExit(int exitCode);
164+
165+
[DllImport("libSystem.dylib", EntryPoint = "exit")]
166+
private static extern void MacNativeExit(int exitCode);
167+
168+
[DllImport("libc.so.6", EntryPoint = "exit")]
169+
private static extern void LinuxNativeExit(int exitCode);
170+
171+
private static void NativeExit(int exitCode)
172+
{
173+
try
174+
{
175+
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
176+
switch (System.Environment.OSVersion.Platform)
177+
{
178+
case PlatformID.Win32S:
179+
case PlatformID.Win32Windows:
180+
case PlatformID.Win32NT:
181+
case PlatformID.WinCE:
182+
case PlatformID.Xbox:
183+
WindowsNativeExit(exitCode);
184+
break;
185+
186+
case PlatformID.Unix:
187+
try
188+
{
189+
LinuxNativeExit(exitCode);
190+
}
191+
catch (DllNotFoundException)
192+
{
193+
MacNativeExit(exitCode);
194+
}
195+
196+
break;
197+
198+
case PlatformID.MacOSX:
199+
MacNativeExit(exitCode);
200+
break;
201+
}
202+
}
203+
catch(Exception error)
204+
{
205+
System.Environment.FailFast("Failed to exit.", error);
206+
}
207+
208+
System.Environment.FailFast("Failed to exit.");
209+
}
161210
}

TestBuild/Program.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.CommandLine;
2+
using HostApi;
3+
using NuGet.Configuration;
4+
using Pure.DI;
5+
using static Pure.DI.Lifetime;
6+
// ReSharper disable HeuristicUnreachableCode
7+
#pragma warning disable CS0162 // Unreachable code detected
8+
9+
WriteLine("Hello");
10+
return await new Composition().Root.RunAsync(CancellationToken.None);
11+
12+
DI.Setup(nameof(Composition))
13+
.Hint(Hint.ThreadSafe, "Off")
14+
.Hint(Hint.Resolve, "Off")
15+
.Root<RootTarget>(nameof(Composition.Root))
16+
17+
.DefaultLifetime(PerResolve)
18+
.Bind().To<RootCommand>()
19+
.Bind().To<Settings>()
20+
21+
.DefaultLifetime(PerBlock)
22+
23+
// Targets
24+
.Bind(Tag.Type).To<MyTarget>();
25+
26+
internal interface ITarget<T>
27+
{
28+
Task<T> RunAsync(CancellationToken cancellationToken);
29+
}
30+
31+
internal interface IInitializable
32+
{
33+
Task InitializeAsync(CancellationToken cancellationToken);
34+
}
35+
36+
internal class RootTarget(
37+
RootCommand rootCommand,
38+
IEnumerable<IInitializable> initializables)
39+
: ITarget<int>
40+
{
41+
public async Task<int> RunAsync(CancellationToken cancellationToken)
42+
{
43+
foreach (var initializable in initializables)
44+
{
45+
await initializable.InitializeAsync(cancellationToken);
46+
}
47+
48+
return await rootCommand.InvokeAsync(Args.ToArray());
49+
}
50+
51+
}
52+
53+
internal class MyTarget(Commands commands)
54+
: IInitializable, ITarget<int>
55+
{
56+
public Task InitializeAsync(CancellationToken cancellationToken) =>
57+
commands.RegisterAsync(this, "Run", "run", "r");
58+
59+
public async Task<int> RunAsync(CancellationToken cancellationToken)
60+
{
61+
await new DotNetBuild()
62+
.WithProject("unknown project")
63+
.BuildAsync(cancellationToken: cancellationToken).EnsureSuccess(failureExitCode: 33);
64+
65+
return 0;
66+
}
67+
68+
}
69+
70+
internal class Commands(RootCommand rootCommand)
71+
{
72+
public Task RegisterAsync<T>(
73+
ITarget<T> target,
74+
string description,
75+
string name,
76+
params string[] aliases)
77+
{
78+
var command = new Command(name, description);
79+
command.SetHandler(ctx => target.RunAsync(ctx.GetCancellationToken()));
80+
foreach (var alias in aliases)
81+
{
82+
command.AddAlias(alias);
83+
}
84+
85+
rootCommand.AddCommand(command);
86+
return Task.CompletedTask;
87+
}
88+
}

TestBuild/TestBuild.csproj

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
9+
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\CSharpInteractive.HostApi\CSharpInteractive.HostApi.csproj"/>
14+
<ProjectReference Include="..\CSharpInteractive\CSharpInteractive.csproj"/>
15+
<PackageReference Include="TeamCity.DotNet.Integration" Version="1.0.33" PrivateAssets="all" GeneratePathProperty="true" ExcludeAssets="All" IncludeAssets="none" />
16+
<Using Include="System"/>
17+
<Using Include="System.Collections.Generic"/>
18+
<Using Include="System.IO"/>
19+
<Using Include="System.Linq"/>
20+
<Using Include="System.Net.Http"/>
21+
<Using Include="System.Threading"/>
22+
<Using Include="System.Threading.Tasks"/>
23+
<Using Include="Host" Static="True"/>
24+
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1"/>
25+
<PackageReference Include="Pure.DI" Version="2.1.53">
26+
<PrivateAssets>all</PrivateAssets>
27+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
28+
</PackageReference>
29+
</ItemGroup>
30+
31+
<PropertyGroup>
32+
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
33+
</PropertyGroup>
34+
35+
<Target Name="GetDependencyTargetPaths">
36+
<ItemGroup>
37+
<MSBuildLoggerFiles Include="$(PKGTeamCity_Dotnet_Integration)\build\_common\msbuild15\*.*"/>
38+
<VSTestLoggerFiles Include="$(PKGTeamCity_Dotnet_Integration)\build\_common\vstest15\*.*"/>
39+
</ItemGroup>
40+
41+
<Copy SourceFiles="@(MSBuildLoggerFiles)" DestinationFolder="$(OutDir)\msbuild"/>
42+
<Copy SourceFiles="@(VSTestLoggerFiles)" DestinationFolder="$(OutDir)\vstest"/>
43+
</Target>
44+
45+
</Project>

0 commit comments

Comments
 (0)