- 
                Notifications
    You must be signed in to change notification settings 
- Fork 2.7k
refactor(locking): Created locking strategy to control locking mode #16177
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
base: master
Are you sure you want to change the base?
Conversation
This commit introduces a `LockingStrategy` struct and `LockMode` enum to allow more control over the locking strategy used during compilation. This work is done in preparation to enable fine grain locking.
| ws: &Workspace<'_>, | ||
| target: Option<CompileTarget>, | ||
| dest: &str, | ||
| locking_strategy: &LockingStrategy, | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming the decision is made outside of Layout for future changes.  However, I can't easily evaluate that and so I would prefer we instead keep the decision inside of Layout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the reason for this is I plan add build_runner argument to LockingStrategy::determine_locking_strategy() as its needed to determine if we want to enable fine grain locking. (number of build units)
I could move this into Layout but I am not in love with the prospect of adding build_runner as an arg to Layout::new()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like we should have a number_of_units arg for Layout::new?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like we should have a number_of_units arg for Layout::new?
We could, though it would be an Option since cargo clean does not have a build_runner.
So it does not know the number of units.
See https://github.com/rust-lang/cargo/pull/16177/files#diff-5a67831086004bef79bcf2ccf2d5d804dc0fafe90e4fc116fe4cdc59c78f5049R120
My thought was to have a struct with some semantic meaning be passed into Layout::new() instead of adding primitive types that grow over time.
| impl LockingStrategy { | ||
| /// Determines the locking strategy the current environment can support. | ||
| pub fn determine_locking_strategy(ws: &Workspace<'_>) -> CargoResult<Self> { | ||
| let artifact_dir_locking_mode = match is_on_nfs_mount(ws.target_dir().as_path_unlocked()) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we matching on bool?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me find matching on bools easier to read when its a simple transformation
let foo = match my_bool {
   true => MyEnum::Variant1,
   false => MyEnum::Variant2,
};
// vs
let foo = if my_bool {
   MyEnum::Variant1
} else {
   MyEnum::Variant2
};Though I later added the debug! statements which makes it less clean.
Happy to change to if statements if you'd prefer that
| let build_dir_locking_mode = if ws.target_dir() == ws.build_dir() { | ||
| None | ||
| } else { | ||
| Some(match is_on_nfs_mount(ws.build_dir().as_path_unlocked()) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: To me, I prefer returning wrapped values like this to be limited to values and not expressions. It just always reads in a funny way to me and adds extra nesting on wrapping to complex expressions that could instead use them being simplified.
| #[derive(Debug)] | ||
| pub struct LockingStrategy { | ||
| /// The locking mode for the artifact-dir | ||
| artifact_dir: LockingMode, | ||
| /// The locking mode for the build-dir | ||
| /// | ||
| /// Will be `None` when artifact_dir and build_dir are the same directory. | ||
| build_dir: Option<LockingMode>, | ||
| } | ||
|  | ||
| impl LockingStrategy { | ||
| /// Determines the locking strategy the current environment can support. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I know what change this is prep for, could we simplify it down for what is needed for this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what way would you like to see this simplified?
I believe this logic is the minimum to keep the behavior unchanged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The motivation is to move logic to determine if we should disable locking outside of try_acquire()
While I kinda can guess why this is useful, would you mind saying it a bit clearer how this is going to be used and evolve to help the fine grained locking implementation?
(Or any comments/code I should read first in the other PR?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason is to eventually add a LockingMode enum that can be controlled by the cargo operation.
The vision I have is the have 3 variants Disabled, Coarse, Fine which can be determined by the operation & environment.
For example, cargo clean would use either Disabled or Coarse as it does not have access to the dep graph.
Another motivation is to fallback to Coarse grain locking if we detect that Fine grain locking may fail. (e.g. too many file descriptors)
See determine_locking_mode() in the original PR for proposed heuristics.
What does this PR try to resolve?
This PR introduces a
LockingStrategystruct andLockingModeenum to allow more control over the locking strategy used during compilation.This work is done in preparation to enable fine grain locking.
The motivation is to move logic to determine if we should disable locking outside of
try_acquire()targetdirectory #16155Notes:
try_acquire()as the registry cache still relies on that check.targetdirectory #16155 there was an oversight which did not consider if artifact-dir was on an nfs mount but not build-dir. This is now taken into consideration byLockingStrategyin this PR.How to test and review this PR?
I created a local nfs mount over localhost and mounted it at
/mnt/nfs_test, then setCARGO_BUILD_BUILD_DIRto a directory in that mount.after build I verified no
.cargo-lockexists in the build-dirNot sure if there is an easy way to test this in CI and I am unsure if building in network filesystem mounts are a common enough usecase to warrant adding that complexity to the test CI.
Other observation during testing
I noticed that
is_on_nfs_mount()will returnfalseif the directory does not exist but will be nested with in an NFS mount. After the directory is created by a previous cargo invocation it will begin returningtrue. I believe this is a bug, though its probably low priority given that this is a probably niche usecase. I did not attempt to fix this in this PR.