Skip to content

Conversation

@Flamefire
Copy link
Contributor

@Flamefire Flamefire commented Apr 7, 2021

This PR avoids using a priority in prepend_module_path (Lmod) which means that $MODULEPATH can be directly modified by this and all subsequent calls to either prepend_module_path or use which is a lot faster than calling Lmod.

The test has been changed so it now expects that prepend_module_path actually does prepend even when no priority is passed and another priority is in effect.
For compatibility I kept the constant 10000 as HIGH_PRIORITY so user sites that really want other module paths to take preference can use any higher priority.

As discussed in Slack this change is unlikely to effectively change any behavior and will very likely continue to work, see TACC/Lmod#509 (comment)

Fixes #3631

@boegel
Copy link
Member

boegel commented Apr 7, 2021

@Flamefire Let's close #3634 and continue the discussion here. When this PR (#3636) gets merged, #3634 will also be merged automatically (since the commits included in here are also included in there), but context w.r.t. the discussion will be missing...

priority = self.HIGH_PRIORITY
self.use(path, priority=priority)
modulepath = curr_module_paths(clean=False)
path_idx = modulepath.index(path)
Copy link
Member

Choose a reason for hiding this comment

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

This could trigger a ValueError if (somehow...) path is not in $MODULEPATH, so maybe wrap this in a try/except to avoid a nasty traceback?

It would be really weird to not have path at all in modulepath though, I must admit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be virtually impossible for this to happen as it should be either caught already by our tests or is an actual (and serious) bug in Lmod. So I'd say the "nasty traceback" is fine in this rare case so we don't need to litter our code :)



def curr_module_paths():
def curr_module_paths(clean=True):
Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't make cleaning optional at all, I think weird things may happen when empty values are included in $MODULEPATH and EasyBuild starts using those. There's a reason we're filtering those here...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is required to match Lmod behavior: Lmod uses any path as specified, so non-existing, empty or "unusual" ones are just used.
So the "fast" module use/unuse must use the non-cleaned path or behavior will differ

There's a reason we're filtering those here...

Remember which? Also the default is still to clean, so all of EB except for the new functions is unaffected

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From Slack:

we do need a clean_existing_paths for curr_module_paths, but it should still filter out empty paths

Why? I don't even think we should clean out non-existing paths by default and filtering out empty paths would be different from what Lmod does, although I'm not sure about the semantic of that. And if we do filter those, then the fast and regular module.use would be different.


def unuse(self, path):
"""Remove a module path"""
# We can simply remove the path from MODULEPATH to avoid the costly module call
Copy link
Member

Choose a reason for hiding this comment

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

costly "module unuse" call

Maybe also clarify that modifying $MODULEPATH directly implies that Lmod won't reconsider loaded modules, but in the context of an EasyBuild session that should be OK...

Maybe we should also add an option to force running the actual module unuse command (same above for use), in case people are using the EasyBuild interface to the modules tool in Python scripts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done with default set to False to provide the fast implementation by default

self.use(path, priority=priority)
modulepath = curr_module_paths(clean=False)
path_idx = modulepath.index(path)
if path_idx != 0:
Copy link
Member

Choose a reason for hiding this comment

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

If we notice the path isn't first, can't we try again with self.use(path, priority=priority, force_module_command=True)?
The warning should suffice to make it clear something fishy is going on, but we can still try to do the correct thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This wouldn't change anything: If priority is None, then the path will be at the front already due to the fast path, if priority is not None or any priorities are in use then the module command will be used already.

@akesandgren
Copy link
Contributor

@Flamefire Conflict resolution needed

@boegel boegel modified the milestones: 4.4.0, release after 4.4.0 May 26, 2021
@Flamefire Flamefire force-pushed the fasterPrependModulePath branch from 1d62274 to 59af049 Compare May 27, 2021 07:01
@Flamefire
Copy link
Contributor Author

Rebased and conflicts resolved. That was quite a lot... Please double-check the changes

@Flamefire Flamefire force-pushed the fasterPrependModulePath branch from 59af049 to 2f85eaa Compare May 27, 2021 07:04
@akesandgren
Copy link
Contributor

This now looks good to me, but since it's large i'll wait for @boegel to review before merging.

akesandgren
akesandgren previously approved these changes May 27, 2021
Copy link
Contributor

@akesandgren akesandgren left a comment

Choose a reason for hiding this comment

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

LGTM

os.environ['MODULEPATH'] = old_module_path # Restore

# Forcing to use the module command reloads modules (Only Lmod does this)
old_module_path = os.environ['MODULEPATH']
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is superfluous, old_module_path is already == os.environ['MODULEPATH']

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually the "os.environ... Restore" line above is also useless since you're setting it again below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, but those are more or less different (sub-)testcases. I wanted to make them self-contained, so it would still work if we remove one of those. Maybe best done via a fixture or context-manager but yeah...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FWIW: #5015 introduces a context manager that can be used here

@boegel boegel modified the milestones: 4.5.0, release after 4.5.0 Oct 13, 2021
@boegel boegel removed this from the 4.5.1 milestone Dec 7, 2021
@boegel boegel added this to the release after 4.9.0 milestone Dec 26, 2023
@boegel boegel modified the milestones: 4.9.1, 5.0 Apr 3, 2024
@Flamefire Flamefire force-pushed the fasterPrependModulePath branch from 670ea75 to a503b87 Compare April 16, 2024 08:11
@boegel boegel modified the milestones: 5.0.0, 5.x Mar 17, 2025
@Flamefire
Copy link
Contributor Author

There is one failing test where we should consider what we want:

self.prepend_module_path(aa)
self.prepend_module_path(bb, 10000)
self.prepend_module_path(cc)

If the caller of cc doesn't know anything about the former states he reasonably expects to be first but is not.

This currently happens in a test: load(module, mod_paths=[aa, (bb,10000), cc]) and checks for bb being first while with this patch cc is first.

@boegel
Copy link
Member

boegel commented Oct 22, 2025

There is one failing test where we should consider what we want:

self.prepend_module_path(aa)
self.prepend_module_path(bb, 10000)
self.prepend_module_path(cc)

If the caller of cc doesn't know anything about the former states he reasonably expects to be first but is not.

This currently happens in a test: load(module, mod_paths=[aa, (bb,10000), cc]) and checks for bb being first while with this patch cc is first.

If a path was added with priority, that was likely done with good reason, so we should respect that...

This recently popped up actually, see c8e723a (which is part of #4991)

@Flamefire
Copy link
Contributor Author

This could be easily done by removing:

            # If no explicit priority is set, but priorities are already in use we need to use a high
            # priority to make sure the path (very likely) ends up at the front
            if priority is None and self._has_module_paths_with_priority():
                priority = self.HIGH_PRIORITY

I can do that.

@Flamefire Flamefire changed the title Avoid using a priority in prepend_module_path (Lmod) to avoid costly module calls Directly modify LMod $MODULEPATH to avoid costly module calls Oct 23, 2025
@Flamefire Flamefire closed this Oct 23, 2025
@Flamefire Flamefire changed the title Directly modify LMod $MODULEPATH to avoid costly module calls Avoid using a priority in prepend_module_path (Lmod) to avoid costly module calls Oct 23, 2025
@Flamefire Flamefire deleted the fasterPrependModulePath branch October 23, 2025 12:03
@Flamefire
Copy link
Contributor Author

This is no longer useful as priorities need to be used when specified and so we might end up with prepend_module_path not prepending paths and we cannot fix that, see the above comments

I kept the relevant changes from this PR and opened a new one: #5030

@boegel This includes 2 important bugfixes I discovered with the added tests

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.

Don't use priority for module use

3 participants