Skip to content

FIX intermittent Windows module enumeration failure (#217)#219

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

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

Conversation

@ogrisel

@ogrisel ogrisel commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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
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
assert ThreadpoolController().info() == original_info


def test_threadpool_controller_repeated_init():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It's probably worthwhile running this as is, but in my testing I have only seen its code fail when it is run after import cv2 in a Windows conda-forge environment.

Comment thread threadpoolctl.py
kernel_32 = self._get_windll("kernel32")
self._setup_windows_module_apis(ps_api, kernel_32)

h_process = kernel_32.OpenProcess(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You could use kernel_32.GetCurrentProcess() here. It returns a pseudo-handle that doesn't need to be closed.

Comment thread threadpoolctl.py
Comment on lines +1076 to +1082
filepath = self._resolve_module_filepath(
ps_api,
h_process,
h_module,
snapshot_path,
max_path,
path_buf,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't think this is necessary here. In my testing, I only see _snapshot_loaded_modules() succeed and return paths when they are all complete and shorter than MAX_PATH. If any path of a loaded DLL is longer than MAX_PATH (actually 255), CreateToolhelp32Snapshot() fails.

Comment thread threadpoolctl.py
"""Return the full path for a module, or None if it should be skipped."""
from ctypes.wintypes import MAX_PATH

if ps_api.GetModuleFileNameExW(h_process, h_module, path_buf, max_path):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I suggest using kernel_32.GetModuleFileNameW(). It is ~10x faster.

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

2 participants