Skip to content

Commit f330e14

Browse files
committed
Preserve sticky module requirements during purge
Purge skips sticky and super-sticky modules but still attempts to unload their non-sticky requirements. This reports an unrelated dependency error without explaining why the requirement remains loaded. Preserve requirements needed by retained sticky modules and apply the sticky_purge reporting mode to their skipped unload. Fixes #582 Assisted-by: Codex:gpt-6-astra Signed-off-by: Adrien Cotte <adrien.cotte@asplus.fr>
1 parent 31741fc commit f330e14

15 files changed

Lines changed: 289 additions & 15 deletions

‎.hunspell.en.dic‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ gh
497497
ghcommit
498498
gitlog
499499
gmake
500+
gpt
500501
grp
501502
gz
502503
gzip

‎NEWS.rst‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,16 @@ Modules 5.7.0 (not yet released)
251251
* Doc: add the :ref:`user-guide` document that explains a selection of
252252
useful but lesser known features through practical examples and common
253253
use cases.
254+
* Keep non-sticky requirements of sticky modules loaded during a
255+
:subcmd:`purge` and explicitly report why their unload is skipped according
256+
to the :mconfig:`sticky_purge` configuration. (fix issue #582)
254257
* Init: fix command injection in Bash completion when module names contain
255258
shell meta-characters. Completion candidates were passed to ``compgen -W``
256259
which evaluates command substitution syntax. (fix `CVE-2026-85013`_ found
257260
by AISLE in partnership with Red Hat)
258261

259262
.. _CVE-2026-85013: https://github.com/envmodules/modules/security/advisories/GHSA-8hrw-p88g-qhmg
260263

261-
262264
.. _5.6 release notes:
263265

264266
Modules 5.6.2 (2026-09-07)

‎doc/source/changes.rst‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,15 @@ Module Sub-Commands
648648
these variables. Exception is made for :envvar:`MODULEPATH` environment
649649
variable where the mechanism still applies.
650650

651+
:subcmd:`purge`
652+
653+
Starting with version 5.7, direct and indirect requirements of retained
654+
sticky and super-sticky modules are kept loaded, even if those requirements
655+
are not sticky themselves. The :mconfig:`sticky_purge` configuration
656+
controls reporting for their skipped unload as well as for sticky modules.
657+
With :option:`--force`, sticky modules and their requirements can be
658+
unloaded, but super-sticky modules and their requirements remain loaded.
659+
651660
:subcmd:`refresh`
652661

653662
Sub-command evaluates all loaded modulefiles and re-apply the non-persistent

‎doc/source/cookbook/sticky-modules.rst‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,56 @@ for load in the initialization RC file |file etcdir_initrc|:
170170
module load core
171171
module load compiler/compB
172172
173+
Preserving requirements during purge
174+
------------------------------------
175+
176+
Starting with Modules v5.7, :subcmd:`purge` also preserves the requirements
177+
of retained sticky or super-sticky modules.
178+
179+
A base environment can group *core* and *compiler* under a single *env/base*
180+
module. In this example, *core* and *compiler* have no sticky tag. The
181+
:file:`env/base` modulefile declares both as requirements:
182+
183+
.. code-block:: tcl
184+
185+
#%Module
186+
prereq core
187+
prereq compiler
188+
189+
Only *env/base* is tagged *super-sticky*, in :file:`env/.modulerc`:
190+
191+
.. code-block:: tcl
192+
193+
#%Module
194+
module-tag super-sticky env/base
195+
196+
With :mconfig:`sticky_purge` set to ``warning``, a purge explains why each
197+
module remains loaded:
198+
199+
.. parsed-literal::
200+
201+
:ps:`$` module config sticky_purge warning
202+
:ps:`$` module load env/base
203+
:ps:`$` module purge
204+
Unloading :sgrhi:`env/base`
205+
:sgrwa:`WARNING`: Unload of super-sticky module skipped
206+
207+
Unloading :sgrhi:`compiler`
208+
:sgrwa:`WARNING`: Unload of super-sticky module requirement skipped
209+
210+
Unloading :sgrhi:`core`
211+
:sgrwa:`WARNING`: Unload of super-sticky module requirement skipped
212+
:ps:`$` module list
213+
Currently Loaded Modulefiles:
214+
1) core 2) compiler 3) :sgrss:`env/base`
215+
216+
Key:
217+
:sgrss:`super-sticky`
218+
219+
Both requirements remain loaded even though they are not sticky themselves.
220+
The same protection applies to indirect requirements and to a forced purge
221+
because *env/base* is super-sticky. Setting ``sticky_purge`` to ``silent``
222+
suppresses these warnings; its default value, ``error``, reports errors for
223+
the skipped unloads.
224+
173225
.. vim:set tabstop=2 shiftwidth=2 expandtab autoindent:

‎doc/source/design/evaluation-errors.rst‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,13 @@ In case of error, *continue on error* behavior is applied. If purge is added
336336
in the value list of :mconfig:`abort_on_error` configuration option and if
337337
force mode is not set, *abort on error* behavior applies.
338338

339+
Starting Modules 5.7, unload of a requirement needed by a retained sticky or
340+
super-sticky module is skipped according to :mconfig:`sticky_purge`.
341+
``error`` raises an error and follows the above abort or continue behavior.
342+
``warning`` and ``silent`` skip unload without an error exit code. This also
343+
applies to indirect requirements and, even in force mode, to requirements of
344+
super-sticky modules.
345+
339346
Unload sub-command
340347
------------------
341348

‎doc/source/design/module-tags.rst‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ Specification
5656
- ``super-sticky``: loaded module cannot be unloaded even if forced, it stills can be unloaded if reloaded afterward (see :ref:`sticky-modules`)
5757
- ``keep-loaded``: auto_handling mechanism does not unload auto-loaded module
5858

59+
- Starting Modules 5.7, :subcmd:`purge` also preserves the direct and
60+
indirect requirements of retained ``sticky`` or ``super-sticky`` modules.
61+
Requirements do not inherit these tags. Their unload is skipped according
62+
to :mconfig:`sticky_purge`, including when a requirement has no sticky tag.
63+
With :option:`--force`, only requirements needed by retained super-sticky
64+
modules are protected by this mechanism.
65+
5966
- Tags inherited from module state cannot be set with ``module-tag`` command
6067

6168
- An error is otherwise thrown

‎doc/source/design/sticky-modules.rst‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,16 @@ Specification
4848
- it means stickiness applies to the module version
4949
- thus these versions targeted specifically with ``module-tag`` cannot be swapped by another version from same module
5050

51-
- When a super-sticky module depends on a non-super-sticky module
51+
- Starting Modules 5.7, ``purge`` preserves requirements of retained sticky
52+
and super-sticky modules
5253

53-
- If a forced ``purge`` command occurs, the dependent module will be unloaded
54-
- Which let the super-sticky module with a missing dependency
54+
- Direct and indirect requirements are preserved, even without a sticky tag
55+
- Only strong requirements are preserved: an alternative requirement may
56+
be unloaded while another loaded module still satisfies it
57+
- A forced ``purge`` unloads sticky modules and can unload their
58+
requirements, but preserves super-sticky modules and their requirements
59+
- Before version 5.7, a forced ``purge`` could unload a non-super-sticky
60+
requirement and leave a super-sticky module with a missing dependency
5561

5662
- Starting Modules 5.2, sticky modules are unloaded
5763

@@ -88,6 +94,13 @@ Specification
8894
code
8995
- when set to ``silent``, no message reported, no error exit code
9096

97+
- Starting Modules 5.7, this configuration also applies to skipped unload
98+
of requirements of retained sticky or super-sticky modules
99+
- Requirement unload is reported as ``Unload of sticky module requirement
100+
skipped`` or ``Unload of super-sticky module requirement skipped``
101+
- If both sticky and super-sticky modules require a module, the
102+
super-sticky tag takes precedence in this message
103+
91104
- Even when ``sticky_purge`` is set to ``silent``, a warning message is
92105
reported when unload of sticky module is forced during a purge.
93106

‎doc/source/module.rst‎

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ switches are accepted:
341341
On :subcmd:`clear` sub-command, skip the confirmation dialog and proceed.
342342

343343
On :subcmd:`purge` sub-command also unload `sticky modules`_ and modulefiles
344-
that are depended by non-unloadable modules.
344+
that are depended by non-unloadable modules, except requirements of retained
345+
super-sticky modules.
345346

346347
.. only:: html or latex
347348

@@ -1772,6 +1773,9 @@ Module Sub-Commands
17721773
Error behavior when unloading sticky or super-sticky module during a module
17731774
:subcmd:`purge`.
17741775

1776+
Also applies to modules whose unload is skipped because they are required,
1777+
directly or indirectly, by a retained sticky or super-sticky module.
1778+
17751779
Raise an ``error`` (default) or emit a ``warning`` or be ``silent``. It can
17761780
be changed at installation time with :instopt:`--with-sticky-purge` option.
17771781
The :envvar:`MODULES_STICKY_PURGE` environment variable is defined by
@@ -2379,9 +2383,15 @@ Module Sub-Commands
23792383

23802384
Unload all loaded *modulefiles*.
23812385

2386+
Sticky and super-sticky modules are kept loaded, along with their required
2387+
modules, including indirect requirements. Unload attempts for these modules
2388+
are skipped according to the :mconfig:`sticky_purge` configuration: raise an
2389+
``error`` (default), emit a ``warning`` or remain ``silent``.
2390+
23822391
When the :option:`--force` option is set, also unload `sticky modules`_,
23832392
modulefiles that are depended by non-unloadable modules and modulefiles
2384-
raising an evaluation error.
2393+
raising an evaluation error. Super-sticky modules and their requirements
2394+
remain loaded even with :option:`--force`.
23852395

23862396
If one modulefile unload evaluation raises an error, purge sequence
23872397
continues: unloaded modules prior the evaluation error are kept unloaded and
@@ -2398,6 +2408,9 @@ Module Sub-Commands
23982408
.. versionchanged:: 5.4
23992409
Support for :mconfig:`abort_on_error` configuration option added
24002410

2411+
.. versionchanged:: 5.7
2412+
Preserve requirements of retained sticky and super-sticky modules
2413+
24012414
.. subcmd:: refresh
24022415

24032416
Force a refresh of all non-persistent components of currently loaded modules.
@@ -3542,6 +3555,14 @@ Modules are designated sticky by associating them the ``sticky`` or the
35423555
``super-sticky`` :ref:`module tag<Module tags>` with the :mfcmd:`module-tag`
35433556
modulefile command.
35443557

3558+
Starting with version 5.7, :subcmd:`purge` also preserves the direct and
3559+
indirect requirements of retained sticky or super-sticky modules, even if
3560+
these requirements have no sticky tag. The :mconfig:`sticky_purge`
3561+
configuration applies to their skipped unload. With :option:`--force`,
3562+
sticky modules can be unloaded, but super-sticky modules and their
3563+
requirements remain loaded. If several loaded modules satisfy an alternative
3564+
requirement, only the last necessary one is preserved.
3565+
35453566
When stickiness is defined over the generic module name (and not over a
35463567
specific module version, a version list or a version range), sticky or
35473568
super-sticky module can be swapped by another version of module. For instance
@@ -6128,6 +6149,9 @@ ENVIRONMENT
61286149
:subcmd:`purge`, raise an ``error`` or emit a ``warning`` message or be
61296150
``silent``.
61306151

6152+
Also applies to the skipped unload of modules required, directly or
6153+
indirectly, by a retained sticky or super-sticky module.
6154+
61316155
This environment variable value supersedes the default value set in the
61326156
:mconfig:`sticky_purge` configuration option. It can be defined with the
61336157
:subcmd:`config` sub-command.
@@ -6433,4 +6457,3 @@ SEE ALSO
64336457
--------
64346458

64356459
:ref:`envml(1)`, :ref:`ml(1)`, :ref:`modulecmd(1)`, :ref:`modulefile(5)`
6436-

‎tcl/modeval.tcl‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,19 @@ proc isModuleSticky {mod} {
959959
$mod sticky 1] && ![getState force])}]
960960
}
961961

962+
proc getModuleStickyDependentTag {mod} {
963+
set sticky_tag {}
964+
foreach dep_mod [getDependentLoadedModuleList [list $mod] 1 0 0 0] {
965+
if {[isModuleTagged $dep_mod super-sticky 1]} {
966+
set sticky_tag super-sticky
967+
break
968+
} elseif {![getState force] && [isModuleTagged $dep_mod sticky 1]} {
969+
set sticky_tag sticky
970+
}
971+
}
972+
return $sticky_tag
973+
}
974+
962975
proc saveLoadedReqOfUnloadingModule {unload_mod} {
963976
# fetch requirements of unloading module
964977
set ::g_savedLoReqOfUnloadMod($unload_mod)\
@@ -1172,6 +1185,21 @@ proc failOrSkipUnloadIfSticky {modname modfile} {
11721185
return 0
11731186
}
11741187

1188+
proc failOrSkipUnloadIfRequiredBySticky {mod} {
1189+
set sticky_tag [getModuleStickyDependentTag $mod]
1190+
if {$sticky_tag eq {}} {
1191+
return 0
1192+
}
1193+
1194+
set msg [getStickyReqUnloadMsg $sticky_tag]
1195+
switch -- [getConf sticky_purge] {
1196+
error {knerror $msg}
1197+
warning {reportWarning $msg}
1198+
}
1199+
1200+
return 1
1201+
}
1202+
11751203
# ;;; Local Variables:
11761204
# ;;; Mode: tcl-mode
11771205
# ;;; tcl-indent-level: 3

‎tcl/report.tcl.in‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,10 @@ proc getStickyUnloadMsg {{tag sticky}} {
11151115
return "Unload of $tag module skipped"
11161116
}
11171117
1118+
proc getStickyReqUnloadMsg {{tag sticky}} {
1119+
return "Unload of $tag module requirement skipped"
1120+
}
1121+
11181122
proc getStickyForcedUnloadMsg {} {
11191123
return {Unload of sticky module forced}
11201124
}

0 commit comments

Comments
 (0)