From 885dc8d3c4235ac002090ccde72e34da6e4f5bbe Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sat, 12 Aug 2023 12:05:18 -0400 Subject: [PATCH] With yt-dlp, cancellation requires killing the child process --- src/YouTubeDownloadTool/Utils/Utils.cs | 29 ++++++++++++++++++++++++++ src/YouTubeDownloadTool/YTDlpTool.cs | 14 ++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/YouTubeDownloadTool/Utils/Utils.cs b/src/YouTubeDownloadTool/Utils/Utils.cs index c498ca5..2882b56 100644 --- a/src/YouTubeDownloadTool/Utils/Utils.cs +++ b/src/YouTubeDownloadTool/Utils/Utils.cs @@ -1,4 +1,6 @@ +using System.Diagnostics; using System.IO; +using System.Reflection; namespace YouTubeDownloadTool; @@ -40,4 +42,31 @@ public static async Task GetOrDownloadFileAsync(string fileP return fileLock; } + + private static readonly Func>? GetChildProcesses = + typeof(Process).GetMethod("GetChildProcesses", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(Process[]) }, null) + ?.CreateDelegate>>(); + + public static IReadOnlyList? TryFilterToChildProcesses(Process[] candidates, Process parentProcess) + { + return GetChildProcesses?.Invoke(parentProcess, candidates); + } + + public static void TryKillImmediateChildrenWithProcessName(Process parentProcess, string childProcessName) + { + var allProcessesWithName = Process.GetProcessesByName(childProcessName); + try + { + if (TryFilterToChildProcesses(allProcessesWithName, parentProcess) is { } childProcesses) + { + foreach (var childProcess in childProcesses) + childProcess.Kill(); + } + } + finally + { + foreach (var process in allProcessesWithName) + process.Dispose(); + } + } } diff --git a/src/YouTubeDownloadTool/YTDlpTool.cs b/src/YouTubeDownloadTool/YTDlpTool.cs index 931236a..302bd15 100644 --- a/src/YouTubeDownloadTool/YTDlpTool.cs +++ b/src/YouTubeDownloadTool/YTDlpTool.cs @@ -126,7 +126,19 @@ public async Task DownloadToDirectoryAsync( process.BeginOutputReadLine(); process.BeginErrorReadLine(); - await using (cancellationToken.Register(process.Kill)) + void Cancel() + { + var processName = process.ProcessName; // This can't be accessed after Process.Kill + + // Prevent from starting any future child processes + process.Kill(); + + // Unlike youtube-dl, yt-dlp starts a child process with the same name (yt-dlp). Once it's done this, + // killing the process we started doesn't cancel anything. + Utils.TryKillImmediateChildrenWithProcessName(parentProcess: process, childProcessName: processName); + } + + await using (cancellationToken.Register(Cancel)) await process.WaitForExitAsync(CancellationToken.None); cancellationToken.ThrowIfCancellationRequested();