Skip to content

FIX intermittent Windows module enumeration failure - #219

Open
ogrisel wants to merge 22 commits into
joblib:masterfrom
ogrisel:fix-windows-module-enumeration-217
Open

FIX intermittent Windows module enumeration failure#219
ogrisel wants to merge 22 commits into
joblib:masterfrom
ogrisel:fix-windows-module-enumeration-217

Conversation

@ogrisel

@ogrisel ogrisel commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Fix #217

Summary

  • Fix intermittent OSError("GetModuleFileNameEx failed") on Windows when DLLs are loaded or unloaded during library discovery (reported with conda-forge OpenCV).
  • Replace the two-phase EnumProcessModulesEx + GetModuleFileNameExW scan with a snapshot-first approach using CreateToolhelp32Snapshot, keeping long-path support via GetModuleFileNameExW and graceful per-module fallbacks.
  • Add Windows integration tests for library paths longer than MAX_PATH, inspired by the local repro documented in ENH Support path length > MAX_PATH=260 on Windows #189 (comment).

Closes #217

Test plan

AI assistance disclosure

This pull request was prepared with assistance from an AI coding agent (Cursor / Claude).

Made with Cursor

Use CreateToolhelp32Snapshot for atomic module discovery on Windows,
with GetModuleFileNameExW for long paths and graceful per-module
fallbacks when handles go stale. Add Windows integration tests for
paths longer than MAX_PATH.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread continuous_integration/check_no_test_skipped.py Outdated
Comment thread tests/test_threadpoolctl.py Outdated
Comment thread tests/utils.py Outdated
Comment thread tests/utils.py Outdated
ogrisel and others added 2 commits July 9, 2026 10:18
- Run repeated-init stress test on all platforms
- Drop redundant OpenBLAS glob pattern and win32 guard in utils
- Remove MAX_PATH tests from SAFE_SKIPPED_TESTS whitelist

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Dugal Harris <dugal@leftfield.online>
Co-authored-by: Cursor <cursoragent@cursor.com>
ogrisel and others added 4 commits July 9, 2026 14:08
Correct numpy.libs glob to use the site-packages sibling directory,
add conda-forge Library/bin patterns, and fall back to the loaded
OpenBLAS filepath from ThreadpoolController when globs find nothing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use extended-length paths when copying and loading test DLLs so
LoadLibrary works beyond MAX_PATH, and pass the module buffer
directly to EnumProcessModulesEx to match its ctypes signature.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use libopenblas-prefixed destination DLL names so conda builds are
recognized, normalize extended-length paths for comparisons, prefer
shipped libscipy_openblas sources, and detect truncation when the
resolved path fills the GetModuleFileNameExW buffer.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread CHANGES.md Outdated
@ogrisel
ogrisel marked this pull request as ready for review July 9, 2026 16:34
Comment thread tests/test_threadpoolctl.py
Comment thread threadpoolctl.py Outdated
Comment thread threadpoolctl.py Outdated
Comment thread threadpoolctl.py
Load cv2 before stressing ThreadpoolController() to match the Windows
conda-forge repro from issue joblib#217, and install OpenCV in CI jobs that
exercise this test.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use GetCurrentProcess(), trust snapshot szExePath on the happy path, and
resolve fallback module paths with GetModuleFileNameW before the slower
GetModuleFileNameExW long-path lookup.

Co-authored-by: Cursor <cursoragent@cursor.com>

@ogrisel ogrisel left a comment

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.

Code style review.

Comment thread tests/utils.py Outdated
Comment thread tests/utils.py Outdated
Comment thread tests/utils.py Outdated
Comment thread tests/utils.py Outdated
Comment thread tests/test_threadpoolctl.py Outdated
ogrisel and others added 3 commits July 10, 2026 14:43
Move ThreadpoolController usage to module scope in utils, hoist conda
OpenBLAS globs out of the numpy import block, and inline Windows
MAX_PATH test setup in the tests instead of small utils helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@ogrisel ogrisel left a comment

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.

I think this LGTM. The licensing issue is still there but will be addressed in a follow-up PR to decouple the concerns.

cc @jeremiedbb @tomMoral.

Comment thread threadpoolctl.py Outdated
@ogrisel

ogrisel commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

It seems that clang conda-forge packaging has changed because it can no longer find its openmp but it's unrelated to this PR.

@ogrisel

ogrisel commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

I synced with master and everything is green thanks to @lesteve's recent CI fixes.

Shall we merge?

@lesteve lesteve changed the title FIX intermittent Windows module enumeration failure (#217) FIX intermittent Windows module enumeration failure Sep 7, 2026
@lesteve

lesteve commented Sep 7, 2026

Copy link
Copy Markdown
Member

Codex /review tells me the following. I am afraid that I haven't tried to understand yet whether it makes sense or not, very sorry about that 😓

[P1] Bound snapshot retries so long paths reach the fallback

File: threadpoolctl.py:1312-1313

When CreateToolhelp32Snapshot persistently returns ERROR_BAD_LENGTH—notably in the long-DLL-path scenario this fallback is intended to handle—this unconditional retry loops forever, so _find_libraries_on_windows never catches an
OSError and never invokes EnumProcessModulesEx.

Limit the retries and raise after exhaustion so discovery can use the long-path-capable fallback.

@lesteve

lesteve commented Sep 7, 2026

Copy link
Copy Markdown
Member

I can reproduce the original on my Windows VM so I'll give it a try as a sanity check.

@ogrisel

ogrisel commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Here is the reply from agent to your agent:

The Codex diagnosis is incorrect, but a retry cap would still be a reasonable hardening change.

ERROR_BAD_LENGTH (24) is the documented transient race when the module list changes while CreateToolhelp32Snapshot is taken. MSDN's own wording for TH32CS_SNAPMODULE / TH32CS_SNAPMODULE32 is to retry until it succeeds. The while True loop is that guidance, not a forgotten bound.

Long DLL paths are a different failure mode: MODULEENTRY32W.szExePath is limited to MAX_PATH. If the snapshot succeeds, we get a truncated path and skip that library with a warning. The EnumProcessModulesEx fallback is only used when snapshot creation raises OSError, not when szExePath is truncated. ERROR_BAD_LENGTH is not the long-path error.

test_windows_library_path_longer_than_max_path already exercises that case. If long paths caused a persistent ERROR_BAD_LENGTH spin, that test would hang; it does not.

A bounded retry (say 8-16 attempts, no sleep) then raise OSError(...) would still be good library hygiene, since ThreadpoolController() sits on a hot path and an unbounded loop could hang if GetLastError were ever sticky or mis-attributed. I would treat that as optional hardening, not a merge blocker for #217.

A related but separate gap: if the snapshot succeeds with a truncated szExePath, we currently skip the library instead of resolving that one handle with GetModuleFileNameExW. That is independent of the infinite-loop claim.

ogrisel and others added 4 commits September 8, 2026 17:54
Keep the Toolhelp snapshot as the module list, and only call GetModuleFileNameExW for empty or MAX_PATH-truncated szExePath values instead of skipping those libraries.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

ThreadpoolController() raises an error intermittently with conda-forge OpenCV on Windows

3 participants