Skip to content

Commit 03e5424

Browse files
committed
PYCBC-1820: Add C++ exception boundaries at extension entry points
Changes -------- * Wrap execute_multi_op's body in try/catch(const std::exception&), matching execute_kv_op's existing shape, so a throw from py_to_cbpp (e.g. invalid-UTF-8 bucket/scope/collection/key) can't unwind uncaught into CPython * Convert every Py_BEGIN/END_ALLOW_THREADS pair in transactions.cxx (21 across the 9 module-level entry points plus dealloc_transactions) to pycbc::gil_release_guard, and wrap each in try/catch: the guard restores the GIL during unwind before the catch runs, which the bare macro pair doesn't * Dispatch-call failures raise_invalid_argument after releasing any already-incref'd callback/errback refs; sync fut.get() failures use set_runtime_error_if_unset; dealloc_transactions logs and swallows, since a dealloc can't propagate an exception * Fix create_transaction_context leaking py_ctx on a PyCapsule_New failure, found while adding its boundary Change-Id: Ia575112e0b59ec2139151a86ae247eadf913cbee Reviewed-on: https://review.couchbase.org/c/couchbase-python-client/+/250614 Tested-by: Build Bot <build@couchbase.com> Reviewed-by: Dimitris Christodoulou <dimitris.christodoulou@couchbase.com>
1 parent 8567599 commit 03e5424

2 files changed

Lines changed: 393 additions & 269 deletions

File tree

src/connection.hxx

Lines changed: 68 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -496,83 +496,88 @@ Connection::execute_multi_op(PyObject* arg)
496496
}
497497
pycbc_result* multi_result = reinterpret_cast<pycbc_result*>(pyObj_multi_result);
498498

499-
for (size_t i = 0; i < num_docs; ++i) {
500-
PyObject* pyObj_binding = PyList_GetItem(arg, i); // Borrowed ref
501-
// Unchecked by contract, see validate_connection_and_multi_request
502-
pycbc_kv_request* request = reinterpret_cast<pycbc_kv_request*>(pyObj_binding);
503-
std::string key_str = py_to_cbpp<std::string>(request->key);
504-
std::shared_ptr<couchbase::core::tracing::wrapper_sdk_span> wrapper_span;
505-
std::string span_name;
506-
extract_field(request->wrapper_span_name, span_name);
507-
if (!span_name.empty()) {
508-
wrapper_span = std::make_shared<couchbase::core::tracing::wrapper_sdk_span>(span_name);
509-
}
510-
511-
auto req = py_to_cbpp<Request>(request, wrapper_span);
512-
if (PyErr_Occurred()) {
513-
Py_DECREF(pyObj_multi_result);
514-
return nullptr;
515-
}
499+
try {
500+
for (size_t i = 0; i < num_docs; ++i) {
501+
PyObject* pyObj_binding = PyList_GetItem(arg, i); // Borrowed ref
502+
// Unchecked by contract, see validate_connection_and_multi_request
503+
pycbc_kv_request* request = reinterpret_cast<pycbc_kv_request*>(pyObj_binding);
504+
std::string key_str = py_to_cbpp<std::string>(request->key);
505+
std::shared_ptr<couchbase::core::tracing::wrapper_sdk_span> wrapper_span;
506+
std::string span_name;
507+
extract_field(request->wrapper_span_name, span_name);
508+
if (!span_name.empty()) {
509+
wrapper_span = std::make_shared<couchbase::core::tracing::wrapper_sdk_span>(span_name);
510+
}
516511

517-
// TODO(PYCBC-1746): Delete w/ removal of legacy tracing logic
518-
if (wrapper_span == nullptr) {
519-
add_cluster_labels(req);
520-
}
512+
auto req = py_to_cbpp<Request>(request, wrapper_span);
513+
if (PyErr_Occurred()) {
514+
Py_DECREF(pyObj_multi_result);
515+
return nullptr;
516+
}
521517

522-
std::optional<std::chrono::system_clock::time_point> start_time;
523-
if (request->with_metrics == Py_True) {
524-
start_time = std::chrono::system_clock::now();
525-
}
518+
// TODO(PYCBC-1746): Delete w/ removal of legacy tracing logic
519+
if (wrapper_span == nullptr) {
520+
add_cluster_labels(req);
521+
}
526522

527-
auto barrier = std::make_shared<std::promise<Response>>();
528-
auto fut = barrier->get_future();
523+
std::optional<std::chrono::system_clock::time_point> start_time;
524+
if (request->with_metrics == Py_True) {
525+
start_time = std::chrono::system_clock::now();
526+
}
529527

530-
staging.push_back({ std::move(req),
531-
std::move(key_str),
532-
std::move(wrapper_span),
533-
start_time,
534-
std::move(barrier),
535-
std::move(fut) });
536-
}
528+
auto barrier = std::make_shared<std::promise<Response>>();
529+
auto fut = barrier->get_future();
537530

538-
{
539-
gil_release_guard no_gil;
540-
for (auto& s : staging) {
541-
auto barrier = s.barrier;
542-
cluster_.execute(s.req, [barrier](Response resp) {
543-
barrier->set_value(std::move(resp));
544-
});
531+
staging.push_back({ std::move(req),
532+
std::move(key_str),
533+
std::move(wrapper_span),
534+
start_time,
535+
std::move(barrier),
536+
std::move(fut) });
545537
}
546538

547-
for (auto& s : staging) {
548-
s.fut.wait();
549-
}
550-
}
539+
{
540+
gil_release_guard no_gil;
541+
for (auto& s : staging) {
542+
auto barrier = s.barrier;
543+
cluster_.execute(s.req, [barrier](Response resp) {
544+
barrier->set_value(std::move(resp));
545+
});
546+
}
551547

552-
bool all_okay = true;
553-
for (auto& s : staging) {
554-
PyObject* res =
555-
finalize_kv_result<Request>(s.fut.get(), std::move(s.wrapper_span), std::move(s.start_time));
556-
// OOM is not a per-key condition, so abandon the whole multi result rather than
557-
// reporting a partial one. An exception is already pending.
558-
if (res == nullptr) {
559-
Py_DECREF(pyObj_multi_result);
560-
return nullptr;
548+
for (auto& s : staging) {
549+
s.fut.wait();
550+
}
561551
}
562-
if (PyObject_TypeCheck(res, &pycbc_exception_type)) {
563-
all_okay = false;
552+
553+
bool all_okay = true;
554+
for (auto& s : staging) {
555+
PyObject* res = finalize_kv_result<Request>(
556+
s.fut.get(), std::move(s.wrapper_span), std::move(s.start_time));
557+
// OOM is not a per-key condition, so abandon the whole multi result rather than
558+
// reporting a partial one. An exception is already pending.
559+
if (res == nullptr) {
560+
Py_DECREF(pyObj_multi_result);
561+
return nullptr;
562+
}
563+
if (PyObject_TypeCheck(res, &pycbc_exception_type)) {
564+
all_okay = false;
565+
}
566+
int rc = PyDict_SetItemString(multi_result->raw_result, s.key_str.c_str(), res);
567+
Py_DECREF(res);
568+
if (rc < 0) {
569+
Py_DECREF(pyObj_multi_result);
570+
return nullptr;
571+
}
564572
}
565-
int rc = PyDict_SetItemString(multi_result->raw_result, s.key_str.c_str(), res);
566-
Py_DECREF(res);
567-
if (rc < 0) {
573+
if (PyDict_SetItemString(multi_result->raw_result, "all_okay", all_okay ? Py_True : Py_False) <
574+
0) {
568575
Py_DECREF(pyObj_multi_result);
569576
return nullptr;
570577
}
571-
}
572-
if (PyDict_SetItemString(multi_result->raw_result, "all_okay", all_okay ? Py_True : Py_False) <
573-
0) {
578+
} catch (const std::exception& e) {
574579
Py_DECREF(pyObj_multi_result);
575-
return nullptr;
580+
return raise_invalid_argument(e.what());
576581
}
577582

578583
return pyObj_multi_result;

0 commit comments

Comments
 (0)