Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Piping task timeout #243

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

dmilosz
Copy link

@dmilosz dmilosz commented Apr 29, 2024

Closes 241

It resolves the issue, by allowing to specify max timeout to wait for piping to be finished. By doing this way, we can customize how long to wait after the process is finished. The code throws exception, when waiting for piping exceeded timeout, which can be cached by client and it can be properly handled.

Timeout also gives flexibility for the client to give enough time to handle the output, so that it's not lost for the cases when child process is started, but it's important to read all output of the main process.

It's also a lot easier to create nice unit test for the behavior. Unfortunately, it .NET Framework behaves differently in the same case, so I had to exclude it in unit test. In .NET Framework, when main process is exited but child process is still running, the Exited event is not fired for some reason.

Example from unit test:

[Fact(Timeout = 10000)]
public async Task I_can_execute_a_command_and_cancel_piping_when_command_is_finished_but_child_process_remains_running()
{
    // Arrange
    var cmd = Cli.Wrap(Dummy.Program.FilePath)
        .WithArguments(
            [
                "run",
                "process",
                "--path",
                Dummy.Program.FilePath,
                "--arguments",
                "sleep 00:00:15"
            ]
        )
        .WithStandardOutputPipe(PipeTarget.ToDelegate(_ => { }))
        .WithStandardErrorPipe(PipeTarget.ToDelegate(_ => { }))
        .WithPipingTimeout(TimeSpan.FromSeconds(1));

    // Act
    var executeAsync = async () => await cmd.ExecuteAsync();

    // Assert
    var ex = await Assert.ThrowsAnyAsync<PipesCancelledException>(
        async () => await executeAsync()
    );

    ex.ExitCode.Should().Be(0);
}

Without .WithPipingTimeout(TimeSpan.FromSeconds(1)) this test gets timeout after 10 seconds.

@Tyrrrz
Copy link
Owner

Tyrrrz commented Apr 29, 2024

Hmm what if we make it possible to configure the command to not throw on cancellation and instead use ExecuteAsync(cancellationToken) where cancellationToken is your timeout.

@dmilosz
Copy link
Author

dmilosz commented Apr 30, 2024

When executing a command, it's impossible to know how long it will take to complete. If a cancellation token is set and fired while the command is still running, it will force pipingTask an immediate finish, potentially resulting in lost output logs.

It's important to note that the cancellation token will only work if it's fired manually by the client, such as in reaction to some expected output on process finish. Otherwise, using CancelAfter could result in lost output.

To avoid this, it's better to allow some time for the pipingTask to finish its work, especially when there is traffic in the output. If the client knows that the command has no running child processes, the pipingTimeout should be left empty. However, if the client knows that there is a child process started that will block the output and the command may produce a large output, the client can decide how long to wait before timing out.

@Tyrrrz
Copy link
Owner

Tyrrrz commented May 5, 2024

So is the difference from the current cancellation behavior such that the pipingTimeout doesn't kill the process, while normal cancellation does?

@dmilosz
Copy link
Author

dmilosz commented May 6, 2024

When you use graceful/forceful cancellation tokens, they send interrupt/kill signal to process, then forceful cancellation token is used to finish piping task. So you could use forceful cancellation token to cancel the process and piping, but then

  • you can't get exit code, start time and exit time of the process
  • you don't know whether the main process was finished or not
  • there is no way to notify when the main process is finished, but stdout is locked

This PR resolves above issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants