Reputation: 861
Our C# application exits with code 0, even though it is explicitly returning -1 in the code:
internal class Program
{
public int Main()
{
....
return -1;
}
}
The same happened if void Main
was used:
internal class Program
{
public void Main()
{
....
Environment.Exit(-1);
}
}
As other questions on SO suggested it could have been an unhandled CLR/C++/native exception in some other thread. However I've added graceful shutdown of all managed/native threads right before this the last one, but the behavior stayed.
What could be the reason?
Upvotes: 2
Views: 675
Reputation: 861
Turns out this happened because we used JobObjects to make sure that all child process exit when current process exits using this code in C (we actually p-invoked from C#):
HANDLE h = ::CreateJobObject(NULL, NULL);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
::ZeroMemory(&info, sizeof(info));
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
::SetInformationJobObject(h, JobObjectExtendedLimitInformation, &info, sizeof(info));
::AssignProcessToJobObject(h, ::GetCurrentProcess());
...
::CloseHandle(h);
return -1;
This code adds the current process and all its child processes to a job object which will be closed on current process exit.
BUT it has a side-effect when CloseHandle
was invoked it would kill the current process without ever reaching to the line return -1
. And since JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
flag automatically kills all processes there is no way to set a exit code for all processes, so OS exited the process with exit code 0.
In C# we followed standard guidelines to clean-up resources and used SafeHandle
-derived class to make sure that CloseHandle
is invoked and absolutely the same happened - before CLR actually exited it invoked ::CloseHandle
for all SafeHandle
s ignoring the actual return code set both by return value and Environment.Exit
.
However what's even more interesting, is that if an explicit (or not so explicit) call to CloseHandle
is removed in both C# and C++, OS will still close all the handles at the process exit after CLR/CRT exited, and the actual exit code will be returned. So sometimes it is good not to clean-up resources :-) or in another words, until a native ::ExitProcess
is invoked, you can't guarantee that the exit code will be intact.
So to fix this particular issue I could either call AssignProcessToJobObject
whenever a child process is started or removed the explicit (or not so explicit) call to CloseHandle
. I chose the first approach.
Upvotes: 2