Skip to content

Exception-unsafe ACQUIRE_GLOBAL_LOCK/RELEASE_GLOBAL_LOCK in solver try/catch blocks #226

Description

@devdanzin

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions