22

I'm spawning a child process in ASP.NET Core (.NET Framework) with Process class:

var process = new Process
            {
                StartInfo = new ProcessStartInfo(executableDir)
                {
                    Arguments = commandDefinition.CommandDef.ArgumentsAsString,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    WorkingDirectory = _contentPath,
                },
            };

process.Start()

As far as I understand when parent (ASP.Net Core) process gets killed, the children process should stay alive. I have tested this behaviour using two console applications and children process never gets killed after killing parent process. However when I spawn a new process in ASP.NET Core then children process gets killed when:

  • IIS recycles app.
  • MSDeploy publishes a new version of ASP.NET Core app.
  • When using dotnet watch and the application is restarted during to code change.

It doesn't get killed ONLY if parent is killed through task manager.(after some tests it's not always the case)

From the above I suspect that there is a mechanism in ASP.NET Core that kills all children processes on successful exit. Is it documented somewhere? Is there a way to avoid it? I couldn't find any info about such behaviour.

Edit: The repro is actually pretty easy.

  1. Create ASP.NET Core project (.NET Framework or .NET Core, doesn't matter)
  2. Add below code somewhere to your Startup class
  3. Start web app. It will be hosted under IIS Express. The calc process will start. Now either kill your app through task manager or close it through IIS express tray icon.
  4. Calc process will get killed. (Sometimes you need to try to refresh the your offline webpage)
 var process = new Process
 {
      StartInfo = new ProcessStartInfo("calc.exe")
      {
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
      },
 };
 process.Start();

Edit2: The issue seems to be in IIS. I have two profiles in launchSettings.json. If I run it with IISExpress then it gets closed, however when using a second one it lives.

"IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "WebApplication3Core": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:52135/"
    }

Edit4:

I did some research with process monitor and here is the output:

ss1

ss2

ss3

As you can see in ss1 that there is a "Process Exit" operation with iisexpress, then there are many irrevelant logs and after some time there is a Process Exit for calc.exe. It's not any different than normal exit. The only difference is the latter log which says "CloseFile" and path to my web app, I don't know what it actually means. It's definitely iis who kills calc.exe. I have IIS Express 10.0.14358 version (Server version where I found this is also 10)

MistyK
  • 6,055
  • 2
  • 42
  • 76
  • Have you seen this closed issue from CLI repo? [A way to graceful shutdown a process chain.](https://github.com/dotnet/cli/issues/1327) I think it may help you. – Keyur Ramoliya Jan 09 '19 at 08:38
  • Hey, this issue applies for dotnet watch however I'm not sure about IIS. Is dotnet cli used when iis hosts my app? I can't see dotnet.exe process there. – MistyK Jan 09 '19 at 08:50
  • I am also not sure about IIS `InProcess` hosting. That's why I refer you that issue. – Keyur Ramoliya Jan 09 '19 at 08:52
  • 2
    They may be using [Job Objects](https://learn.microsoft.com/en-us/windows/desktop/procthread/job-objects) but not sure. If you want something untied to ASP.Net process lifecycle, I'd be looking at e.g. a separate windows service or something like that, rather than having ASP.Net launching processes. – Damien_The_Unbeliever Jan 14 '19 at 09:31
  • @Damien_The_Unbeliever that's actually not an option for me. I'll check on Job Objects – MistyK Jan 14 '19 at 10:00
  • What's your hosting process? Is it inprocess or outofprocess? https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.2#in-process-hosting-model if it's out of process, yes there is a job object. All associated processes will be terminated: https://github.com/aspnet/AspNetCore/blob/master/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp#L89 – Simon Mourier Jan 14 '19 at 10:07
  • @SimonMourier it's out of process because it's .NET framework. So as far as I understand iis process uses JobObject to host my web app. And whenever it gets recycled it kills my app + all children processes? And there is no one liner which can avoid this right? I know some workarounds like creating process using intermediate process but I just wanted to make it simpler. – MistyK Jan 14 '19 at 11:26
  • Use Process Explorer to see if job objects are in use. Configure coloring to show processes in jobs in a brown color. Then, see if the children have a brown color. Let's test this job object theory. It seems odd to me that .NET Core would do something like that. Why would they?! Alternative theory: The processes you start kill themselves. Try launching notepad.exe (or somehting non-gui like cmd.exe) and see if that survives. – usr Jan 14 '19 at 12:21
  • Also, I wonder what happens when RedirectStandardOutput is set to true, like here, and the parent exits. Where do the children then send their output? Do they maybe just crash? This setting seems wrong in any case if you want the children to keep running after the parent exits. – usr Jan 14 '19 at 12:22
  • @MistyK I suggest you try disabling output redirection as per my 2nd comment. – usr Jan 15 '19 at 10:11
  • @usr it doesn't help – MistyK Jan 15 '19 at 13:54
  • @MistyK if the answer does not solve your problem ping me. I will contribute more ideas. – usr Jan 16 '19 at 20:43
  • 1
    Couldn't repro using Full .Net Framework the calc.exe process stays alive. Should I even bother trying .net core if I cant repro it like you say with Full .Net? – Jeremy Thompson Jan 18 '19 at 03:37
  • @JeremyThompson nope, it's also not .NET Core version related. I've tested on 2.0 and 2.1 and it works fine. Only at work calc gets killed, not sure if it's iis version or what, will need to investigate further – MistyK Jan 18 '19 at 08:49
  • @MistyK I have seen your comments. Maybe you can make a Process Monitor trace. You can see the process killing there as an event. Look at the details of that event for clues (all columns and stack (double-click)). Also look at events directly preceding the killing. What other process is active, what file names are touched, ...; At this point is sounds to me like that server is special somehow. The solution might turn out trivial in the end. – usr Jan 19 '19 at 12:06
  • @usr see edit 4 – MistyK Jan 21 '19 at 09:09
  • From the log it seems that IIS does *not* kill calc.exe because IIS exits before calc does. The CloseFile event that closes the app directory is closing the "current directory" of the calc.exe process. I could not reproduce this on Windows 7 with .NET Core and IISExpress. What repro step could I be missing? I made a fresh web app with Visual Studio. – usr Jan 21 '19 at 10:48
  • @usr It's not like Process Exit event is the last when the process terminates. Look at ss3 which I've just uploaded. What 's your iis version? As I said, it doesn't happen at my home, only at work setup. I have IIS 10 here but I haven't checked the version at home, will do that today. Repro is in the first post but I suspect it may be IIS version or setting. – MistyK Jan 21 '19 at 11:13
  • I'd look at the stacks of those thread and process exit events. Maybe the stack shows something like an exception. That could give a clue to the cause. You would need to configure symbols first in Process Monitor.; I have no good theory what might cause this. I have never heard of such a feature or bug. Also, I cannot imagine what software or config on your work machine might cause this. Maybe it's worth going through all running processes in Process Explorer to see what weird software may be running. – usr Jan 21 '19 at 11:19

2 Answers2

6

As I said in my comment, there is a Job Object which is created by ASP.NET core in the out-of-process scenario. The relevant source code portion is here:

https://github.com/aspnet/AspNetCore/blob/master/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp#L89

HRESULT
SERVER_PROCESS::SetupJobObject(VOID)
{
    HRESULT                                 hr = S_OK;
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION    jobInfo = { 0 };

    if (m_hJobObject == NULL)
    {
      ....
            jobInfo.BasicLimitInformation.LimitFlags =
                JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

            if (!SetInformationJobObject(m_hJobObject,
                JobObjectExtendedLimitInformation,
                &jobInfo,
                sizeof jobInfo))
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }
    }

    return hr;
}

As per documentation, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE:

Causes all processes associated with the job to terminate when the last handle to the job is closed.

If you dig the source further, it does not seem to be optional.

Now, you can actually see this by yourself, if you reproduce your steps. Use Process Explorer, and navigate to your dotnet.exe process, this is what it will display:

enter image description here

  • note 1: in fact calc.exe stays alive (at least in my Windows 10 installation because it's now a WinRT app, so it will not end up as child of dotnet.exe), that's why I used notepad.exe
  • note 2: iisexpress.exe also creates a job object, but it's configured as breakaway ok, which means it won't kill child processes.
  • note 3: if you run from Visual Studio (not my screenshot), you may see an intermediary VSIISExeLauncher.exe process between iisexpress.exe and dotnet.exe. This one also creates a Job Object with 'kill on close' to add to the confusion...
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • 1
    Wow, it seems like it might be it. note1: It makes sense actually why it doesn't close at my home machine, I've got win10 and tested it only using calc, will check that out. note3: I tested it only from VS and using out of process on real IIS so basically all this would make sense. I'll look into process monitor more deeply to find this job object. I saw your comment previously but there is so much confusion, especially note3. Thanks mate! – MistyK Jan 21 '19 at 11:45
  • 2
    A full solution to the mystery. – usr Jan 21 '19 at 11:53
  • 1
    Is there a way to avoid it? How can I create a process which will not get killed? Any workaround? – MistyK Jan 21 '19 at 13:09
  • @MistyK - you said in your comments that you had workarounds, so I didn't searched it, but it's difficult as it's the whole job object point. What version of windows are your running on? – Simon Mourier Jan 21 '19 at 16:02
  • 1
    @SimonMourier The workaround with cmd /c calc.exe doesn't work actually. The only workaround I can think of but haven't tested yet is to create detached child, it involves approach with using windows api directly. I was thinking that maybe there is an easier way. Windows 7. edit: Actually there is an easy way to create process using ManagementClass https://stackoverflow.com/questions/12068647/creating-a-new-process-thats-not-a-child-of-the-creating-process – MistyK Jan 21 '19 at 16:09
  • @MistyK I was not aware that ManagementClass is available in dotnetcore – Alexandru Clonțea Jan 21 '19 at 17:18
  • @AlexandruClonțea it is, with System.Management nuget package. – MistyK Jan 21 '19 at 17:21
  • 1
    @MistyK Just be careful to not leave to big of an attack surface :) I am reverting most of the changes I've done to test this. Also, good luck, let us know if you find a working solution (post it), it would be useful to a lot of people – Alexandru Clonțea Jan 21 '19 at 17:25
  • This is documented here: https://learn.microsoft.com/en-us/windows/desktop/procthread/job-objects *After a process is associated with a job, by default any child processes it creates using CreateProcess are also associated with the job. Child processes created using Win32_Process.Create are not associated with the job.* Win32_Process is WMI/System.Management. In this case the process parent will be WmiPrvSe.exe and it won't be killed (which is not always a good thing ... https://stackoverflow.com/questions/34991452/why-is-wmiprvse-exe-holding-onto-a-handle-to-my-process-job-object) – Simon Mourier Jan 21 '19 at 18:12
  • If you are trying to prevent the child process from being killed when the parent process terminates checkout this answer: https://stackoverflow.com/a/12068949/2974621 – Chris Mar 12 '20 at 16:37
4

Edit

Solution not working for notepad.exe - I will try to see if there is any optionn based on Simon's answer as yo why this behaviour happens

Old answer (to be removed)

I have to wash my hands now for writing this code, but I think you can work around this limitation if that is what you need in your context. I advise against running external processes, especially from the "web server". It's asking for trouble. Replacing the process by some malicious person, credential escalation, unexpected behaviour because of syntax changes just to name a few.

Anyway this might work for you (interposing cmd.exe in between, to try to break the child-parent relationship):

var process = new Process
{
  StartInfo = new ProcessStartInfo 
      { 
         FileName ="cmd.exe",  
         Arguments = "/c calc.exe", 
         WindowStyle =  ProcessWindowStyle.Hidden
      }
};

process.Start();
Alexandru Clonțea
  • 1,746
  • 13
  • 23
  • 1
    This answer might solve his problem. I suspect the IO redirection causes the problem. Interposing a cmd.exe process might solve this. We will see what the OP responds with. – usr Jan 16 '19 at 20:42
  • @usr I did test it and it "worked on my containers" in a simple setup. I think there might be non-trivial non-safe OS level changes needed in the absolute worst case :) – Alexandru Clonțea Jan 16 '19 at 22:32
  • @usr Thanks for the feedback, I have added the IO redirection as a possible cause. Without debugging the issue, I cannot be 100% sure. – Alexandru Clonțea Jan 16 '19 at 22:48
  • The funny thing is, none of the solutions work fine.@usr it's not an issue with IO because I open calc.exe even with Process.Start("calc.exe") and still having the same issues. I performed my tests using ASP.NET Framework App (.NET Core 2.0). The process is created in Configure function in Startup.cs. I also tested it at home with .NET Core 2.1 and this issue is not reproducable. Now i don't know if it's something specific to .NET Core version or something machine related. I'll check the same .NET core as a next step when I get a chance. – MistyK Jan 17 '19 at 11:47
  • @MistyK I would investigate the machine related path, especially app pool credentials and in general authentication/authorization issues. I would comb the event log or procmon it. Sounds like quite the time investment. Let us know if you find anything new, especially a solution, for future generations :) – Alexandru Clonțea Jan 17 '19 at 11:55
  • 1
    @AlexandruClonțea Actually I'm testing it now under IIS Express in VS2017 which makes it easier to debug, I'll keep you posted with further investigations – MistyK Jan 17 '19 at 12:16
  • I'm sorry, but I don't see how this answers the question. The static `Process.Start(string)` method is just a convenient wrapper for `new Process(new ProcessStartInfo(string)).Start()`. The documentation quoted in the last part of your answer is just to clear a common misconception for desktop developers who switch to WebForms and assume they can control the client's machine from serverside code. The part of your answer that I think is valid is the `cmd /c` workaround, but that's just that, a workaround. – CodeCaster Jan 21 '19 at 09:37
  • 1
    @codecaster You are right about the misinterpreted quote. Since answering this, in local testing and I saw no significant difference in behaviour for the static vs instance Start methods of the Process class. The OP was asking about how to break the parent-child process chain. In my testing the "cmd /c" option had worked. Since then, new information has resurfaced from the OP, and I am indeed looking to improve/edit the answer when I have the time. For a non-workaround real answer to the up-to-date issue, the rights of the App Pool + OS configurations are needed. Thank you for your feedback! – Alexandru Clonțea Jan 21 '19 at 09:46
  • I don't think that cmd /c will work here. It seems like JobObjects also wrap cmd.exe process including the child process and kills them all after iis terminates. Don't use calc.exe as it's UniversalApp in win10 and the behavior is slightly different, try notepad.exe – MistyK Jan 21 '19 at 13:11
  • @MistyK you are right, can reproduce your issue with notepad.exe. Investigating further, now that I have a repro – Alexandru Clonțea Jan 21 '19 at 14:37
  • @MistyK my research yielded no notable result yet. I'm a bit rusty on P/Invoke stuff. I was thinking that something like var result = Kernel32.CreateProcess("notepad", "", IntPtr.Zero, IntPtr.Zero, false, CREATE_BREAKAWAY_FROM_JOB, IntPtr.Zero, null, ref si, out var pi); would work, but under normal circumstances it appears I am unable to grant "Create Process" rights for my user within the IIS Express process :( The only notable finding was that once a process is created + assigned to a job (i.e. what dotnetcore does), it cannot be reassigned to another job.... – Alexandru Clonțea Jan 21 '19 at 17:01
  • @AlexandruClonțea check my comment for the previous post. It seems to be working fine. – MistyK Jan 21 '19 at 17:09