Skip to content

bugfix: original concretizer is sensitive to dependency order#41975

Merged
tgamblin merged 3 commits intodevelopfrom
bugfix-original-concretizer-ignores-dep-constraints
Jan 8, 2024
Merged

bugfix: original concretizer is sensitive to dependency order#41975
tgamblin merged 3 commits intodevelopfrom
bugfix-original-concretizer-ignores-dep-constraints

Conversation

@tgamblin
Copy link
Copy Markdown
Member

@tgamblin tgamblin commented Jan 6, 2024

Needed for #40326, which can changes the iteration order over package dependencies during concretization.

While clingo doesn't have this problem, the original concretizer (which we still use for bootstrapping) can be sensitive to iteration order when evaluating dependency constraints in when conditions. This can cause it to ignore conditional dependencies unless the dependencies in the condition are listed first in the package.

The issue was in the way the original concretizer would disconnect specs every time normalize() ran. When specs were disconnected, ^dependency constraints wouldn't see the dependency in the dependency condition loop.

We now only only disconnect all dependencies at the start of concretize() and normalize(), and we disconnect any leftover dependents from replaced externals at the end of normalize(). This trims stale connections while keeping the ones that are needed to trigger dependency conditions.

  • refactor flat_dependencies() to not disconnect the spec by default.
  • flat_dependencies() is never called with copy=True -- remove the copy kwarg.
  • disconnect only once at the beginning of normalize() or concretize().
  • add a test that perturbs dependency iteration order to ensure this doesn't regress.
  • disconnect unused dependents at end of normalize()

Needed for #40326, which can changes the iteration order over package
dependencies during concretization.

While clingo doesn't have this problem, the original concretizer (which we
still use for bootstrapping) can be sensitive to iteration order when evaluating
dependency constraints in `when` conditions. This can cause it to ignore
conditional dependencies unless the dependencies in the condition are lsited
first in the package.

The issue was in the way the original concretizer would disconnect specs *every*
time `normalize()` ran. When specs were disconnected, `^dependency` constraints
wouldn't see the dependency in the dependency condition loop.

- [x] refactor `flat_dependencies()` to not disconnect the spec by default.
- [x] disconnect only once at the beginning of `normalize()` or `concretize()`.
- [x] add a test that perturbs dependency iteration order to ensure this doesn't
      regress.
@tgamblin tgamblin requested a review from alalazo January 6, 2024 07:56
@spackbot-app spackbot-app bot added core PR affects Spack core functionality stand-alone-tests Stand-alone (or smoke) tests for installed packages tests General test capability(ies) labels Jan 6, 2024
@tgamblin tgamblin requested a review from becker33 January 6, 2024 07:58
@tgamblin tgamblin force-pushed the bugfix-original-concretizer-ignores-dep-constraints branch from 05c0253 to fba3c7f Compare January 7, 2024 03:15
@tgamblin tgamblin added concretization bootstrap Anything that has to do with Spack building its own dependencies. labels Jan 7, 2024
@tgamblin tgamblin enabled auto-merge (squash) January 7, 2024 05:25
Copy link
Copy Markdown
Member

@alalazo alalazo left a comment

Choose a reason for hiding this comment

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

Basically LGTM. Let me know if you want to tweak docstring etc. in following PRs. In that case this can be merged.

Comment on lines -3132 to -3133
if copy:
spec = spec.copy(deps=False)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems to be the only semantic change in the function. I tried to inspect closely, and I think it's ok to remove the copy. Do you recall why we added the copy here in the first place?

Copy link
Copy Markdown
Member Author

@tgamblin tgamblin Jan 9, 2024

Choose a reason for hiding this comment

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

It dates back to 2014 in 5f073ae. I guess it lasted not quite ten years :). _dup used to use flat_dependencies() to do its copying, but doesn't anymore. All remaining calls were passing copy=False, so I removed the kwarg.

if self.satisfies(when_spec):
if dep is None:
dep = dp.Dependency(self.name, Spec(name), depflag=0)
dep = dp.Dependency(Spec(self.name), Spec(name), depflag=0)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Even here it seems safe to use a copy instead of self. Reviewing this code makes me appreciate the new concretizer more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This change was really just to ensure early Spec parsing (and thus validation). It didn't have a self ref before -- just the name.

Comment on lines +3432 to +3438
# remove any leftover dependents outside the spec from, e.g., pruning externals
valid = {id(spec) for spec in all_spec_deps.values()} | {id(self)}
for spec in all_spec_deps.values():
remove = [dep for dep in spec.dependents() if id(dep) not in valid]
for dep in remove:
del spec._dependents.edges[dep.name]
del dep._dependencies.edges[spec.name]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OT (comment, no action needed): I noticed slight variations of this pattern in many places in the code. I introduced in #40636 a class that helps keeping consistency between objects in memory and specs. See the PR for three similar cases that have been unified in the code where we want to ensure that a set of specs doesn't refere to external specs, and have the same id in memory.

Copy link
Copy Markdown
Member

@alalazo alalazo left a comment

Choose a reason for hiding this comment

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

On a second thought, let me merge this and submit a tweak to the docstring - that will give us a fix in develop sooner 🙂

@tgamblin tgamblin merged commit 6f91514 into develop Jan 8, 2024
@tgamblin tgamblin deleted the bugfix-original-concretizer-ignores-dep-constraints branch January 8, 2024 08:47
RikkiButler20 pushed a commit to RikkiButler20/spack that referenced this pull request Jan 25, 2024
…41975)

Needed for spack#40326, which can changes the iteration order over package dependencies during concretization.

While clingo doesn't have this problem, the original concretizer (which we still use for bootstrapping) can be sensitive to iteration order when evaluating dependency constraints in `when` conditions. This can cause it to ignore conditional dependencies unless the dependencies in the condition are listed first in the package.

The issue was in the way the original concretizer would disconnect specs *every* time `normalize()` ran. When specs were disconnected, `^dependency` constraints wouldn't see the dependency in the dependency condition loop.

We now only only disconnect *all* dependencies at the start of `concretize()` and `normalize()`, and we disconnect any leftover dependents from replaced externals at the *end* of `normalize()`.  This trims stale connections while keeping the ones that are needed to trigger dependency conditions.

- [x] refactor `flat_dependencies()` to not disconnect the spec by default.
- [x] `flat_dependencies()` is never called with `copy=True` -- remove the `copy` kwarg.
- [x] disconnect only once at the beginning of `normalize()` or `concretize()`.
- [x] add a test that perturbs dependency iteration order to ensure this doesn't regress.
- [x] disconnect unused dependents at end of `normalize()`
RikkiButler20 pushed a commit to RikkiButler20/spack that referenced this pull request Jan 31, 2024
…41975)

Needed for spack#40326, which can changes the iteration order over package dependencies during concretization.

While clingo doesn't have this problem, the original concretizer (which we still use for bootstrapping) can be sensitive to iteration order when evaluating dependency constraints in `when` conditions. This can cause it to ignore conditional dependencies unless the dependencies in the condition are listed first in the package.

The issue was in the way the original concretizer would disconnect specs *every* time `normalize()` ran. When specs were disconnected, `^dependency` constraints wouldn't see the dependency in the dependency condition loop.

We now only only disconnect *all* dependencies at the start of `concretize()` and `normalize()`, and we disconnect any leftover dependents from replaced externals at the *end* of `normalize()`.  This trims stale connections while keeping the ones that are needed to trigger dependency conditions.

- [x] refactor `flat_dependencies()` to not disconnect the spec by default.
- [x] `flat_dependencies()` is never called with `copy=True` -- remove the `copy` kwarg.
- [x] disconnect only once at the beginning of `normalize()` or `concretize()`.
- [x] add a test that perturbs dependency iteration order to ensure this doesn't regress.
- [x] disconnect unused dependents at end of `normalize()`
kwryankrattiger pushed a commit to kwryankrattiger/spack that referenced this pull request Jul 9, 2024
…41975)

Needed for spack#40326, which can changes the iteration order over package dependencies during concretization.

While clingo doesn't have this problem, the original concretizer (which we still use for bootstrapping) can be sensitive to iteration order when evaluating dependency constraints in `when` conditions. This can cause it to ignore conditional dependencies unless the dependencies in the condition are listed first in the package.

The issue was in the way the original concretizer would disconnect specs *every* time `normalize()` ran. When specs were disconnected, `^dependency` constraints wouldn't see the dependency in the dependency condition loop.

We now only only disconnect *all* dependencies at the start of `concretize()` and `normalize()`, and we disconnect any leftover dependents from replaced externals at the *end* of `normalize()`.  This trims stale connections while keeping the ones that are needed to trigger dependency conditions.

- [x] refactor `flat_dependencies()` to not disconnect the spec by default.
- [x] `flat_dependencies()` is never called with `copy=True` -- remove the `copy` kwarg.
- [x] disconnect only once at the beginning of `normalize()` or `concretize()`.
- [x] add a test that perturbs dependency iteration order to ensure this doesn't regress.
- [x] disconnect unused dependents at end of `normalize()`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bootstrap Anything that has to do with Spack building its own dependencies. concretization core PR affects Spack core functionality stand-alone-tests Stand-alone (or smoke) tests for installed packages tests General test capability(ies)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants