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

Add timeouts for commit hooks #2547

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

Conversation

DaRacci
Copy link

@DaRacci DaRacci commented Mar 5, 2025

This Pull Request closes #2546.

It changes the following:

  • Adds timeouts to git hooks

I followed the checklist:

  • I added unittests
  • I ran make check without errors
  • I tested the overall application
  • I added an appropriate item to the changelog

@DaRacci
Copy link
Author

DaRacci commented Mar 5, 2025

Opening early as a draft.

Initial changes are working and have tests added for them just need to figure out how we are going to implement a timeout value, if thats user configuration or a default impl.

@DaRacci
Copy link
Author

DaRacci commented Mar 6, 2025

Was wanting to get some feedback on this before progressing further, is this the correct place to implement the timeout logic? @extrawurst

@extrawurst
Copy link
Collaborator

This would impact other users like Gitbutler. @Byron what do you think about this change?

@extrawurst extrawurst requested a review from Byron March 11, 2025 10:24
@extrawurst extrawurst requested a review from cruessler March 15, 2025 09:09
Copy link
Collaborator

@cruessler cruessler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From an implementation standpoint, this looks good. The only thing I would add is tests for non-zero timeouts (maybe that’s something you already planned or maybe I missed something).

@DaRacci
Copy link
Author

DaRacci commented Mar 16, 2025

Also worth mentioning as its done in this PR, I've changed the test shell shebang to use /usr/bin/env sh as this is a more widely accepted and allows distributions like NixOS to run the tests.

@DaRacci
Copy link
Author

DaRacci commented Mar 16, 2025

I've also noticed that while running tests that since i use the non POSIX Shell (nushell) that i need to prefix the command with SHELL=bash, i was wondering if it may be worth testing if the $SHELL is a known non compliant shell and trying to look for a default POSIX shell in the path?

@DaRacci
Copy link
Author

DaRacci commented Mar 16, 2025

Now that the implementation is solid how do i go about with setting the timeout, how do we expose configurable value for the user and should there be a default timeout?

Copy link

@Byron Byron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for letting me know, @extrawurst.

The changes seem to not apply to git2-hooks, so wouldn't affect GitButler in its current form. However, that would change if timeouts would move into git2-hooks as I seem to be proposing :).

PS: I unsubscribed as this PR is relevant to me only once git2-hooks is involved. You can always ping me to change that.

DaRacci added 4 commits March 16, 2025 16:59
…meouts

All hooks functions now have their own with_timeout variant inside git2-hooks and asyncgit.

The previous implementation of using a new thread with a timeout has been ditched in favour of a child command which we can now poll and kill to prevent the hook from continueing.

I've added a hard coded default 5 second timeout but this will be handled by the Options struct later
@DaRacci
Copy link
Author

DaRacci commented Mar 16, 2025

@Byron I've updated the implementation and it is now inside git2-hooks, I've added new functions with suffixed _with_timeout for each hook but the run_hooks function has a breaking change having the timeout parameter added to it.

@DaRacci
Copy link
Author

DaRacci commented Mar 16, 2025

The last remaining thing to do is move the tests i had written over to the git2_hooks crate, even so the current tests in asyncgit should be able to cover most cases i think.

@DaRacci
Copy link
Author

DaRacci commented Mar 16, 2025

I was also thinking about the initial issue where the problem was an infinite wait because the hook was waiting for input, maybe a solution could be when a hook is running a new window is shown with the stdio from the hook and also allowing stdin, not sure if that would work how im thinking though.

@DaRacci DaRacci marked this pull request as ready for review March 16, 2025 11:02
Copy link

@Byron Byron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the quick turnaround!

The new implementation definitely addresses the previous issue, and now the timeout has an actual effect on the hook.

When looking at git2-hooks exclusively, I am not sure I saw a test for this though.

@@ -130,7 +136,28 @@ impl HookPaths {
"DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS",
"FixPathHandlingOnWindows",
)
.output()?;
.stdout(Stdio::piped())
.stderr(Stdio::piped())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the semantics we had before, where stdin was null (see the docs of output).
Doing so should also make it impossible for a hook to hang waiting on stdin. Hooks which still hang are probably buggy in another way.

}

std::thread::yield_now();
std::thread::sleep(Duration::from_millis(10));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't sleep() imply a yield, among other things?

@@ -66,6 +67,10 @@ pub enum HookResult {
/// path of the hook that was run
hook: PathBuf,
},
TimedOut {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs are missing.
Probably the crate could use a deny(missing_docs) as well.

@@ -106,7 +108,11 @@ impl HookPaths {

/// this function calls hook scripts based on conventions documented here
/// see <https://git-scm.com/docs/githooks>
pub fn run_hook(&self, args: &[&str]) -> Result<HookResult> {
pub fn run_hook(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this doesn't have to be a breaking change unless one really wants to tell folks to deal with the timeout. That timeouts are implemented now probably means most people didn't have issues with this (or solved them by fixing the hooks), so I'd argue that run_hook_with_timeout() should be favored.

}

std::thread::yield_now();
std::thread::sleep(Duration::from_millis(10));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could think about a quadratic backoff (or a custom sequence) here where the first few milliseconds are checked more often, and as it takes longer, one checks less often.
This might also be overthinking it as 10ms seems fine.

Depending on how this is done, I think one should mention in the docs that the timeout may prolong the actual runtimes, right now by up to 10ms.

@@ -106,7 +108,11 @@ impl HookPaths {

/// this function calls hook scripts based on conventions documented here
/// see <https://git-scm.com/docs/githooks>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a documentation update.

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

Successfully merging this pull request may close these issues.

Program hangs if repo uses prepare-commit-hook that requires user input
4 participants