Skip to content

Commit 051c3b8

Browse files
committed
PYCBC-1811: Fix conditional GIL hold in pycbc_logger_sink
Changes -------- * Add pycbc::gil_acquire_guard in src/gil_guard.hxx, a sibling of Ticket I's gil_release_guard that wraps PyGILState_Ensure/Release instead of PyEval_SaveThread/RestoreThread, since the sink runs on a core IO thread that may hold no Python thread state at all * Replace the raw PyGILState_Ensure/Release pair and try/catch in pycbc_logger_sink::log_it_ with the guard. The catch(...) block only released the GIL when active_ was still true, so a throw racing the atexit deactivate() call held the GIL forever on that thread; the guard now releases unconditionally on every exit path, including exception unwind, so the try/catch is no longer needed * Leave ~pycbc_logger_sink's conditional Ensure/Release alone, it is intentional and already balanced Change-Id: I3fb7c793200dcd51dd91899a0bf06339d25a37fa Reviewed-on: https://review.couchbase.org/c/couchbase-python-client/+/250498 Reviewed-by: Sergey Avseyev <sergey.avseyev@gmail.com> Tested-by: Build Bot <build@couchbase.com>
1 parent 1c1f21b commit 051c3b8

2 files changed

Lines changed: 62 additions & 46 deletions

File tree

src/gil_guard.hxx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,29 @@ private:
4646
PyThreadState* state_;
4747
};
4848

49+
// Acquires the GIL for the guard's lifetime, for threads that may hold no Python thread state
50+
// at all (e.g. a core IO thread). Always releases on scope exit, including exception unwind,
51+
// unlike a bare PyGILState_Ensure/Release pair with a release site in every early return.
52+
class gil_acquire_guard
53+
{
54+
public:
55+
gil_acquire_guard()
56+
: state_(PyGILState_Ensure())
57+
{
58+
}
59+
60+
~gil_acquire_guard()
61+
{
62+
PyGILState_Release(state_);
63+
}
64+
65+
gil_acquire_guard(const gil_acquire_guard&) = delete;
66+
gil_acquire_guard& operator=(const gil_acquire_guard&) = delete;
67+
gil_acquire_guard(gil_acquire_guard&&) = delete;
68+
gil_acquire_guard& operator=(gil_acquire_guard&&) = delete;
69+
70+
private:
71+
PyGILState_STATE state_;
72+
};
73+
4974
} // namespace pycbc

src/logger.hxx

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#pragma once
1919

2020
#include "Python.h"
21+
#include "gil_guard.hxx"
2122
#include <atomic>
2223
#include <core/logger/configuration.hxx>
2324
#include <core/logger/logger.hxx>
@@ -132,61 +133,51 @@ public:
132133
protected:
133134
void log_it_(const spdlog::details::log_msg& msg)
134135
{
135-
PyGILState_STATE state = PyGILState_Ensure();
136-
try {
136+
pycbc::gil_acquire_guard gil;
137137

138-
// convert the log_msg_copy to a dict first...
139-
auto pyObj_log_record_details = convert_log_msg(msg);
140-
if (nullptr == pyObj_log_record_details) {
141-
PyErr_WriteUnraisable(pyObj_logger_);
142-
PyGILState_Release(state);
143-
return;
144-
}
138+
// convert the log_msg_copy to a dict first...
139+
auto pyObj_log_record_details = convert_log_msg(msg);
140+
if (nullptr == pyObj_log_record_details) {
141+
PyErr_WriteUnraisable(pyObj_logger_);
142+
return;
143+
}
145144

146-
// now, create an actual LogRecord from it...
147-
auto pyObj_log_record = PyObject_CallObject(pyObj_log_record_type_, pyObj_log_record_details);
148-
Py_DECREF(pyObj_log_record_details);
149-
if (nullptr != pyObj_log_record) {
150-
// we need to fixup the created time, which cannot be passed in the constructor...
151-
// The created member is a float containing a float expressed as seconds since the epoch, in
152-
// UTC.
153-
PyObject* log_time = convert_time_to_float(msg.time);
154-
if (nullptr == log_time) {
145+
// now, create an actual LogRecord from it...
146+
auto pyObj_log_record = PyObject_CallObject(pyObj_log_record_type_, pyObj_log_record_details);
147+
Py_DECREF(pyObj_log_record_details);
148+
if (nullptr != pyObj_log_record) {
149+
// we need to fixup the created time, which cannot be passed in the constructor...
150+
// The created member is a float containing a float expressed as seconds since the epoch, in
151+
// UTC.
152+
PyObject* log_time = convert_time_to_float(msg.time);
153+
if (nullptr == log_time) {
154+
PyErr_WriteUnraisable(pyObj_log_record);
155+
} else {
156+
if (-1 == PyObject_SetAttrString(pyObj_log_record, "created", log_time)) {
155157
PyErr_WriteUnraisable(pyObj_log_record);
156-
} else {
157-
if (-1 == PyObject_SetAttrString(pyObj_log_record, "created", log_time)) {
158-
PyErr_WriteUnraisable(pyObj_log_record);
159-
}
160-
Py_DECREF(log_time);
161158
}
159+
Py_DECREF(log_time);
160+
}
162161

163-
// now, we want to hand this record to the logger...
164-
PyObject* pyObj_args = PyTuple_Pack(1, pyObj_log_record);
165-
if (nullptr == pyObj_args) {
162+
// now, we want to hand this record to the logger...
163+
PyObject* pyObj_args = PyTuple_Pack(1, pyObj_log_record);
164+
if (nullptr == pyObj_args) {
165+
PyErr_WriteUnraisable(pyObj_logger_handle_method_);
166+
} else {
167+
PyObject* pyObj_handle_result =
168+
PyObject_CallObject(pyObj_logger_handle_method_, pyObj_args);
169+
if (nullptr == pyObj_handle_result) {
166170
PyErr_WriteUnraisable(pyObj_logger_handle_method_);
167171
} else {
168-
PyObject* pyObj_handle_result =
169-
PyObject_CallObject(pyObj_logger_handle_method_, pyObj_args);
170-
if (nullptr == pyObj_handle_result) {
171-
PyErr_WriteUnraisable(pyObj_logger_handle_method_);
172-
} else {
173-
Py_DECREF(pyObj_handle_result);
174-
}
175-
Py_DECREF(pyObj_args);
172+
Py_DECREF(pyObj_handle_result);
176173
}
177-
178-
// that's it, now cleanup.
179-
Py_DECREF(pyObj_log_record);
180-
} else {
181-
PyErr_WriteUnraisable(pyObj_log_record_type_);
182-
}
183-
PyGILState_Release(state);
184-
} catch (...) {
185-
// Only release GIL if still active
186-
if (active_.load(std::memory_order_acquire)) {
187-
PyGILState_Release(state);
174+
Py_DECREF(pyObj_args);
188175
}
189-
throw;
176+
177+
// that's it, now cleanup.
178+
Py_DECREF(pyObj_log_record);
179+
} else {
180+
PyErr_WriteUnraisable(pyObj_log_record_type_);
190181
}
191182
}
192183

0 commit comments

Comments
 (0)