11

Could anyone kindly tell me how to kill child processes when the calling (parent) process is forcefully terminated? By the way, I am not able to change the source code of child application.

I have checked the existing thread in StackOverflow and it seems JobObject is the correct way to do so. However when I tested it (using a console application to call notepad.exe), I found that when the console application exited, Notepad didn't.

I used CreateProcess to spawn the new process.

I have also seen somebody says that setting up a pipe between parent process and child process will do the job, but I have not yet tried it.

If anyone can give me some hints, I would really appreciate it.

Update: WINAPI AssignProcessToJobObject failed to work if without | CREATE_BREAKAWAY_FROM_JOB in CreatProcess. Now it works!

Thank everyone.

Update again:

It's really tricky. I am always confused by Windows API.

Group 1:

Process flag: CREATE_SUSPENDED

JobObject flag: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_SECURITY_RESTRICTED_TOKEN or JOB_OBJECT_SECURITY_NO_ADMIN or JOB_OBJECT_LIMIT_BREAKAWAY_OK

Result: AssingProcessToJobObject fails with Error code 5 Access is denied

Group 2:

Process flag: CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB

JobObject flag: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_SECURITY_RESTRICTED_TOKEN or JOB_OBJECT_SECURITY_NO_ADMIN

Results: AssingProcessToJobObject succeeds, but child process fails to be killed when parent one is killed.

Group 3:

Process flag: CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB

JobObject flag: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE

Results: AssingProcessToJobObject succeeds, and child process is automatically killed when parent one is killed.

Group 4:

Process flag: CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB

JobObject flag: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK

Results: same to Group 3.

The following code uses JobObeject which I copied from http://cboard.cprogramming.com/windows-programming/60561-program-termination.html#post430075

#define _WIN32_WINNT 0x0500
#include <windows.h>

int main(void)
{
    HANDLE                               hJob;
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
    PROCESS_INFORMATION                  pi   = { 0 };
    STARTUPINFO                          si   = { 0 };


    /*
     * Create a job object.
     */
    hJob = CreateJobObject(NULL, NULL);

    /*
     * Causes all processes associated with the job to terminate when the
     * last handle to the job is closed.
     */
    jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));

    /*
     * Create the process suspended.
     */
    si.cb = sizeof(si);
    CreateProcess(TEXT("C:\\Windows\\System32\\Notepad.exe"), NULL, NULL, NULL, FALSE, 
                  CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB /*Important*/, NULL, NULL, &si, &pi);

    /*
     * Add the process to our job object.
     */
    AssignProcessToJobObject(hJob, pi.hProcess); // Does not work if without CREATE_BREAKAWAY_FROM_JOB


    /*
     * Start our suspended process.
     */
    ResumeThread(pi.hThread);

    /*
     * At this point, if we are closed, windows will automatically clean up
     * by closing any handles we have open. When the handle to the job object
     * is closed, any processes belonging to the job will be terminated.
     * Note: Grandchild processes automatically become part of the job and
     * will also be terminated. This behaviour can be avoided by using the
     * JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK limit flag.
     */

    /*
     * Do what you like here. For demonstration purposes we will just wait
     * for the child process to complete. Click our close button to see
     * everything in action.
     */
    WaitForSingleObject(pi.hProcess, 3000);
    /*
     * Cleanup. As mentioned, Windows does this automagically when our process
     * exits, but it is good style to do it explicitly. 
     */
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    CloseHandle(hJob);

    return 0;
}
user3692418
  • 335
  • 2
  • 7
  • You should post a minimal sample code showing your use of Job when launching notepad – manuell Jun 03 '14 at 10:39
  • @manuell yes, you are right. do it now. – user3692418 Jun 03 '14 at 11:04
  • Just a hint, what about assigning current process to the job frst before spawning child processes? `AssignProcessToJobObject(hJob, GetCurrentProcess());` The job should be closed if the creator process terminates since this is the only process that has an open handle to the job. – bkausbk Jun 03 '14 at 13:40
  • @bkausbk Thanks, but still not work. – user3692418 Jun 03 '14 at 14:29
  • Did you check, if your spanwed child process is really part of the Job-Object? You can use "Process Explorer" for this purpose. There should be an tab "Job" on process properties dialog for your console process and the spawned child process. Please note, creating a job requires administrator rights as far as I know! – bkausbk Jun 03 '14 at 14:40
  • @bkausbk thanks. you are right. AssignProceeToJobObject fails. I have to use flag CREATE_BREAKAWAY_FROM_JOB to get child process sucessfully assgined, but I have no idea how to successfully assign the parent process into the job object. – user3692418 Jun 03 '14 at 16:02
  • @user3692418 What is your question? The code you initially posted works fine when launched outside of Visual Studio, as said in my answer. Notepad is automatically killed by Windows when your process ends. You seem to have tried lot of things, playing with various flags, but you state that "group 3" and "group 4" are OK... So what's the problem, exactly? – manuell Jun 04 '14 at 17:04
  • @manuell Hello, the code I initially posted does NOT work. The one you see here does work but it is not the initial one. The initial one is without flag CREATE_BREAKAWAY_FROM_JOB. – user3692418 Jun 04 '14 at 21:30
  • @manuell please see my comments in the thread below – user3692418 Jun 06 '14 at 16:55
  • @user3692418 Would you mind telling me what my answer is missing? Why don't you accept it? I may improve it. – manuell Jun 07 '14 at 10:58

3 Answers3

12

Do NOT test from within Visual Studio, via F5 or Ctrl+F5. When Visual Studio launch your program, it itself uses Job to manage things, and that interacts badly with your code.

Open a console and launch your exe "manually". Your code is correct ("works here", with VS2010 on Seven)

Edit: you may add error checking, don't assume all APIs always succeed.

Edit: you may use the IsProcessInJob API to know whether your process is already in a job when starting. If this is the case, child processes, by default, are created in that preexisting job, and you then have to use CREATE_BREAKAWAY_FROM_JOB (if you don't, you can't use AssignProcessToJobObject)

When starting the process from Visual Studio, or by double click in the Explorer,the process is launched in a job.

Adding, my code based on yours, works from VS or Explorer.

#include <Windows.h>
#include <stdio.h>

int main( void ) {

    BOOL bIsProcessInJob;
    BOOL bSuccess = IsProcessInJob( GetCurrentProcess(), NULL, &bIsProcessInJob );
    if ( bSuccess == 0 ) {
        printf( "IsProcessInJob failed: error %d\n", GetLastError() );
        return 0;
    }
    if ( bIsProcessInJob ) {
        MessageBox( NULL, L"Process is already in Job", L"Job Test", 0 );
    }

    HANDLE hJob = CreateJobObject( NULL, NULL );
    if ( hJob == NULL ) {
        printf( "CreateJobObject failed: error %d\n", GetLastError() );
        return 0;
    }

    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
    jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    bSuccess = SetInformationJobObject( hJob, JobObjectExtendedLimitInformation, &jeli, sizeof( jeli ) );
    if ( bSuccess == 0 ) {
        printf( "SetInformationJobObject failed: error %d\n", GetLastError() );
        return 0;
    }

    PROCESS_INFORMATION pi = { 0 };
    STARTUPINFO si = { 0 };
    si.cb = sizeof( si );
    DWORD dwCreationFlags = bIsProcessInJob ? CREATE_BREAKAWAY_FROM_JOB : 0;
    bSuccess = CreateProcess( L"C:\\Windows\\System32\\Notepad.exe", NULL, NULL, NULL, FALSE, 
                              dwCreationFlags, NULL, NULL, &si, &pi);
    if ( bSuccess == 0 ) {
        printf( "CreateProcess failed: error %d\n", GetLastError() );
        return 0;
    }

    bSuccess = AssignProcessToJobObject( hJob, pi.hProcess );
    if ( bSuccess == 0 ) {
        printf( "AssignProcessToJobObject failed: error %d\n", GetLastError() );
        return 0;
    }

    CloseHandle( pi.hThread );
    CloseHandle( pi.hProcess );

    printf( "Type a key to exit..." );
    getchar();

    CloseHandle( hJob );

    return 0;

}
manuell
  • 7,528
  • 5
  • 31
  • 58
  • Hi, I found it more and more tricky. When you run it from a console window, the original code works. But if I run it by double clicking, I have to use the flag CREATE_BREAKAWAY_FROM_JOB. I don't know the difference behind explorer.exe (double clicking) and cmd.exe (console). – user3692418 Jun 04 '14 at 22:29
  • Thank you a lot. Could I understand it like this: when a parent process is launched from VS, it has been already in a job (say job A), and therefore all the child process it creates are also in that job (job A). But if I kill the parent process, since Visual Studio is still on, the child process will not be killed automatically. On the other hand, when the flag CREATE_BREAKAWAY_FROM_JOB is set, the child process is not in the job (job A), and the parent process can create a new job (say job B) and put child process in job B. Then the parent process is in job A but child in job B – user3692418 Jun 06 '14 at 16:53
  • when the parent process is killed and job B will be automatically closed (am I right?), and thus the child process is also killed. – user3692418 Jun 06 '14 at 16:54
  • @user3692418 I think you are absolutely right in the 2 comments above. My answer and my code are based on that. – manuell Jun 06 '14 at 17:51
2

To summerize my comments:

The process that spawns the child have to create the job and is the only that has an open handle to it. All spwaned child processes are then part of the job.

hJob = CreateJobObject(NULL, NULL);

JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {
    0, 0, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, 0
};
SetInformationJobObject(hJob, &jeli);

if (AssignProcessToJobObject(hJob, GetCurrentProcess()) == FALSE) {
    DWORD Error = GetLastError();
}

If the above assignment fails, try to play with security flags like JOB_OBJECT_SECURITY_NO_ADMIN or JOB_OBJECT_SECURITY_RESTRICTED_TOKEN.

MSDN about AssignProcessToJobObject:

If the process is already running and the job has security limitations, AssignProcessToJobObject may fail. For example, if the primary token of the process contains the local administrators group, but the job object has the security limitation JOB_OBJECT_SECURITY_NO_ADMIN, the function fails. If the job has the security limitation JOB_OBJECT_SECURITY_ONLY_TOKEN, the process must be created suspended. To create a suspended process, call the CreateProcess function with the CREATE_SUSPENDED flag.

A process can be associated only with a single job. A process inherits limits from the job it is associated with and adds its accounting information to the job. If a process is associated with a job, all processes it creates are associated with that job by default. To create a process that is not part of the same job, call the CreateProcess function with the CREATE_BREAKAWAY_FROM_JOB flag.

So child processes are automatically part of the job in this case. However in order to create a job, it could be necessary to start the console application with higher rights. What is the error code returned after call to AssignProcessToJobObject(hJob, GetCurrentProcess())?

MSDN about CREATE_BREAKAWAY_FROM_JOB:

The child processes of a process associated with a job are not associated with the job.

If the calling process is not associated with a job, this constant has no effect. If the calling process is associated with a job, the job must set the JOB_OBJECT_LIMIT_BREAKAWAY_OK limit.

The flag CREATE_BREAKAWAY_FROM_JOB should prevent that the child process is part of the job. But this only applies if JOB_OBJECT_LIMIT_BREAKAWAY_OK was setfor the job.

bkausbk
  • 2,740
  • 1
  • 36
  • 52
0

I have a somewhat similar situation. My main application creates a child process that is used to log "events". The child process keeps a record of its parent processes (it can have many). Using a timer in the child process, I am able to check if the parent process has crashed, or, shut down. When this is detected, the child process shuts down cleanly.

rrirower
  • 4,338
  • 4
  • 27
  • 45
  • Thank you, but I cannot have access to the source code of the child application. It is external. – user3692418 Jun 03 '14 at 13:19
  • In your case, you should not need the source code. You should be able to enumerate processes to determine if it is still present. – rrirower Jun 03 '14 at 13:32
  • Thank you, but what if the parent process is forcefully skilled, for instance, killed in the task manager – user3692418 Jun 03 '14 at 14:30
  • If the process is killed, it will no longer show in the enumerated process list. You would use that fact to cleanly close your child process. – rrirower Jun 03 '14 at 14:59