Skip to content
Merged
36 changes: 27 additions & 9 deletions cpp/src/branch_and_bound/mip_node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <utilities/omp_helpers.hpp>

#include <cmath>
#include <cstdio>
#include <list>
#include <memory>
#include <vector>
Expand Down Expand Up @@ -44,18 +45,35 @@ class mip_node_t {
{
// Iterative teardown to avoid stack overflow on deep trees.
// Detach all descendants breadth-first, then destroy them as leaves.
std::vector<std::unique_ptr<mip_node_t>> nodes;
for (auto& c : children) {
if (c) { nodes.push_back(std::move(c)); }
}
// nodes.size() grows so that this loop only terminates when only leaves remain
for (size_t i = 0; i < nodes.size(); ++i) {
for (auto& c : nodes[i]->children) {
// vector::push_back can throw bad_alloc; the catch-all keeps the destructor
// exception-free. Under OOM, any not-yet-detached descendants are destroyed
// via the recursive unique_ptr chain in `children` as this frame unwinds.
try {
std::vector<std::unique_ptr<mip_node_t>> nodes;
for (auto& c : children) {
if (c) { nodes.push_back(std::move(c)); }
}
}
// nodes.size() grows so that this loop only terminates when only leaves remain
for (size_t i = 0; i < nodes.size(); ++i) {
for (auto& c : nodes[i]->children) {
if (c) { nodes.push_back(std::move(c)); }
}
}

// scope-exit ensure destruction of all detached leaves
// scope-exit ensure destruction of all detached leaves
} catch (const std::exception& e) {
// fprintf to stderr is allocation-free and cannot throw; using the
// project logger here would risk a secondary bad_alloc that would
// escape the destructor and re-introduce std::terminate.
std::fprintf(stderr,
"mip_node_t destructor: iterative teardown failed (%s); falling back to "
"recursive unique_ptr destruction.\n",
e.what());
} catch (...) {
std::fprintf(stderr,
"mip_node_t destructor: iterative teardown failed (unknown exception); "
"falling back to recursive unique_ptr destruction.\n");
}
}

mip_node_t(mip_node_t&&) noexcept = default;
Expand Down
65 changes: 64 additions & 1 deletion cpp/src/grpc/client/grpc_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <future>
#include <iomanip>
Expand Down Expand Up @@ -127,7 +128,69 @@ grpc_client_t::grpc_client_t(const std::string& server_address) : impl_(std::mak
chunked_array_threshold_bytes_ = config_.max_message_bytes * 3 / 4;
}

grpc_client_t::~grpc_client_t() { stop_log_streaming(); }
grpc_client_t::~grpc_client_t()
{
// The destructor must not propagate exceptions AND must not leave a joinable
// std::thread alive — a joinable thread's destructor calls std::terminate.
// We inline a noexcept variant of stop_log_streaming() here so that on any
// failure we still detach the thread before its destructor runs.
//
// fprintf to stderr is used instead of CUOPT_LOG_ERROR because the project
// logger can allocate, and a secondary bad_alloc raised from inside one of
// these catch handlers would escape the destructor and re-introduce
// std::terminate — defeating the purpose of catching at all.
stop_logs_.store(true);
try {
std::lock_guard<std::mutex> lk(log_context_mutex_);
if (active_log_context_) {
static_cast<grpc::ClientContext*>(active_log_context_)->TryCancel();
}
} catch (const std::exception& e) {
std::fprintf(stderr,
"grpc_client_t destructor: TryCancel/lock failed (%s); proceeding to "
"join/detach.\n",
e.what());
} catch (...) {
std::fprintf(stderr,
"grpc_client_t destructor: TryCancel/lock failed (unknown exception); "
"proceeding to join/detach.\n");
}
std::unique_ptr<std::thread> t;
std::swap(t, log_thread_);
if (t && t->joinable()) {
try {
t->join();
} catch (const std::exception& e) {
std::fprintf(
stderr, "grpc_client_t destructor: log-thread join failed (%s); detaching.\n", e.what());
// join failed (e.g., std::system_error). Detach so the local
// unique_ptr's destructor doesn't terminate on the joinable thread.
try {
t->detach();
} catch (const std::exception& e2) {
std::fprintf(stderr,
"grpc_client_t destructor: detach also failed (%s); thread may "
"terminate the process on unique_ptr destruction.\n",
e2.what());
} catch (...) {
std::fprintf(stderr,
"grpc_client_t destructor: detach also failed (unknown exception); "
"thread may terminate the process on unique_ptr destruction.\n");
}
} catch (...) {
std::fprintf(stderr,
"grpc_client_t destructor: log-thread join failed (unknown exception); "
"detaching.\n");
try {
t->detach();
} catch (...) {
std::fprintf(stderr,
"grpc_client_t destructor: detach also failed; thread may "
"terminate the process on unique_ptr destruction.\n");
}
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

bool grpc_client_t::connect()
{
Expand Down
21 changes: 17 additions & 4 deletions cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cu
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <iomanip>
#include <mutex>
#include <random>
Expand Down Expand Up @@ -230,10 +231,22 @@ class timing_raii_t {

~timing_raii_t()
{
auto end_time = std::chrono::high_resolution_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time_);
times_vec_.push_back(duration.count());
// vector::push_back can throw bad_alloc; the catch-all keeps the destructor
// exception-free. Losing one timing sample under OOM is acceptable.
// fprintf to stderr is allocation-free and cannot throw; using the project
// logger here would risk a secondary bad_alloc that would escape the
// destructor and re-introduce std::terminate.
try {
auto end_time = std::chrono::high_resolution_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time_);
times_vec_.push_back(duration.count());
} catch (const std::exception& e) {
std::fprintf(stderr, "timing_raii_t destructor: failed to record sample (%s).\n", e.what());
} catch (...) {
std::fprintf(stderr,
"timing_raii_t destructor: failed to record sample (unknown exception).\n");
}
}

private:
Expand Down
Loading