Repository navigation
Conversation
A later context manager cleared control taken by a plain threadpool_limits() call or a direct set_num_threads(), and every OpenBLAS copy shared one controlling thread. Co-authored-by: Cursor <cursoragent@cursor.com>
…them. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…sticking. Also add SHARED_POOL_CONTROL_OWNERSHIP.increment() and decrement() without a key to act on every shared pool this thread can control, and rename the ownership helpers so they are not OpenBLAS-specific. Co-authored-by: Cursor <cursoragent@cursor.com>
Put _SharedPoolState above the registry and document the lock order. Use one helper to find claimable shared pools for both the bare increment() and the limiter. Drop a duplicate test stand-in. Co-authored-by: Cursor <cursoragent@cursor.com>
Drop _shared_pool_key(): the library filepath already identifies the pool. SHARED_POOL_CONTROL_OWNERSHIP.increment() and decrement() take an optional controller and default to get_cached_controller(). Rename variables and tests to match what they now describe. Co-authored-by: Cursor <cursoragent@cursor.com>
…rol. Only increment() and decrement() are part of SHARED_POOL_CONTROL_OWNERSHIP. Update the README note on OpenBLAS pthreads/Windows to describe one controlling thread per loaded copy. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
OpenBLAS reports 0 threads until a BLAS call has picked a count, and set_num_threads(0) cannot return to that state. The autouse fixture now sets the processor count when the count is unset, before and after each test, and clears the controlling thread. Co-authored-by: Cursor <cursoragent@cursor.com>
…OWNERSHIP Restore the name used before the per-library ownership change. Co-authored-by: Cursor <cursoragent@cursor.com>
Revert the per-copy wording in that section and the clear_controlling_thread() call in the restore example, so the section stays focused on the limits. Co-authored-by: Cursor <cursoragent@cursor.com>
…mp_openblas The helper only returns non-OpenMP OpenBLAS libraries, so the name says so. Co-authored-by: Cursor <cursoragent@cursor.com>
clear_controlling_thread() is now an instance method that drops the claim on one loaded copy. The former class method is clear_all_controlling_threads() and still clears every copy of that controller class. Forced clears bump a generation per filepath, so clearing one copy does not drop another copy's counts. Co-authored-by: Cursor <cursoragent@cursor.com>
Drop _snapshot_shared_pool_states(), which had no callers. Clearing a holder and its sticky flag now goes through _clear_shared_pool_holder() for both per-library and per-class clears. A per-library clear no longer syncs every pool before forgetting its own count. Co-authored-by: Cursor <cursoragent@cursor.com>
…claim A threadpool_limits.wrap() decorator reused one limiter for every call, so a recursive or concurrent call overwrote the claims of the outer call and left the OpenBLAS controlling thread set after the outermost call returned. Each call now gets a fresh limiter. Also rename the ownership helpers and class to match CONTROLLING_THREAD_OWNERSHIP, make the ownership counters per-thread state set in __init__, merge the duplicated holder-clearing code, and document that a forced clear during an active scope can make the next claim sticky. Co-authored-by: Cursor <cursoragent@cursor.com>
…lder A generation epoch lets a thread skip the generation walk when nothing has been force-cleared. A limiter scope keeps the pool state it counted so dropping an unheld claim does not take the registry lock. The wrap decorator now copies itself per call instead of relying on ContextDecorator. Co-authored-by: Cursor <cursoragent@cursor.com>
…copes A limiter that lost the claim race dropped its count after an unlocked read of the winner. On free-threaded CPython that read could miss the winning write and keep a count for a pool it does not hold, making the next claim sticky. The read now takes the pool lock. The iterator also yields the pool state it already looked up, and the decrement no longer takes a flag, since the generation epoch makes the extra sync cheap. Co-authored-by: Cursor <cursoragent@cursor.com>
…braries The autouse fixture builds a ThreadpoolController, which warns when LLVM and Intel OpenMP are both loaded. pytest runs with -W error, so that warning aborted test_libomp_libiomp_warning during setup, before the test could record it. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Are those real issues? Respectively:
|
Tests that hand the claim to another thread now record and assert which thread holds it, not only the thread count. The recursive-wrap test now runs through threadpool_limits.wrap on a real OpenBLAS library. Co-authored-by: Cursor <cursoragent@cursor.com>
Say loaded OpenBLAS library instead of copy, and note that NumPy and SciPy can each load their own. Describe the automatic release for context managers and decorators and point to the OpenBLASController docstring for details. Co-authored-by: Cursor <cursoragent@cursor.com>
|
This seems complicated, just glancing at the number of lines of code in EDIT: i.e. add some sort of mapping from filepath to controlling thread to the original implementation's code. |
|
Yes I agree this is complicated and I am still not sure if it is worth it or no myself.
This will solve the second bug described in this PR's decription but would probably be enough to solver the first problem. In particular, I doubt it would make I'd like to iterate to see if we can simplify this PR to reach a better complexity/robustness tradeoff but I will probably not have the time to do it this week. That being said, I don't expect the public ownership/scope handling API to change so progress on joblib/joblib#1871 can happen independently. |
|
For EDIT: Which is in fact what the current logic does, so I guess I should go look at the failing test. |
|
Oh, so, for the first problem where plain Since all this does is revert to the way things were before, in older versions of threadpoolctl. Which was maybe not ideal but also there were no significant complaints? |
I used an LLM to review the code and it found problems with:
threadpool_limits(limits=None)could unintentally release thread ownerships of pass calls.threadpool_limits. The existing ownership model cannot deal with this case cleanly.This PR is a draft to refactor the ownership model to deal with those cases more robustly. See the tests for more details about the bugs fixed by this PR.
I am iterating with my agent to try to strike a good balance between robustness to edge cases and code complexity. I am not satisfied yet but I realized that opening this PR early could help in case @itamarst @cakedev0 want to provide general feedback about this whole endaevor.