Skip to content

Commit e32ed3e

Browse files
Fix DECREF bug during interpreter shutdown (#275)
This solves a long standing bug where pybind calls DECREF on a _static_ py::object during interpreter shutdown, which is too late.
2 parents 3ca4738 + 39c384b commit e32ed3e

1 file changed

Lines changed: 15 additions & 9 deletions

File tree

src/duckdb_py/common/exceptions.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -350,13 +350,19 @@ void RegisterExceptions(const py::module &m) {
350350
auto io_exception = py::register_exception<PyIOException>(m, "IOException", operational_error).ptr();
351351
py::register_exception<PySerializationException>(m, "SerializationException", operational_error);
352352

353-
static py::exception<PyHTTPException> HTTP_EXCEPTION(m, "HTTPException", io_exception);
354-
const auto string_type = py::type::of(py::str());
355-
const auto Dict = py::module_::import("typing").attr("Dict");
356-
HTTP_EXCEPTION.attr("__annotations__") =
357-
py::dict(py::arg("status_code") = py::type::of(py::int_()), py::arg("body") = string_type,
358-
py::arg("reason") = string_type, py::arg("headers") = Dict[py::make_tuple(string_type, string_type)]);
359-
HTTP_EXCEPTION.doc() = "Thrown when an error occurs in the httpfs extension, or whilst downloading an extension.";
353+
// Use a raw pointer to avoid destructor running after Python finalization.
354+
// The module holds a reference to the exception type, keeping it alive.
355+
static PyObject *HTTP_EXCEPTION = nullptr;
356+
{
357+
auto http_exc = py::register_exception<PyHTTPException>(m, "HTTPException", io_exception);
358+
HTTP_EXCEPTION = http_exc.ptr();
359+
const auto string_type = py::type::of(py::str());
360+
const auto Dict = py::module_::import("typing").attr("Dict");
361+
http_exc.attr("__annotations__") = py::dict(
362+
py::arg("status_code") = py::type::of(py::int_()), py::arg("body") = string_type,
363+
py::arg("reason") = string_type, py::arg("headers") = Dict[py::make_tuple(string_type, string_type)]);
364+
http_exc.doc() = "Thrown when an error occurs in the httpfs extension, or whilst downloading an extension.";
365+
}
360366

361367
// IntegrityError
362368
auto integrity_error = py::register_exception<IntegrityError>(m, "IntegrityError", db_error).ptr();
@@ -388,7 +394,7 @@ void RegisterExceptions(const py::module &m) {
388394
} catch (const duckdb::Exception &ex) {
389395
duckdb::ErrorData error(ex);
390396
UnsetPythonException();
391-
PyThrowException(error, HTTP_EXCEPTION.ptr());
397+
PyThrowException(error, HTTP_EXCEPTION);
392398
} catch (const py::builtin_exception &ex) {
393399
// These represent Python exceptions, we don't want to catch these
394400
throw;
@@ -399,7 +405,7 @@ void RegisterExceptions(const py::module &m) {
399405
throw;
400406
}
401407
UnsetPythonException();
402-
PyThrowException(error, HTTP_EXCEPTION.ptr());
408+
PyThrowException(error, HTTP_EXCEPTION);
403409
}
404410
});
405411
}

0 commit comments

Comments
 (0)