Skip to content

fix[next]: unique sys.modules key per compiled program variant - #2740

Open
tehrengruber wants to merge 2 commits into
GridTools:mainfrom
tehrengruber:fix-compiled-program-sys-modules-key
Open

fix[next]: unique sys.modules key per compiled program variant#2740
tehrengruber wants to merge 2 commits into
GridTools:mainfrom
tehrengruber:fix-compiled-program-sys-modules-key

Conversation

@tehrengruber

@tehrengruber tehrengruber commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

#2431 keeps a nanobind module alive by registering it in sys.modules, but the key was just the entry_point_name. Static variants of the same program share that name (and the .so base name), so they collided and evicted one another:

# variant_a / variant_b: same program, different static args
variant_a: CompilationArtifact
variant_b: CompilationArtifact
func_a = variant_a.load()  # sys.modules["gt4py.__compiled_programs__.my_fo"] = module_a
func_b = variant_b.load()  # overwrites the key; module_a now only reachable via func_a

del variant_a
# on nanobind >=2.10 module_a can be GC'd (nb_module_clear) while func_a
# is still in use -> segfault when func_a is called

Solution here is to not rely on sys.modules, but just keep a reference to the module in a global variable.

Text and code generated with the help of LLMs and then verified by me.

tehrengruber added a commit to tehrengruber/gt4py that referenced this pull request Jul 30, 2026
@tehrengruber
tehrengruber force-pushed the fix-compiled-program-sys-modules-key branch from 00c8b78 to a93f3af Compare July 30, 2026 15:22
tehrengruber added a commit to tehrengruber/gt4py that referenced this pull request Jul 30, 2026
tehrengruber added a commit to tehrengruber/gt4py that referenced this pull request Jul 30, 2026
@tehrengruber
tehrengruber force-pushed the fix-compiled-program-sys-modules-key branch from a93f3af to ba333cf Compare July 30, 2026 21:20
@tehrengruber
tehrengruber requested a review from egparedes July 30, 2026 21:21

@egparedes egparedes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure about using the holding variable as a cache, so proposed an alternative.

Comment on lines +37 to +38
if keep_reference and (cached := _loaded_modules.get(key)) is not None:
return cached

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure if using the _loaded_modules variable as a cache is a good idea, I think it can add hidden bugs in weird corner cases which might be very hard to track. Are you aware of any scenario where this would matter for performance? Otherwise, I think I'd prefer to define it something similar to:

_loaded_modules: list[tuple[str, Module]]
...
_loaded_modules.append((key, loaded_module))

…llisions

GridTools#2431 kept a nanobind module alive by registering it in `sys.modules`, but the
key was just the `entry_point_name`. Static variants of the same program share
that name (and the `.so` base name), so they collided and evicted one another:

    # variant_a / variant_b: same program, different static args
    func_a = variant_a.load()  # sys.modules["...copy"] = module_a
    func_b = variant_b.load()  # overwrites the key; module_a now only
                               # reachable via func_a

    del variant_a
    # on nanobind >=2.10 module_a can be GC'd (nb_module_clear) while func_a
    # is still in use -> segfault when func_a is called

Replace the `sys.modules` registration with an opt-in `keep_reference` flag on
`import_from_path` that appends the module to a private, process-wide list. The
compiler's `load()` (which returns a function to be called later) passes it, so
every loaded variant stays alive; callers that only introspect the module leave
the default and don't pollute the process.
@tehrengruber
tehrengruber force-pushed the fix-compiled-program-sys-modules-key branch from ba333cf to 8ea829a Compare July 31, 2026 11:42
module = importer.import_from_path(
file, add_to_sys_modules=True, sys_modules_prefix="_temp_test_prefix_"
)
# Without keeping a reference, the module is loaded but not retained.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not sure if these tests still make sense. I'm leaning to remove them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Those are testing if the keep_reference option works as intended, right? I think they still make sense, but feel free to remove them since in the current trivial this is trivially true.

@egparedes egparedes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

module = importer.import_from_path(
file, add_to_sys_modules=True, sys_modules_prefix="_temp_test_prefix_"
)
# Without keeping a reference, the module is loaded but not retained.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Those are testing if the keep_reference option works as intended, right? I think they still make sense, but feel free to remove them since in the current trivial this is trivially true.

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.

2 participants