madelson / MedallionShell

A .NET library simplifying the execution and chaining of processes
MIT License
415 stars 32 forks source link

Killing feature: Kill process and its children #17

Open rocky05475 opened 7 years ago

rocky05475 commented 7 years ago

This is a little bit hard to Implement but It's a killing feature.

For example: Command.Run("cmd", "/c robocopy..."); Command.KillTree(); // Kill both cmd.exe and robocopy.exe

madelson commented 7 years ago

This doesn't seem easy to implement on top of .NET APIs; we'd need to either use the TASKKILL windows utility or call into native APIs

madelson commented 7 years ago

@rocky05475 I looked into this a bit but I couldn't find a way to implement it that was not Windows or even Windows version-specific (some versions seem to have TSKILL instead of TASKILL).

With the new ProcessId property, it is relatively easy to do a kill tree operation on Windows using TASKILL (which can itself be fired off using a command).

Therefore, I'm going to close this for now. We can reconsider if there is more demand for this feature and/or we can figure out a robust way to do this that works everywhere.

adamsd308 commented 4 years ago

Ran into this issue as well. Added an extension as a workaround and thought I would share in case it was useful for anyone else. Can only be used with .NET Core 3.0 which has a new overload for Kill() and should work cross platform. Unfortunately .net standard 2.0 and 2.1 do not yet support this api.

   // DisposeOnExit(false) must be set to false
    public static class CommandExtensions
    {
        public static async Task<Command> WaitAsync(this Command command, TimeSpan timeout, CancellationToken cancellationToken)
        {
            var cancelCompletionSource = new TaskCompletionSource<object>();
            var delayTask = Task.Delay(timeout);
            using (cancellationToken.Register(() => cancelCompletionSource.TrySetCanceled()))
            { 
                await Task.WhenAny(command.Task, delayTask, cancelCompletionSource.Task);
                if (delayTask.IsCompleted || cancelCompletionSource.Task.IsCanceled)
                {
                    command.KillTree();
                }
                return command;
            }
        }

        public static void KillTree(this Command command)
        {
            command.Process.Kill(true);
        }
    }

//usage: await cmd.WaitAsync(TimeSpan.FromSeconds(5), tokenSrc.Token);
madelson commented 4 years ago

@adamsd308 thanks this is very helpful. With this, I think we can add some APIs that are only on netcoreapp3.0+ to support this.

I think that the cleanest way to represent this in an actual library change would be a new command option:

options => options.OwnsEntireProcessTree(bool value = true)

When this option is set, the following behavioral changes will occur:

My thinking is that either you'll be wanting to treat a command as owning its entire tree, or you won't. Either way, you'll want all the mechanisms for killing to be consistent. And, if you want to violate this you can always do so by accessing the underlying process directly through an extension like the one you wrote.

Does that seem reasonable?

madelson commented 4 years ago

KillTree() implementation for windows (for possible shimming):

https://github.com/dotnet/runtime/blob/master/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Win32.cs#L382

KillTree() implementation for unix (for possible shimming):

https://github.com/dotnet/runtime/blob/master/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L89

adamsd308 commented 4 years ago

@madelson adding the additional api for .netcore 3.0+ would be awesome.