Is there any way to detect whether a user or the system closes a console application? I support Ctrl-C for closing by a user but since the system provides the ability to simply close the console window I can not clean up my application which is important since it performs regular tasks that should be terminated cleanly. The same goes for system shutdown.
I tried
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading
but that does not fire when the window is closed.
Try using IApplicationLifetime..
Process shuts down before stopping event executes sometimes but I guess it is fixed in 1.0.3
http://shazwazza.com/post/aspnet-core-application-shutdown-events/
This seems to be an ASP.NET Core feature. Are you sure this can be used in a .NET Core console application?
Sorry, my mistake, missed the ".NET Core Console only" part of your question. Isn't closing console window directly same as killing the process since console windows do not have message loops? Maybe, you may create an Kestrel server instance without injecting MVC and manage your requirements (the way I suggested above) through startup class..
CC @AlexGhiondea
@nicolasr75 have you considered using https://msdn.microsoft.com/en-us/library/system.appdomain.processexit(v=vs.110).aspx?
@AlexGhiondea yes I considered that but according to
https://blogs.msdn.microsoft.com/dotnet/2016/02/10/porting-to-net-core/
AppDomains aren't part of .NET Core. The blog post recommends using AssemblyLoadContext but that doesn't seem to work.
@nicolasr75 we have brought back quite a few APIs. You can check http://apisof.net for a recent list of API availability.
With the recent set of APIs we have brought back,ProcessExit
exists on AppDomain
.
Can you try that?
Is this available in .NET Core 1.1?
https://apisof.net/catalog/System.AppDomain
says System.Runtime.Extensions, Version=4.2.0.0 is needed.
Nuget gets me 4.3.0.0 but I can't find it in namespace System.
@nicolasr75 it is not in .NET Core 1.1... It is in .NET Core 1.2 though.
Ok, I will set up a project with the .NET Core dev feed on a test PC after Christmas.
@AlexGhiondea
Unfortunately I don't know how to correctly set it up.
I downloaded and installed .NET Core Runtime from here:
https://github.com/dotnet/core-setup/blob/master/README.md
Trying to run _dotnet new_ I was asked to install .NET SDK and was pointed to
https://www.microsoft.com/net/core#windowscmd
_Programs and features_ now shows two things installed
.NET Core 1.1.0 - SDK 1.0.0 Preview 2.1-003177
.NET Core 1.2.0 - Runtime
This is my project.json for a simple command line application:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {},
"frameworks": {
"netcoreapp1.2": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
},
"System.Runtime.Extensions": "4.3.0"
},
"imports": "dnxcore50"
}
}
}
My code:
using System;
using System.Threading;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
System.AppDomain.ProcessExit += (s, e) => Console.WriteLine("Process exiting");
Console.WriteLine("Hello World!");
while(true)
{
string s = Console.ReadLine();
if(s == "q")
break;
System.Threading.Thread.Sleep(100);
}
}
}
}
Running _dotnet restore_ works fine, _dotnet build_ gives me
The type or namespace name 'AppDomain' does not exist in the namespace 'System' (are you missing an assembly reference?)
Any idea what I'm doing wrong?
@nicolasr75 I think you are using .NET 1.1. If you want to use the newer APIs you need to update the version of `System.Runtime.Extensions' to a newer one (i.e. 4.4.0-beta-24903-02) that we publish on the dotnet.myget.org feed.
Talk about a throwback. Cut my teeth on .NET 1.1 way back in 2003.
I meant .NET Core 1.1 :).
@AlexGhiondea Sorry to bother you again but I still cannot get this to run. My current project.json is like this:
{
"name:": "ConsoleTest",
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {},
"frameworks": {
"netcoreapp1.2": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
,
"System.Runtime.Extensions": "4.4.0-*"
}
}
}
}
.NET Core installations are still as shown in my post above. In my code I changed a single line:
System.AppDomain.CurrentDomain.ProcessExit += (s, e) => Console.WriteLine("Process exiting");
I was missing the fact that we need to use the static CurrentDomain property. This restores and builds successfully.
To have the dev feed I use a local nuget.config like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear/>
<add key="myget.org dotnet-buildtools" value="https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json" />
<add key="myget.org dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="nuget.org" value="https://www.nuget.org/api/v2/" />
</packageSources>
</configuration>
Furthermore notice that I use netcoreapp1.2 in project.json but Microsoft.NETCore.App 1.1.0. I'm not sure whether this is correct.
Executing _dotnet run_ fails with
Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly '
System.Runtime, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
The located assembly's manifest definition does not match the assembly reference.
(Exception from HRESULT: 0x80131040)
I have no idea where this dependency comes from. Any further idea?
@joperezr do you have any tips on how to figure out where that reference is coming from?
@nicolasr75 unfortunately you will not be able to use the new APIs while referencing Microsoft.NETCore.App 1.1.0. You will need to reference a Microsoft.NETCore.App 1.2.0-beta
@terrajobst is working on writing up how to consume our newest prerelease packages.
@weshaggard thanks for these informations. I updated everything as you say and also switched from project.json to msbuild. Finally it runs. Here is the final code:
using System;
using System.Threading;
namespace ConsoleApplication
{
public class Program
{
public static void Log(string s)
{
using(var sw = System.IO.File.AppendText("log.txt"))
{
sw.WriteLine(s);
}
}
public static void Main(string[] args)
{
System.AppDomain.CurrentDomain.ProcessExit += (s, e) =>
{
Log("Process exiting");
};
Log("Hello World!");
while(true)
{
string s = Console.ReadLine();
if(s == "q")
break;
System.Threading.Thread.Sleep(100);
}
}
}
}
The result is:
ProcessExit is not called when I close the console window :-(
I read that for Windows applications there exists a SetConsoleCtrlHandler API in kernel32. I have not yet tried that myself but from what others report this should do what I need. I had hoped that I don't need to pinvoke and that there were a cross-platform way.
Good to hear that the code is running now. As for the behavior of ProcessExit I'm not sure. @rahku do you know what the expected behavior is for this case?
I don't it will be invoked in case of rude termination of process.
It looks like Linux does by default warn the user if there is still an active process running in a terminal. This is a great feature! Unfortunately Windows does not seem to support this :-( Maybe I could handle this by pinvoking SetConsoleCtrlHandler but in my case I will switch to a service application on Windows anyway which should give me better control. So from my side this issue could be closed. Thanks to all!
@danmosemsft ProcessExit still isn't invoked when close a console window. How else can we detect the closing of a dotnet core console app?
@Workshop2 I did this
```c#
using System;
using System.Diagnostics;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.ProcessExit += (s, ev) =>
{
Console.WriteLine("process exit");
Console.ReadLine();
};
Console.CancelKeyPress += (s, ev) =>
{
Console.WriteLine("Ctrl+C pressed");
Console.ReadLine();
ev.Cancel = true;
};
Console.WriteLine("hit a key");
Console.ReadLine();
}
}
}
```
And I get the same behavior on .NET Core 2.0 as on Desktop. This whether I hit Ctrl-C or I hit enter at the prompt.
Can you give me a repro that behaves differently on Core?
That behavior being, that output occurs thus:
hit a key
process exit
Ctrl+C pressed
Hello @danmosemsft, I am able to hook into CTRL+C
ok, but closing the Window (using the close button) doesn't cause the ProcessExit
to hit.
https://github.com/noobot/noobot/blob/dotnet-standard-port/src/Noobot.Console/Program.cs#L21
@danmosemsft @Workshop2 I can confirm this. I just tried my test code from above (Jan 9th) with .NET Core 2.0 and it still behaves the same, no ProcessExit when closing the window.
On version 2.0.2, I don't get the ctrl-c pressed message, does this fail for anyone else?
I have same issue. .Net Core 2.0 and 2.0.3 app doing AppDomain.CurrentDomain.ProcessExit += ... will never fire on closing window by the X
Console.CancelKeyPress works fine on both
Same boat here. I usually don't like leaving +1-esque messages, But i didn't see any activity for a while.
I'm hooking Console.CancelKeyPress
, AppDomain.CurrentDomain.ProcessExit
and AssemblyLoadContext.Default.Unloading
and while CTRL+C and CTRL+BREAK (SIGINT/SIGTERM, i believe?) are caught - closing the window by the window chrome button does not.
Hi there. It sounds like a bug. It might take a while for us to look at it but perhaps someone motivated might want to debug some meanwhile.
closing the window by the window chrome button does not
Looks like System.Console
doesn't handle the CTRL_CLOSE_EVENT
, it only handles CTRL_C_EVENT
and CTRL_BREAK_EVENT
:
https://github.com/dotnet/corefx/blob/1be850f5708a820b132020cc9628efb972239db9/src/System.Console/src/System/ConsolePal.Windows.cs#L1274-L1283
Hi !
Any newses on the subject, regarding detection of closing console app by pressing X (despite that it is not implemented as @mikedn said ?
Theoretically, you can register them according to:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/707e9ae1-a53f-4918-8ac4-62a1eddb3c4a/detecting-console-application-exit-in-c?forum=csharpgeneral
But this breaks on Linux, because it won't catch x-window terminal close there, if I put an if around the windows-specific code, which is besides the point of using .NET Core.
But nice to know we can at least catch CTRL+C in a managed fashion.
I would need this feature for my Linux-SQL-server profiler:
https://github.com/ststeiger/sql_profiler
I'm porting ExpressProfiler to Linux.
SQL-Server Ops Studio (Linux) doesn't support profiling, yet, AFAIK - which is a bit precarious situation.
Having a Console.Closing - event would ensure I'm always capable of closing any running traces before the application is closed.
If a trace would keep running, that would be a potential performance-meltdown...
@mikedn the code you point to only exists to implement Console.CancelKeyPress, which is documented to only fire for Ctrl-C or Ctrl-Break. So if any change is needed, it seems it would be to fix the ConsolePal.Unix.cs to ignore anything but those two. However on top of that I can't seem to get Ctrl-Break to fire CancelKeyPress on my Linux VM.
I notice in https://github.com/dotnet/corefx/blob/7c4d35b12e16beaf8a10c9d9b304c6b92defcb61/src/Native/Unix/System.Native/pal_console.cpp#L388 we expect Ctrl-Break to cause a SIGQUIT.
[edit: Ctrl-Break on Linux is Ctrl-/ and does give SIGQUIT and fire CancelKeyPress. Also since the pal_console.cpp only listens for SIGQUIT and SIGINT I guess no futher filtering is needed in the managed side. It seems to me the console is working correctly.]
Whether AppDomain.CurrentDomain.ProcessExit and AssemblyLoadContext.Default.Unloading should be fired is a separate issue it seems to me.
So what happens when a .NET Core console application is running on Linux and the system is shutting down? Will it receive a Ctrl+C event? Or a SIGQUIT? Or SIGTERM? And what happens on Windows? Can we somehow gracefully handle that situation or will the .NET Core console application just blindly fall down and disappear when the system is shutting down in an ordered way? I need to ensure that whatever my app does will be suspended normally so that no data gets lost.
PS: I've implemented the Console.CancelKeyPress
event handler. When I close the console window of my application, it's gone immediately. The work (incl. the delay) after the Ctrl+C handling does not happen. When I close the console window of a running ASP.NET Core application however, it visibly performs more work and properly quits. I want that behaviour for my app. No, I need it.
I finally got around to investigate this thing and find the responsible ASP.NET Core code. And I did find it. Just look at the AttachCtrlcSigtermShutdown
function in Microsoft.AspNetCore.Hosting.WebHostExtensions
. Here's the code. And it's used in the RunAsync
method.
The most important parts:
Console.CancelKeyPress
is registered as usual. It runs a Shutdown method and cancels the immediate event.AppDomain.CurrentDomain.ProcessExit
is also registered with the same Shutdown method. No cancellation here.This allowed me to properly end work in a .NET Core 2.1 console application when clicking on the console window close button, just the same as pressing Ctrl+C.
I haven't tested this on Linux, but since ASP.NET Core uses exactly this code, I expect it to work for SIGTERM as well. And that would resolve this issue for me.
Yay, that's some great news, things are going forward :) thank you for the
investigation!
czw., 25 paź 2018 o 00:16 Yves Goergen notifications@github.com
napisał(a):
I finally got around to investigate this thing and find the responsible
ASP.NET Core code. And I did find it. Just look at the
AttachCtrlcSigtermShutdown function in
Microsoft.AspNetCore.Hosting.WebHostExtensions. Here's the code
https://github.com/aspnet/Hosting/blob/master/src/Microsoft.AspNetCore.Hosting/WebHostExtensions.cs#L134.
And it's used in the RunAsync method
https://github.com/aspnet/Hosting/blob/master/src/Microsoft.AspNetCore.Hosting/WebHostExtensions.cs#L87
.The most important parts:
- Console.CancelKeyPress is registered as usual. It runs a Shutdown
method and cancels the immediate event.- AppDomain.CurrentDomain.ProcessExit is also registered with the same
Shutdown method. No cancellation here.- On either event, a cancellation token is set so that the calling
code knows it should now stop and shut down. Then a wait handle is waited
for.- This wait handle must be set by the calling code when it is done, so
it's now safe to terminate the process. It should arrive at that point
after the cancellation token has been set, or when the program comes to an
end for any other reason.
Important: This wait handle must not be disposed or there will be an
exception in the ProcessExit event and Visual Studio won't help you with
that one! (I figured it out by looking at my code more closely.)This allowed me to properly end work in a .NET Core 2.1 console
application when clicking on the console window close button, just the same
as pressing Ctrl+C.I haven't tested this on Linux, but since ASP.NET Core uses exactly this
code, I expect it to work for SIGTERM as well. And that would resolve this
issue for me.—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/dotnet/coreclr/issues/8565#issuecomment-432847266,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABfg0LWd4sLg2BvET1yU0c3aS8XP1tLpks5uoObLgaJpZM4LJIwV
.
--
We define the boundaries of reality;
they don't define us
-- Teferi
@ygoe Great news, but sorry I don't fully get it. Could you show a skeleton of your console app, especially the Main method?
This is an extract of my code, not verified by a compiler:
public async Task Main()
{
var done = new ManualResetEventSlim(false);
using (var shutdownCts = new CancellationTokenSource())
{
try
{
AttachCtrlcSigtermShutdown(shutdownCts, done);
// TODO: Start your tasks here
Console.WriteLine("Application is running. Press Ctrl+C to shut down.");
await shutdownCts.Token.WaitAsync();
Console.WriteLine("Application is shutting down...");
// TODO: Stop your tasks here
}
finally
{
done.Set();
}
}
}
// Based on Microsoft.AspNetCore.Hosting.WebHostExtensions.AttachCtrlcSigtermShutdown
private void AttachCtrlcSigtermShutdown(CancellationTokenSource cts, ManualResetEventSlim resetEvent)
{
void Shutdown()
{
try
{
cts.Cancel();
}
catch (ObjectDisposedException)
{
}
resetEvent.Wait();
};
AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => Shutdown();
Console.CancelKeyPress += (sender, eventArgs) =>
{
Shutdown();
// Don't terminate the process immediately, wait for the Main thread to exit gracefully.
eventArgs.Cancel = true;
};
}
The referenced original ASP.NET source code has some more checks and stuff that I thought weren't necessary in my case.
@ygoe Thanks. This indeed solves the issue. I had tried similar code in a .NET 4.X application and interestingly it does not work there! This pattern should really be documented somewhere, maybe in the docs for CancelKeyPress? Another interesting aspect: when I place a breakpoint at
Console.WriteLine("Application is shutting down...");
I can see that the system gives me 5 seconds to clean up before the process is terminated. This should be taken into consideration in the individual cleanup strategy.
Oh, good to know that it's just 5 seconds. I hadn't noticed that yet but can reproduce it. I'm trying to find out if there's a way to request more time. I think I remember Windows services can do that. But I'm having trouble following the AppDomain.ProcessExit
event in the source code. It just redirects to some AppContext.ProcessExit
event which I cannot find source code of. I was hoping to find more information when I see the native implementation of things.
Alright, I'm stuck. From what I can find, AppDomain.ProcessExit
redirects to AppContext.ProcessExit
. AppContext
in its constructor registers its own private OnProcessExit
as event handler for AppDomain.ProcessExit
and that method raises the AppContext.ProcessExit
event. Looks like a reference loop to me, but nobody actually starts it. Can somebody with more knowledge of the .NET Core source code please explain why this black magic actually does anything at all, and why it is somewhat predictable and even useful? Or better yet, what is the underlying native code that raises the ProcessExit
events?
Update: Seems it's not possible to prolong this timeout. Source – Note how the information has presumably been removed from the official docs.
It would also be interesting to see what happens on system shutdown, regarding this timeout, not interactively closing the console window. This would be interesting for background service scenarios. Also, what happens on Linux, again, as a background service (detached / daemon).
@ygoe After experimenting with a variation of your example, I noticed that a Ctrl+C causes
Shutdown() to run, then when the main thread wakes up from the wait and exits, the ProcessExit
event gets fired and causes Shutdown() to run the second time. And because the main thread
has left the using block, the cts is already disposed and causes the exception.
I tried putting a "if (resetEvent.IsSet) return" at the top of Shutdown() and the example
code behaved much better. (At least on the Mac, the app stopped throwing an error onto
the console window.)
I guess I was a little surprised that a SIGTERM and a normal exit both caused the ProcessExit
event to fire.
Net.Core 3.0 is coming. Are there some news on this subject?
@jeffschw do you guys own this part?
I just want to execute some cleanup before my program exits. I hope this issue gets resolved before .NET 5.
Moving to 5.0 milestone just to do a quick check to see if there is anything that we want/need to do in here for 5.0 as from the discussion above it is not clear yet what works and what doesn't.
Seems related to https://github.com/dotnet/runtime/issues/36089
@ricardoboss: Yea, me too.
@joperezr;
In a nutshell: I have a command line SQL-Server profiler in .NET Core based on ExpressProfiler.
And it needs to stop the profiling (=call a stored procedure) when the command-line-program is closed (via quit or CTRL+C or sigkill or system shutdown/ssh logout or exception or threadexception).
Otherwise, the profiling keeps continuing ad infinitum, and slows down production machines...
This was not possible with 2.1 & 2.,2, haven't tried in 3.1.
I'm still using my above code (4 Nov 2018) with .NET Core 3.1. And after digging through lots of WebHost and other host code from the framework, I know that it's still used there as well. So there might not be a ready-to-use API for this, but the code shown above solves the problem completely.
On Windows you have about 3 seconds before the OS kills the process. No way around that. On Linux with Systemd, you can configure this and it defaults to 90 seconds. When using the application shutdown behaviour that's built into the hosts (like WebHost), you can use additional configuration to prolong the internal default of 5 seconds before hosted services get aborted. This is another thing though and unrelated to what the OS does.
From my testing, it looks like ProcessExit
is correctly invoked when a terminal window is closed on Windows (which sends the CTRL_CLOSE_EVENT
) on 5.0. A few things to watch out for (repeating some things @ygoe has already pointed out):
ProcessExit
runs - so don't expect to see output from Console.WriteLine
after closing the window.ProcessExit
handler will not run. You can get around this like ASP.NET does, by installing a CancelKeyPress
handler that sets Cancel = true
(preventing the OS from tearing down the process immediately after the handler runs) and initiates an asynchronous shutdown path.WriteLine
with the shutdown logic that should run on Ctrl+C or window close.Shutdown is not handled correctly - that's tracked by https://github.com/dotnet/runtime/issues/36089.
On Unix, it looks like closing a terminal window sends SIGHUP
which we do not handle at all.
A few questions:
ProcessExit
handler to be called if a process is shut down by an unhandled Ctrl+C? (This would be a breaking change for folks who intentionally have independent shutdown paths for the two events, so I think we want to keep the current behavior).ProcessExit
handler on Unix when receiving a SIGHUP
?One datapoint might be what Mono does.
Wasn't SIGHUP the signal that Systemd sends when a service should be reloaded? I have no experience with Linux desktop though, so never really use a terminal window, only things like PuTTY.
@ygoe:
That's only a convention, and it is by no means a general rule. The primary and documented purpose of SIGHUP is still to signal the fact that the terminal connection has been severed. For that reason, the default action for any program upon receipt of the SIGHUP signal is still to terminate, even if the process is a daemon.
Since SIGHUP is meaningless for a daemon anyway, why not just reuse that?
Right, so that's what happened, and as a result, the convention today is indeed for daemons to re-read their configuration file when they receive SIGHUP.
So, while many services will trigger a reload when receiving a SIGHUP, some might as well ignore the signal or terminate.
(Source: https://unix.stackexchange.com/questions/239599/does-a-service-restart-send-a-hup)
Actually, systemd will look into the ExecReload= option in the [Service] section in the
(e.g. located at /usr/lib/systemd/system/nginx.service on my system):
From nginx(8)
:
-s signal Send a signal to the master process. The argument signal
can be one of: stop, quit, reopen, reload. The following
table shows the corresponding system signals:
stop SIGTERM
quit SIGQUIT
reopen SIGUSR1
reload SIGHUP
(Source: https://superuser.com/questions/710986/how-to-reload-nginx-systemctl-or-nginx-s/953901#953901)
SIGTERM - politely ask a process to terminate. It shall terminate gracefully, cleaning up all resources (files, sockets, child processes, etc.), deleting temporary files and so on.
SIGQUIT - a more forceful request
Also about
I have no experience with Linux desktop though,
Use KDE Neon to get a nice Ubuntu 20.04 based Linux Desktop with KDE Plasma . ;)
It looks to me like mono doesn't handle SIGHUP
either (or SIGTERM
for that matter, which is what 'docker stop' sends to linux containers).
Most helpful comment
@danmosemsft @Workshop2 I can confirm this. I just tried my test code from above (Jan 9th) with .NET Core 2.0 and it still behaves the same, no ProcessExit when closing the window.