Skip to content

Commit 50e4410

Browse files
authored
FIX: Static UUID variable causes pytest crash (#271)
### Work Item / Issue Reference <!-- IMPORTANT: Please follow the PR template guidelines below. For mssql-python maintainers: Insert your ADO Work Item ID below (e.g. AB#37452) For external contributors: Insert Github Issue number below (e.g. #149) Only one reference is required - either GitHub issue OR ADO Work Item. --> <!-- mssql-python maintainers: ADO Work Item --> > [AB#39063](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/39063) ------------------------------------------------------------------- ### Summary <!-- Insert your summary of changes below. Minimum 10 characters required. --> ## Problem Pytest test suite (335+ tests) was crashing on exit with: ``` Fatal Python error: _Py_GetConfig: the function must be called with the GIL held, after Python initialization and before Python finalization, but the GIL is released ``` The crash occurred **after all tests passed**, during Python shutdown on Windows x64 and macOS. ## Root Cause Static `pybind11::module_` and `pybind11::object` instances in `BindParameterArray()` function (lines 2072-2073) were being destroyed during C++ static destruction phase, which happens **after** Python has released the GIL and begun finalization. When the destructors called `Py_XDECREF`, Python's `_Py_GetConfig()` was invoked without the GIL, causing the fatal error. ```cpp // OLD CODE - Problematic static objects static py::module_ uuid_mod = py::module_::import("uuid"); static py::object uuid_class = uuid_mod.attr("UUID"); ``` ## Solution Moved UUID class caching to module initialization level using a helper function. This ensures the Python object lifecycle is managed within pybind11's module context, avoiding destructor calls during finalization. ```cpp // NEW CODE - Module-level cached helper py::object uuid_class = py::module_::import("mssql_python.ddbc_bindings").attr("_get_uuid_class")(); ``` The `_get_uuid_class()` helper is defined at module initialization and maintains a static cache that lives for the module's lifetime, properly integrated with pybind11's lifecycle management. ## Testing - ✅ Full test suite (335+ tests) now completes without crash on macOS - ✅ Exit code 0 after pytest completion - ✅ No functionality changes - UUID parameter binding works identically ## Impact - **Platforms affected**: Windows x64, macOS (all platforms with the issue) - **Risk**: Low - surgical fix, no behavioral changes - **Files changed**: 1 file, ~15 lines added/modified
1 parent 7e65290 commit 50e4410

File tree

1 file changed

+12
-2
lines changed

1 file changed

+12
-2
lines changed

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,8 +2069,10 @@ SQLRETURN BindParameterArray(SQLHANDLE hStmt,
20692069
SQLGUID* guidArray = AllocateParamBufferArray<SQLGUID>(tempBuffers, paramSetSize);
20702070
strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
20712071

2072-
static py::module_ uuid_mod = py::module_::import("uuid");
2073-
static py::object uuid_class = uuid_mod.attr("UUID");
2072+
// Get cached UUID class from module-level helper
2073+
// This avoids static object destruction issues during Python finalization
2074+
py::object uuid_class = py::module_::import("mssql_python.ddbc_bindings").attr("_get_uuid_class")();
2075+
20742076
for (size_t i = 0; i < paramSetSize; ++i) {
20752077
const py::handle& element = columnValues[i];
20762078
std::array<unsigned char, 16> uuid_bytes;
@@ -3903,6 +3905,14 @@ PYBIND11_MODULE(ddbc_bindings, m) {
39033905
});
39043906

39053907

3908+
// Module-level UUID class cache
3909+
// This caches the uuid.UUID class at module initialization time and keeps it alive
3910+
// for the entire module lifetime, avoiding static destructor issues during Python finalization
3911+
m.def("_get_uuid_class", []() -> py::object {
3912+
static py::object uuid_class = py::module_::import("uuid").attr("UUID");
3913+
return uuid_class;
3914+
}, "Internal helper to get cached UUID class");
3915+
39063916
// Add a version attribute
39073917
m.attr("__version__") = "1.0.0";
39083918

0 commit comments

Comments
 (0)