P C
P C

Reputation: 861

Wrong exit code of a Windows process (C#/C++/etc.)?

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

Answers (1)

P C
P C

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 SafeHandles 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

Related Questions