Skip to content

Commit

Permalink
chore: add CancellableCommand
Browse files Browse the repository at this point in the history
  • Loading branch information
filzrev committed Dec 25, 2024
1 parent 4634c48 commit 43bec14
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 39 deletions.
50 changes: 18 additions & 32 deletions src/Docfx.App/PdfBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,9 @@ public static async Task CreatePdf(string outputFolder, CancellationToken cancel
var headerFooterTemplateCache = new ConcurrentDictionary<string, string>();
var headerFooterPageCache = new ConcurrentDictionary<(string, string), Task<byte[]>>();

using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
using var ctr = SubscribeCancelKeyPressEvent(cts);

var pdfBuildTask = AnsiConsole.Progress().StartAsync(async progress =>
{
await Parallel.ForEachAsync(pdfTocs, new ParallelOptions { CancellationToken = cts.Token }, async (item, _) =>
await Parallel.ForEachAsync(pdfTocs, new ParallelOptions { CancellationToken = cancellationToken }, async (item, _) =>
{
var (url, toc) = item;
var outputName = Path.Combine(Path.GetDirectoryName(url) ?? "", toc.pdfFileName ?? Path.ChangeExtension(Path.GetFileName(url), ".pdf"));
Expand All @@ -115,33 +112,34 @@ public static async Task CreatePdf(string outputFolder, CancellationToken cancel
await CreatePdf(
PrintPdf, PrintHeaderFooter, task, new(baseUrl, url), toc, outputFolder, pdfOutputPath,
pageNumbers => pdfPageNumbers[url] = pageNumbers,
cts.Token);
cancellationToken);

task.Value = task.MaxValue;
task.StopTask();
});
});

// Wait pdfBuildTask completed or cancelled.
await Task.WhenAny(pdfBuildTask, Task.Delay(Timeout.Infinite, cts.Token));

if (!pdfBuildTask.IsCompletedSuccessfully)
try
{
await pdfBuildTask.WaitAsync(cancellationToken);
}
catch (OperationCanceledException)
{
// So manually close playwright context and browser to immeadiately shutdown running task.
if (!pdfBuildTask.IsCompleted)
{
// If pdf generation task is not completed.
// Manually close playwright context/browser to immediately shutdown remaining tasks.
await context.CloseAsync();
await browser.CloseAsync();
}

try
{
await pdfBuildTask;
}
catch (OperationCanceledException)
{
Logger.LogError($"PDF file generation is canceled by user interaction.");
return;
try
{
await pdfBuildTask; // Wait AnsiConsole.Progress operation completed to output logs.
}
catch
{
Logger.LogError($"PDF file generation is canceled by user interaction.");
return;
}
}
}

Expand Down Expand Up @@ -690,16 +688,4 @@ private static StringComparison GetStringComparison()
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
}

private static CancellationTokenRegistration SubscribeCancelKeyPressEvent(CancellationTokenSource cts)
{
void onCancelKeyPress(object? sender, ConsoleCancelEventArgs e)
{
e.Cancel = true;
cts.Cancel();
}

Console.CancelKeyPress += onCancelKeyPress;
return cts.Token.Register(() => Console.CancelKeyPress -= onCancelKeyPress);
}
}
33 changes: 33 additions & 0 deletions src/docfx/Models/CancellableCommandBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using Spectre.Console.Cli;
using System.Runtime.InteropServices;

namespace Docfx;

public abstract class CancellableCommandBase<TSettings> : Command<TSettings>
where TSettings : CommandSettings
{
public abstract int Execute(CommandContext context, TSettings settings, CancellationToken cancellation);

public sealed override int Execute(CommandContext context, TSettings settings)
{
using var cancellationSource = new CancellationTokenSource();

using var sigInt = PosixSignalRegistration.Create(PosixSignal.SIGINT, onSignal);
using var sigQuit = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, onSignal);
using var sigTerm = PosixSignalRegistration.Create(PosixSignal.SIGTERM, onSignal);

var exitCode = Execute(context, settings, cancellationSource.Token);
return exitCode;

void onSignal(PosixSignalContext context)
{
context.Cancel = true;
cancellationSource.Cancel();
}
}
}
8 changes: 4 additions & 4 deletions src/docfx/Models/DefaultCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Docfx;

class DefaultCommand : Command<DefaultCommand.Options>
class DefaultCommand : CancellableCommandBase<DefaultCommand.Options>
{
[Description("Runs metadata, build and pdf commands")]
internal class Options : BuildCommandOptions
Expand All @@ -20,7 +20,7 @@ internal class Options : BuildCommandOptions
public bool Version { get; set; }
}

public override int Execute(CommandContext context, Options options)
public override int Execute(CommandContext context, Options options, CancellationToken cancellationToken)
{
if (options.Version)
{
Expand Down Expand Up @@ -48,9 +48,9 @@ public override int Execute(CommandContext context, Options options)
if (config.build is not null)
{
BuildCommand.MergeOptionsToConfig(options, config.build, configDirectory);
serveDirectory = RunBuild.Exec(config.build, new(), configDirectory, outputFolder);
serveDirectory = RunBuild.Exec(config.build, new(), configDirectory, outputFolder, cancellationToken);

PdfBuilder.CreatePdf(serveDirectory).GetAwaiter().GetResult();
PdfBuilder.CreatePdf(serveDirectory, cancellationToken).GetAwaiter().GetResult();
}

if (options.Serve && serveDirectory is not null)
Expand Down
8 changes: 5 additions & 3 deletions src/docfx/Models/PdfCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
using Docfx.Pdf;
using Spectre.Console.Cli;

#nullable enable

namespace Docfx;

internal class PdfCommand : Command<PdfCommandOptions>
internal class PdfCommand : CancellableCommandBase<PdfCommandOptions>
{
public override int Execute([NotNull] CommandContext context, [NotNull] PdfCommandOptions options)
public override int Execute(CommandContext context, PdfCommandOptions options, CancellationToken cancellationToken)
{
return CommandHelper.Run(options, () =>
{
var (config, configDirectory) = Docset.GetConfig(options.ConfigFile);

if (config.build is not null)
PdfBuilder.Run(config.build, configDirectory, options.OutputFolder).GetAwaiter().GetResult();
PdfBuilder.Run(config.build, configDirectory, options.OutputFolder, cancellationToken).GetAwaiter().GetResult();
});
}
}

0 comments on commit 43bec14

Please sign in to comment.