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

Implement -ignore_readdir_race and -noignore_readdir_race. #411

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

hanbings
Copy link
Collaborator

@hanbings hanbings commented Jul 4, 2024

This PR will pass the test about race conditions in BFS test. Our implementation does not seem to cause race conditions, so only add Config related to -ignore_readdir_race and -noignore_readdir_race without making other logical changes.

Closes #377

Copy link

codecov bot commented Jul 4, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 66.29%. Comparing base (5be00c8) to head (2184e7c).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #411      +/-   ##
==========================================
+ Coverage   66.25%   66.29%   +0.03%     
==========================================
  Files          34       34              
  Lines        4004     4017      +13     
  Branches      911      911              
==========================================
+ Hits         2653     2663      +10     
- Misses        996      998       +2     
- Partials      355      356       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

github-actions bot commented Jul 4, 2024

Commit f4bb537 has GNU testsuite comparison:

Run GNU findutils tests: GNU tests summary = TOTAL: 702 / PASS: 445 / FAIL: 254 / ERROR: 2
Run GNU findutils tests: Changes from main: PASS +0 / FAIL +0 / ERROR +0 / SKIP +0 
Run GNU findutils tests: Gnu tests No changes
Run BFS tests: Changes from main: PASS +2 / SKIP +0 / FAIL -2
Run BFS tests: BFS tests summary = TOTAL: 288 / PASS: 192 / SKIP: 1 / FAIL: 95

@sylvestre
Copy link
Contributor

could you please document the fact that this option doesn't do anything ?

@hanbings
Copy link
Collaborator Author

hanbings commented Jul 5, 2024

could you please document the fact that this option doesn't do anything ?

Sorry for the late reply.

In short, taking the test https://github.com/tavianator/bfs/blob/main/tests/gnu/ignore_readdir_race.sh as an example, during the execution of -exec, our main thread will always be blocked by the -exec executor, so there will be no race condition.
But I guess this problem may not appear until #6 is implemented or #366 is fixed.

Wirte up

I usually start by checking the GNU tests and the BFS tests for this parameter.
The test project for -ignore_readdir_race is at https://github.com/tavianator/bfs/blob/main/tests/gnu/ignore_readdir_race.sh

ignore_readdir_race.sh

cd "$TEST"
"$XTOUCH" foo bar

# -links 1 forces a stat() call, which will fail for the second file
invoke_bfs . -mindepth 1 -ignore_readdir_race -links 1 -exec "$TESTS/remove-sibling.sh" {} \;

remove-sibling.sh

#!/usr/bin/env bash

for file in "${1%/*}"/*; do
    if [ "$file" != "$1" ]; then
        rm "$file"
        exit $?
    fi
done

exit 1

I then ran the script on my local machine:

/tmp/test_dir$ touch foo bar
# and copy remove-sibling.sh
/tmp/test_dir$ ls
foo bar remove-sibling.sh

# GNU find
/tmp/test_dir$ find . -mindepth 1 -links 1 -exec "./remove-sibling.sh" {} \;
find: ‘./bar’: No such file or directory
/tmp/test_dir$ ls
remove-sibling.sh

# uutils/find
/tmp/test_dir$ touch foo bar

/tmp/test_dir$ ls
foo bar remove-sibling.sh

/tmp/test_dir$ ... findutils/target/debug/find . -mindepth 1 -links 1 -exec "./remove-sibling.sh" {} \;
/tmp/test_dir$ ls
remove-sibling.sh

Then I realized that our implementation didn't seem to have this problem in the same test.
OK, so let's look at the code.

First we jump here from the main function, then turns to the parser parse_args which parses the arguments.
https://github.com/uutils/findutils/blob/main/src/find/mod.rs#L176

fn do_find<'a>(args: &[&str], deps: &'a dyn Dependencies<'a>) -> Result<u64, Box<dyn Error>> {
    let paths_and_matcher = parse_args(args)?;
    ...
}

After getting all the matchers that meet the parameters, use process_dir to traverse the directory.
https://github.com/uutils/findutils/blob/main/src/find/mod.rs#L189

found_count += process_dir(
    &path,
    &paths_and_matcher.config,
    deps,
    &*paths_and_matcher.matcher,
    &mut quit,
);

Here, matcher.matches is executed once for each directory entry.
So far, everything has been executed linearly, and no parallel processing has occurred.
https://github.com/uutils/findutils/blob/main/src/find/mod.rs#L159

while let Some(result) = it.next() {
    match result {
        Err(err) => {
            uucore::error::set_exit_code(1);
            writeln!(&mut stderr(), "Error: {dir}: {err}").unwrap()
        }
        Ok(entry) => {
            let mut matcher_io = matchers::MatcherIO::new(deps);
            if matcher.matches(&entry, &mut matcher_io) {
                found_count += 1;
            }
            ...
        }
    }
}

Finally, there is the part of exec.rs that executes the instructions.
When executing a command, we must wait for the command to complete before returning a result of whether the execution is completed (of course, an error message is output on the screen here.)
https://github.com/uutils/findutils/blob/main/src/find/matchers/exec.rs#L87

       ...
       match command.status() {
            Ok(status) => status.success(),
            Err(e) => {
                writeln!(&mut stderr(), "Failed to run {}: {}", self.executable, e).unwrap();
                false
            }
       }
       ...

Therefore, in the case of -exec, we always complete all work in sequence within one thread, and there will be no situation where instructions are not completed but uutils/findutils is still traversing.

@sylvestre
Copy link
Contributor

oh, sorry, i just meant "in the code. explaining why it isn't doing anything" :)

@hanbings
Copy link
Collaborator Author

hanbings commented Jul 6, 2024

oh, sorry, i just meant "in the code. explaining why it isn't doing anything" :)

Ok, I've submitted the description in my latest commit.

Copy link

github-actions bot commented Jul 6, 2024

Commit 7413637 has GNU testsuite comparison:

Run GNU findutils tests: GNU tests summary = TOTAL: 702 / PASS: 445 / FAIL: 254 / ERROR: 2
Run GNU findutils tests: Changes from main: PASS +0 / FAIL +0 / ERROR +0 / SKIP +0 
Run GNU findutils tests: Gnu tests No changes
Run BFS tests: Changes from main: PASS +2 / SKIP +0 / FAIL -2
Run BFS tests: BFS tests summary = TOTAL: 288 / PASS: 192 / SKIP: 1 / FAIL: 95

Copy link

github-actions bot commented Jul 6, 2024

Commit c9a36d0 has GNU testsuite comparison:

Run BFS tests: Changes from main: PASS +2 / SKIP +0 / FAIL -2
Run BFS tests: BFS tests summary = TOTAL: 288 / PASS: 192 / SKIP: 1 / FAIL: 95
Run GNU findutils tests: GNU tests summary = TOTAL: 702 / PASS: 445 / FAIL: 254 / ERROR: 2
Run GNU findutils tests: Changes from main: PASS +0 / FAIL +0 / ERROR +0 / SKIP +0 
Run GNU findutils tests: Gnu tests No changes

Copy link

github-actions bot commented Jul 8, 2024

Commit 9250adc has GNU testsuite comparison:

Run GNU findutils tests: GNU tests summary = TOTAL: 702 / PASS: 445 / FAIL: 254 / ERROR: 2
Run GNU findutils tests: Changes from main: PASS +0 / FAIL +0 / ERROR +0 / SKIP +0 
Run GNU findutils tests: Gnu tests No changes
Run BFS tests: Changes from main: PASS +2 / SKIP +0 / FAIL -2
Run BFS tests: BFS tests summary = TOTAL: 288 / PASS: 192 / SKIP: 1 / FAIL: 95

@sylvestre
Copy link
Contributor

needs rebase

@@ -692,6 +692,20 @@ fn build_matcher_tree(

return Ok((i, top_level_matcher.build()));
}
// In our implementation, including the `-exec` parameter,
// it is always run in a single thread.
// Therefore, there is no race condition for now.
Copy link
Contributor

Choose a reason for hiding this comment

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

The "race condition" that this flag is talking about is another command simultaneously modifying the directory while find is reading it, something like

tavianator@graphene $ touch {1..10000}
tavianator@graphene $ find -size 1 & rm ./*
[1] 65991
find: ‘./10000’: No such file or directory
[1]  + 65991 exit 1     find -size 1
tavianator@graphene $ touch {1..10000}
tavianator@graphene $ find -ignore_readdir_race -size 1 & rm ./*
[1] 66034
[1]  + 66034 done       find -ignore_readdir_race -size 1

Copy link
Contributor

Choose a reason for hiding this comment

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

@hanbings ping ? :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, I forgot about this. The race condition does happen when deleting a large number of files, and I'm rewriting a piece of code that implements this feature.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Terribly sorry, I did misunderstand the meaning of readdir_race.

I rechecked the code and read the GNU documentation. The -ignore_readdir_race parameter requires a way to suppress file not found errors globally in the software, so I need to complete #15 to centralize the error handling logic before I can return here.

I will finish them as soon as possible. :)

Copy link

Commit 2184e7c has GNU testsuite comparison:

Run BFS tests: Changes from main: PASS +2 / SKIP +0 / FAIL -2
Run BFS tests: BFS tests summary = TOTAL: 288 / PASS: 195 / SKIP: 1 / FAIL: 92
Run GNU findutils tests: GNU tests summary = TOTAL: 702 / PASS: 445 / FAIL: 254 / ERROR: 2
Run GNU findutils tests: Changes from main: PASS +0 / FAIL +0 / ERROR +0 / SKIP +0 
Run GNU findutils tests: Gnu tests No changes

@hanbings hanbings marked this pull request as draft August 1, 2024 10:38
@sylvestre
Copy link
Contributor

needs rebasing

@sylvestre
Copy link
Contributor

@hanbings do you have an update on this ? thanks :)

@hanbings
Copy link
Collaborator Author

hanbings commented Sep 9, 2024

@hanbings do you have an update on this ? thanks :)

There is nothing new here yet, and completing this PR requires completing the global error handling associated with this feature (which is #15).
Before that I would like to complete #369 first. :)

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.

Implement -ignore_readdir_race and -noignore_readdir_race
3 participants