Is there a way to gracefully shut-down a DOTNET CORE application which is running in DOCKER? If yes, which event I should listen?
All I want is upon cancellation request I would like to pass my cancellation token/s to current methods and postpone the shut-down while they are working.
Looking for a sample code, reference link etc. which are relevant to dotnet core and not generic info
UPDATE
This question is not a duplicate of docker container exits immediately even with Console.ReadLine() in a .net core console application because I'm not having an immediate exit issue. I need to tap into event something like Windows.SystemsEvents.SessionEnding and relying on Console.CancelKeyPress and/or implementing WebHostBuilder() doesn't fit the bill.
In .NET Core 2.0, you can use the AppDomain.CurrentDomain.ProcessExit event, which works fine on Linux in Docker. AssemblyLoadContext.Default.Unloading probably works as well, even before .NET Core 2.0.
System.Console has an event called CancelKeyPress. I believe this is fired when a sigint event is passed into dotnet.
System.Console.CancelKeyPress += (s,e) => { /* do something here */};
Using 2.0.0-preview2-006497 I did some testing, and now the AssemblyLoadContext.Default.Unloading is fired when Docker sends a SIGTERM/SIGINT to the container.
Example code looks like:
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx =>
{
// code here...
};
See also this issue for some details: https://github.com/aspnet/Hosting/issues/870
If your container is running in Linux then Loader.AssemblyLoadContext.Default.Unloading works since it can trap the SIGTERM signal, however Windows do not have the equivalent mechanism for this(dotnet issue). Here's an answer to handle shutdown notification in Windows by using SetConsoleCtrlHandler originally from gist
namespace Routeguide
{
using System;
using System.Threading;
...
class Program
{
[DllImport("Kernel32")]
internal static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool Add);
internal delegate bool HandlerRoutine(CtrlTypes ctrlType);
internal enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
static void Main(string[] args)
{
// do the server starting code
Start();
....
var shutdown = new ManualResetEvent(false);
var complete = new ManualResetEventSlim();
var hr = new HandlerRoutine(type =>
{
Log.Logger.Information($"ConsoleCtrlHandler got signal: {type}");
shutdown.Set();
complete.Wait();
return false;
});
SetConsoleCtrlHandler(hr, true);
Console.WriteLine("Waiting on handler to trigger...");
shutdown.WaitOne();
Console.WriteLine("Stopping server...");
// do the server stopping code
Stop();
complete.Set();
GC.KeepAlive(hr);
}
}
}
Related
I'm trying to make a simple console app client (starter.exe) on c# .NET Framework 4.6 to make a WireGuard protocol based connection using Wireguard source code.
What is done:
Downloaded wireguard source code from here: git://git.zx2c4.com/wireguard-windows
Successfuly built Tunnel.dll in ..\embeddable-dll-service\amd64\tunnel.dll via build.bat
Created a project in Visual Studio 2015.using the c# code from ..\embeddable-dll-service\csharp
Starting from here some strange thing are happenning:
if launching starter.exe \service <path to *.conf> I receive the
error
Service run error: The service process could not connect to the
service controller.
if launching starter.exe without parameters everything works fine until I remove the if{} block:
Unhandled Exception: System.ComponentModel.Win32Exception: The service
did not respond to the start or control request in a timely fashion
at WireGuardTunnel.Service.Add(String configFile) in
D:\Depository\BitBucket\WireGuard_Tunnel_Repository\WireGuardTunnel_proj\Launcher\Service.cs:line
69 at WireGuardTunnel.Program.Main(String[] args) in
D:\Depository\BitBucket\WireGuard_Tunnel_Repository\WireGuardTunnel_proj\Launcher\Program.cs:line
83
That means even if the code in if{} block is not executed it influencese somehow the application behaviour.
Next, as I want to make my app work with parameters I solved the
issue by removing return afer Service.Run and passing args[1] to Service.Add(args[1]). It works OK, but I have an extra log line (the first one due to Service.Run perpetual error described above) in the log:
Service run error: The service process could not connect to the
service controller. 235660: [TUN] [chicago4] Watching network
interfaces 245661: [TUN] [chicago4] Resolving DNS names
245661: [TUN] [chicago4] Creating Wintun interface 225660: [TUN]
[chicago4] Starting WireGuard/0.3.1 (Windows 6.1.7601; amd64)
So finally the questions:
Why Service.Run(confFile) does not work
Why Service.Run(confFile) influences the Service.Add(confFile)
Why if{} block is executed when I launch starte.exe with no parameters
The original Program.cs without modification:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices;
namespace Tunnel
{
class Program
{
[DllImport("kernel32.dll")]
private static extern bool SetConsoleCtrlHandler(SetConsoleCtrlEventHandler handler, bool add);
private delegate bool SetConsoleCtrlEventHandler(UInt32 signal);
public static void Main(string[] args)
{
string baseDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
string configFile = Path.Combine(baseDirectory, "demobox.conf");
string logFile = Path.Combine(baseDirectory, "log.bin");
if (args.Length == 2 && args[0] == "/service")
{
configFile = args[1];
Service.Run(configFile);
return;
}
try { File.Delete(logFile); } catch { }
Ringlogger log = new Ringlogger(logFile, "GUI");
var logPrintingThread = new Thread(() =>
{
var cursor = Ringlogger.CursorAll;
while (Thread.CurrentThread.IsAlive)
{
var lines = log.FollowFromCursor(ref cursor);
foreach (var line in lines)
Console.WriteLine(line);
Thread.Sleep(300);
}
});
logPrintingThread.Start();
SetConsoleCtrlHandler(delegate
{
Service.Remove(configFile);
Environment.Exit(0);
return true;
}, true);
try
{
Service.Add(configFile);
logPrintingThread.Join();
}
finally
{
Service.Remove(configFile);
}
}
}
}
Bit late to the party but I was having the exact same issue as above and discovered that in order to get everything working correctly you have to have Tunnel.Service.Run("path to config") defined on application initialization either in your main loop or your constructor then you can run Tunnel.Service.Add("path to config", true) which will create the service and start the VPN connection. It's also good practice to destroy the service on close using Tunnel.Service.Remove("path to config", true) as the service will continue to run and you will still be connected to your VPN until it is stopped manually.
I created an ConsoleEventHandler based on the following link described: https://www.meziantou.net/detecting-console-closing-in-dotnet.htm
It works perfectly for me. Currently I wish it can handle SIGTERM from both Windows and Linux. Does anybody has some clue about how to optimize it in C#? The following code needs to be optimized in two parts:
can load Kernel32 in docker. Currently it fails in "System.DllNotFoundException: Unable to load shared library 'Kernel32' or one of its dependencies. "
Support linux system;
The source code:
class Program
{
// https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms686016.aspx
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(SetConsoleCtrlEventHandler handler, bool add);
// https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms683242.aspx
private delegate bool SetConsoleCtrlEventHandler(CtrlType sig);
private enum CtrlType
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6
}
static void Main(string[] args)
{
// Register the handler
SetConsoleCtrlHandler(Handler, true);
// Wait for the event
while (true)
{
Thread.Sleep(50);
}
}
private static bool Handler(CtrlType signal)
{
switch (signal)
{
case CtrlType.CTRL_BREAK_EVENT:
case CtrlType.CTRL_C_EVENT:
case CtrlType.CTRL_LOGOFF_EVENT:
case CtrlType.CTRL_SHUTDOWN_EVENT:
case CtrlType.CTRL_CLOSE_EVENT:
Console.WriteLine("Closing");
// TODO Cleanup resources
Environment.Exit(0);
return false;
default:
return false;
}
}
}
Not sure if you still need an answer for it, but you could either go for any solution mentioned here:
Detect when console application is closing/killed?
Or you could use the Console.CancelKeyPress and AppDomain.CurrentDomain.ProcessExit Events. Both are working on Linux. Cheers!
I have created a console application in C#. How can I program this application so that it will re-start itself after a crash?
If I understand your question correctly, you want to attempt to re-start a console app in the event of a crash. In C# console-apps the method defined as the entry point (usually static void main) is the root of the call stacks in the app. You essentially would need to call that method recursively. You will want to make sure that the app eventually fails if it is in some unintended or unrecoverable state.
For example in the main class:
static int retryCount;
const int numberOfRetries = 3;
static void Main(string[] args)
{
try
{
var theApp = new MyApplicationType(args);
theApp.StartMyAppLogic();
}
catch (ExpectedExceptionType expectThisTypeOfException)
{
thisMethodHandlesExceptions(expectThisTypeOfException);
}
catch (AnotherExpectedExceptionType alsoExpectThisTypeOfException)
{
thisMethodHandlesExceptions(alsoExpectThisTypeOfException);
}
catch (Exception unexpectedException)
{
if(retryCount < numberOfRetries)
{
retryCount++;
Main(args);
}
else
{
throw;
}
}
}
You can use a watchdog to process your monitor and restart it if crashed:
see: What's the best way to watchdog a desktop application?
You can use a windows service instead and set it's recovery options as indicated here: https://serverfault.com/questions/48600/how-can-i-automatically-restart-a-windows-service-if-it-crashes
You can use a scheduled task in task manager to start your application periodically , and set it to only start if previous run has ended:
https://support.microsoft.com/en-us/kb/323527
You could try something like this:
static void Main(string[] args)
{
try
{
// Application code goes here
}
catch (Exception)
{
var applicationPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
Process.Start(applicationPath);
Environment.Exit(Environment.ExitCode);
}
}
Basically, wrap all the code in a try/catch, and if any exceptions occur, the program will retrieve the .exe location with System.Reflection.Assembly.GetExecutingAssembly().Location; and then call Process.Start to run the application again.
You should control your console app from another application (watchdog, sheduler, procmon, servman, ...).
E.g. you can create your console app as a service and control it from service manager.
I have built a windows Service to monitor a few settings on our servers, I have developed quite a few WinForm and WPF apps but I am an absolute newbie when it comes to Windows Services, which is why I resorted to msdn and followed the tutorial on how to create a simple service. Now I can install the service just fine and make it run, but only if I cut some bits and pieces out of the microsoft tutorial.. but I am curious why, when I follow the tutorial, my service gets an unexpected error at startup.
After some testing it seems that the service seems to crash in the onstart method at SetServiceStatus()
public partial class MyService: ServiceBase
{
private static ManualResetEvent pause = new ManualResetEvent(false);
[DllImport("ADVAPI32.DLL", EntryPoint = "SetServiceStatus")]
public static extern bool SetServiceStatus(IntPtr hServiceStatus, SERVICE_STATUS lpServiceStatus);
private SERVICE_STATUS myServiceStatus;
private Thread workerThread = null;
public MyService()
{
InitializeComponent();
CanPauseAndContinue = true;
CanHandleSessionChangeEvent = true;
ServiceName = "MyService";
}
static void Main()
{
// Load the service into memory.
System.ServiceProcess.ServiceBase.Run(MyService());
}
protected override void OnStart(string[] args)
{
IntPtr handle = this.ServiceHandle;
myServiceStatus.currentState = (int)State.SERVICE_START_PENDING;
**SetServiceStatus(handle, myServiceStatus);**
// Start a separate thread that does the actual work.
if ((workerThread == null) || ((workerThread.ThreadState & (System.Threading.ThreadState.Unstarted | System.Threading.ThreadState.Stopped)) != 0))
{
workerThread = new Thread(new ThreadStart(ServiceWorkerMethod));
workerThread.Start();
}
myServiceStatus.currentState = (int)State.SERVICE_RUNNING;
SetServiceStatus(handle, myServiceStatus);
}
}
Now my service seems to run just fine when I comment out the SetServiceStatus() lines. Why does this fail? Is this a rights-issue or am I completely missing the point here?
In general, you shouldn't have to call SetServiceStatus when implementing a managed service using the framework.
That being said, if you do call it, you need to fully initialize the SERVICE_STATUS before using it. You're currently only setting the state, but none of the other variables.
This is suggested in the best practices for SetServiceStatus: "Initialize all fields in the SERVICE_STATUS structure, ensuring that there are valid check-point and wait hint values for pending states. Use reasonable wait hints."
I'm having to back port some software from Windows Mobile 6.5 to Windows CE 5.0, the software currently detects when the unit is in the base unit (ActiveSync running).
I need to know when ActiveSync is running on the unit so that I can prepare the unit to send and receive files.
I've found an article on using PINVOKE methods such as CeRunAppAtEvent but I am clueless on how that would work.
bool terminateDeviceEventThreads = false;
IntPtr handleActiveSyncEndEvent;
while (!terminateDeviceEventThreads)
{
handleActiveSyncEndEvent = NativeMethods.CreateEvent (IntPtr.Zero,
true, false, "EventActiveSync");
if (IntPtr.Zero != handleActiveSyncEndEvent)
{
if (NativeMethods.CeRunAppAtEvent ("\\\\.\\Notifications\\NamedEvents\\EventActiveSync",
(int) NOTIFICATION_EVENT.NOTIFICATION_EVENT_RS232_DETECTED))
{
NativeMethods.WaitForSingleObject (handleActiveSyncEndEvent, 0);
//
NativeMethods.ResetEvent (handleActiveSyncEndEvent);
if (!NativeMethods.CeRunAppAtEvent ("\\\\.\\Notifications\\NamedEvents\\EventActiveSync",
(int) NOTIFICATION_EVENT.NOTIFICATION_EVENT_NONE))
{
break;
}
handleActiveSyncEndEvent = IntPtr.Zero;
}
}
}
The code you have here is waiting on the system notification NOTIFICATION_EVENT_RS232_DETECTED. By using CeRunAppAtEvent (a bit of a misnomer, as it's not going to run an app but instead set an event) they've registered a named system event with the name "EventActiveSync" to be set when the notification occurs.
In essence, when the device is docked, the named system event will get set.
Your code has got some of the wait code in there, but not fully - it's calls WaitForSingleObject, but never looks at the result and then unhooks the event. I'd think it would look more like this
event EventHandler OnConnect = delegate{};
void ListenerThreadProc()
{
var eventName = "OnConnect";
// create an event to wait on
IntPtr #event = NativeMethods.CreateEvent (IntPtr.Zero, true, false, eventName);
// register for the notification
NativeMethods.CeRunAppAtEvent (
string.Format("\\\\.\\Notifications\\NamedEvents\\{0}", eventName),
(int) NOTIFICATION_EVENT.NOTIFICATION_EVENT_RS232_DETECTED);
while(!m_shutdown)
{
// wait for the event to be set
// use a 1s timeout so we don't prevent thread shutdown
if(NativeMethods.WaitForSingleObject(#event, 1000) == 0)
{
// raise an event
OnConnect(this, EventArgs.Empty);
}
}
// unregister the notification
NativeMethods.CeRunAppAtEvent (
string.Format("\\\\.\\Notifications\\NamedEvents\\{0}", eventName),
(int) NOTIFICATION_EVENT.NOTIFICATION_EVENT_NONE);
// clean up the event handle
NativeMethods.CloseHandle(#event);
}
Your app would create a thread that uses this proc at startup and wire up an event handler for the OnConnect event.
FWIW, the SDF has this already done, so it would be something like this in your code:
DeviceManagement.SerialDeviceDetected += DeviceConnected;
...
void DeviceConnected()
{
// handle connection
}
Here's an ActiveSync document on MSDN. A little old but should still be relevant. Also take a look at this
As for the CeRunAppAtEvent you need to create a wrapper to the Native method as below
[DllImport("coredll.dll", EntryPoint="CeRunAppAtEvent", SetLastError=true)]
private static extern bool CeRunAppAtEvent(string pwszAppName, int lWhichEvent);
You can find PInvode resources here and on MSDN