Under free-threaded Python (Py_GIL_DISABLED), the ACQUIRE_GLOBAL_LOCK/RELEASE_GLOBAL_LOCK macros expand to global_lock.lock()/global_lock.unlock(). In all 5 solver methods that use try/catch, the lock is acquired inside try and released manually in each catch handler:
try {
ACQUIRE_GLOBAL_LOCK();
self->solver.addConstraint(cn->constraint);
RELEASE_GLOBAL_LOCK();
}
catch (const kiwi::DuplicateConstraint&) {
RELEASE_GLOBAL_LOCK();
PyErr_SetObject(DuplicateConstraint, other);
return 0;
}
catch (const kiwi::UnsatisfiableConstraint&) {
RELEASE_GLOBAL_LOCK();
PyErr_SetObject(UnsatisfiableConstraint, other);
return 0;
}
If the kiwi library throws an unexpected exception type (e.g., std::bad_alloc on OOM), none of the catch handlers match, the lock is never released, and all subsequent solver operations from any thread deadlock permanently.
Affected functions (all in solver.cpp):
Solver_addConstraint (lines 52-69)
Solver_removeConstraint (lines 80-92)
Solver_addEditVariable (lines 124-141)
Solver_removeEditVariable (lines 152-164)
Solver_suggestValue (lines 195-206)
Suggested fix — use std::lock_guard (exception-safe by design):
try {
#ifdef Py_GIL_DISABLED
std::lock_guard<std::recursive_mutex> guard(global_lock);
#endif
self->solver.addConstraint(cn->constraint);
}
catch (const kiwi::DuplicateConstraint&) {
PyErr_SetObject(DuplicateConstraint, other);
return 0;
}
This eliminates all manual RELEASE_GLOBAL_LOCK() calls and is safe against any exception type.
Found by cext-review-toolkit.
Under free-threaded Python (
Py_GIL_DISABLED), theACQUIRE_GLOBAL_LOCK/RELEASE_GLOBAL_LOCKmacros expand toglobal_lock.lock()/global_lock.unlock(). In all 5 solver methods that use try/catch, the lock is acquired insidetryand released manually in eachcatchhandler:If the kiwi library throws an unexpected exception type (e.g.,
std::bad_allocon OOM), none of the catch handlers match, the lock is never released, and all subsequent solver operations from any thread deadlock permanently.Affected functions (all in
solver.cpp):Solver_addConstraint(lines 52-69)Solver_removeConstraint(lines 80-92)Solver_addEditVariable(lines 124-141)Solver_removeEditVariable(lines 152-164)Solver_suggestValue(lines 195-206)Suggested fix — use
std::lock_guard(exception-safe by design):This eliminates all manual
RELEASE_GLOBAL_LOCK()calls and is safe against any exception type.Found by cext-review-toolkit.