Skip to content

Conversation

@hlinsen
Copy link
Contributor

@hlinsen hlinsen commented Nov 13, 2025

This PR fixes:

=================================================================
==1254203==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 12608 byte(s) in 394 object(s) allocated from:
    #0 0x7e3eb66b1cba in malloc ../../../../libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x7e3e94e7a10d in cudss_host_malloc(unsigned long, unsigned long) (/home/hugo/miniforge3/envs/cuopt_dev/lib/libcudss.so.0+0x7a10d) (BuildId: 32758b1c028e396ecb7d0a0feb68940b94db8738)

Direct leak of 6240 byte(s) in 195 object(s) allocated from:
    #0 0x7e3eb66b1cba in malloc ../../../../libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x7e3e99deb2f0  (/home/hugo/miniforge3/envs/cuopt_dev/lib/libcusparse.so.12+0xbeb2f0) (BuildId: 3ddaf480ddd8ee4af8596e8e2a712ea0a65dc692)

Indirect leak of 40976 byte(s) in 394 object(s) allocated from:
    #0 0x7e3eb66b1cba in malloc ../../../../libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x7e3e94e7a10d in cudss_host_malloc(unsigned long, unsigned long) (/home/hugo/miniforge3/envs/cuopt_dev/lib/libcudss.so.0+0x7a10d) (BuildId: 32758b1c028e396ecb7d0a0feb68940b94db8738)

SUMMARY: AddressSanitizer: 59824 byte(s) leaked in 983 allocation(s).

Summary by CodeRabbit

  • Refactor

    • Added a scoped RAII wrapper for GPU dense-vector handles and updated public APIs to use it instead of raw descriptors.
    • Moved resource cleanup into destructors and narrowed vector creation sites to simplify initialization and runtime flow.
    • Removed redundant per-solve recreation of certain GPU structures.
  • Bug Fixes

    • Made descriptor destruction conditional to avoid teardown errors and leaks.
    • Added a non-throwing GPU API error-reporting path to improve stability.

@hlinsen hlinsen requested a review from a team as a code owner November 13, 2025 06:11
@hlinsen hlinsen added the bug Something isn't working label Nov 13, 2025
@hlinsen hlinsen added the non-breaking Introduces a non-breaking change label Nov 13, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 13, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Replaces raw cuSPARSE dense-vector descriptors with a new RAII wrapper cuopt_cusparse_dnvector_t<i_t,f_t>, updates creation sites, function signatures and members to use it, adds destructors and nullptr initialization for SpMat/SpGEMM descriptors, adds non‑throwing cusparse macros, and removes redundant dense-matrix creation in sparse Cholesky.

Changes

Cohort / File(s) Summary
Vector wrapper
cpp/src/dual_simplex/cusparse_dnvector.hpp
Add cuopt_cusparse_dnvector_t<i_t,f_t>: RAII owner for cusparseDnVecDescr_t, non-copyable, movable, constructible from rmm::device_uvector<f_t>, destroys descriptor on teardown using a no-throw path.
View API & usage
cpp/src/dual_simplex/cusparse_view.hpp, cpp/src/dual_simplex/cusparse_view.cu
Add ~cusparse_view_t(); change create_vector to return the wrapper; update spmv/transpose_spmv signatures to accept const& wrapper and forward .vec_descr to cuSPARSE; move SpMat descriptor destruction into destructor; adjust temporary vector handling.
Algorithm integration
cpp/src/dual_simplex/barrier.cu
Replace cusparseDnVecDescr_t parameters and members with cuopt_cusparse_dnvector_t<i_t,f_t> (e.g., gpu_adat_multiply signature and many member fields); update call sites to capture wrapper results (use auto/const-ref) and use .vec_descr where raw descriptors required.
cusparse info cleanup
cpp/src/dual_simplex/cusparse_info.hpp
Initialize cusparseSpMatDescr_t and cusparseSpGEMMDescr_t members to nullptr; update destructor to conditionally destroy descriptors using NO_THROW guards.
Sparse Cholesky simplification
cpp/src/dual_simplex/sparse_cholesky.cuh
Remove redundant cudssMatrixCreateDn calls for temporary dense matrices in constructor and solve paths; the code no longer recreates those dense matrices per solve.
Non-throwing error macros
cpp/src/utilities/macros.cuh
Add CUOPT_SET_ERROR_MSG_NO_THROW and CUOPT_CUSPARSE_TRY_NO_THROW to capture cusparse error status and populate error messages without throwing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • Move semantics and ownership in cuopt_cusparse_dnvector_t (avoid use-after-move or double-destroy).
    • All updated call sites and signatures (gpu_adat_multiply, spmv, transpose_spmv) for correct const-ref usage and .vec_descr extraction.
    • cusparse_view_t and cusparse_info_t destructors and nullptr initialization to prevent double-free.
    • Removed cudssMatrixCreateDn locations in sparse_cholesky.cuh to ensure matrices are initialized on all code paths.

Poem

🐇 I wrapped the vecs in tiny coats of code,

No dangling tails along the CUDA road.
Destructors sweep the midnight heap,
Sparse solves wake without remake-sleep,
Hoppity-hop — the kernels purr and bode.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix memory leaks in barrier' directly matches the PR's main objective of fixing memory leaks in barrier-related code, as confirmed by the LeakSanitizer/AddressSanitizer reports and the changes across barrier.cu and related files.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/dual_simplex/barrier.cu (1)

2298-2306: Initialise cusparse_dy_ before using it.

Line 2302 calls transpose_spmv with data.cusparse_dy_, but that wrapper is never reinitialised after data.d_dy_ is resized. The descriptor stays nullptr, so the very first cusparse call dereferences an invalid handle. Please wrap data.d_dy_ before using it, e.g.:

+      data.cusparse_dy_ = data.cusparse_view_.create_vector(data.d_dy_);
       // r1 <- A'*dy - r1
       data.cusparse_view_.transpose_spmv(1.0, data.cusparse_dy_, -1.0, data.cusparse_r1_);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bba7ece and 0f1303f.

📒 Files selected for processing (5)
  • cpp/src/dual_simplex/barrier.cu (4 hunks)
  • cpp/src/dual_simplex/cusparse_info.hpp (1 hunks)
  • cpp/src/dual_simplex/cusparse_view.cu (5 hunks)
  • cpp/src/dual_simplex/cusparse_view.hpp (2 hunks)
  • cpp/src/dual_simplex/sparse_cholesky.cuh (0 hunks)
💤 Files with no reviewable changes (1)
  • cpp/src/dual_simplex/sparse_cholesky.cuh
🧰 Additional context used
🧬 Code graph analysis (2)
cpp/src/dual_simplex/cusparse_view.hpp (2)
cpp/src/dual_simplex/cusparse_view.cu (2)
  • cusparse_view_t (117-237)
  • cusparse_view_t (240-244)
cpp/src/dual_simplex/barrier.cu (13)
  • alpha (835-841)
  • alpha (835-838)
  • alpha (1198-1232)
  • alpha (1198-1207)
  • alpha (1235-1266)
  • alpha (1235-1239)
  • alpha (1269-1296)
  • alpha (1269-1272)
  • alpha (1518-1524)
  • alpha (1518-1521)
  • alpha (2195-2201)
  • alpha (2195-2198)
  • y (603-603)
cpp/src/dual_simplex/cusparse_view.cu (3)
cpp/src/dual_simplex/barrier.cu (13)
  • y (603-603)
  • alpha (835-841)
  • alpha (835-838)
  • alpha (1198-1232)
  • alpha (1198-1207)
  • alpha (1235-1266)
  • alpha (1235-1239)
  • alpha (1269-1296)
  • alpha (1269-1272)
  • alpha (1518-1524)
  • alpha (1518-1521)
  • alpha (2195-2201)
  • alpha (2195-2198)
cpp/src/dual_simplex/cusparse_view.hpp (7)
  • cusparse_view_t (28-28)
  • cusparse_view_t (29-29)
  • vec (31-31)
  • spmv (34-37)
  • alpha (38-41)
  • alpha (47-50)
  • transpose_spmv (43-46)
cpp/src/utilities/copy_helpers.hpp (22)
  • device_copy (237-243)
  • device_copy (237-238)
  • device_copy (254-260)
  • device_copy (254-256)
  • device_copy (271-277)
  • device_copy (271-272)
  • device_copy (286-303)
  • device_copy (286-286)
  • host_copy (118-125)
  • host_copy (118-118)
  • host_copy (136-151)
  • host_copy (136-136)
  • host_copy (162-165)
  • host_copy (162-162)
  • host_copy (175-181)
  • host_copy (175-175)
  • host_copy (192-198)
  • host_copy (192-192)
  • host_copy (209-212)
  • host_copy (209-209)
  • host_copy (223-226)
  • host_copy (223-223)
🪛 Clang (14.0.6)
cpp/src/dual_simplex/cusparse_view.hpp

[error] 9-9: 'dual_simplex/cusparse_dnvector.hpp' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: wheel-build-cuopt-server / 13.0.2, 3.13, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-server / 12.9.1, 3.13, amd64, rockylinux8
  • GitHub Check: conda-cpp-build / 12.9.1, 3.10, arm64, rockylinux8
  • GitHub Check: conda-cpp-build / 13.0.2, 3.10, amd64, rockylinux8
  • GitHub Check: conda-cpp-build / 13.0.2, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-sh-client / 13.0.2, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.13, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.12, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.11, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.12, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.13, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.11, amd64, rockylinux8

/* clang-format on */
#pragma once

#include <dual_simplex/cusparse_dnvector.hpp>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix missing header include.

Line 9 adds #include <dual_simplex/cusparse_dnvector.hpp>, but Clang reports that header is not found. This will break every build unless the file is added or the include path is corrected (for example, pointing at the actual header if it lives under a different name). Please add the missing header or fix the include path. Based on static analysis hints.

🧰 Tools
🪛 Clang (14.0.6)

[error] 9-9: 'dual_simplex/cusparse_dnvector.hpp' file not found

(clang-diagnostic-error)

🤖 Prompt for AI Agents
In cpp/src/dual_simplex/cusparse_view.hpp around line 9, the new include
#include <dual_simplex/cusparse_dnvector.hpp> cannot be found by Clang; either
add the missing header file at that path in the repo or change the include to
the correct existing header path/name (or correct the case) and update the build
include paths (e.g., CMakeLists.txt target_include_directories) so the compiler
can locate it; after making the change, run a full build to confirm the header
resolves.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f1303f and 2d76c48.

📒 Files selected for processing (1)
  • cpp/src/dual_simplex/cusparse_dnvector.hpp (1 hunks)
🧰 Additional context used
🪛 Clang (14.0.6)
cpp/src/dual_simplex/cusparse_dnvector.hpp

[error] 8-8: 'raft/sparse/detail/cusparse_wrappers.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.13, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.11, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.12, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.11, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-sh-client / 13.0.2, 3.10, amd64, rockylinux8
🔇 Additional comments (1)
cpp/src/dual_simplex/cusparse_dnvector.hpp (1)

31-35: Verify and fix const-correctness by using cuSPARSE's const-aware API.

The constructor accepts a const reference to rmm::device_uvector<f_t> but uses const_cast to remove the const qualifier. cuSPARSE provides a const-aware variant, cusparseCreateConstDnVec(), which accepts const void* and returns cusparseConstDnVecDescr_t for read-only use.

Check if the raft::sparse::detail::cusparsecreatednvec wrapper exposes this const variant. If available, use it instead of const_cast to properly honor the const contract of the parameter. If the wrapper doesn't support the const variant, change the constructor parameter to a non-const reference.


template <typename i_t, typename f_t>
struct cuoptdnvector_t {
cusparseDnVecDescr_t vec_descr{nullptr};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Public member breaks RAII encapsulation.

The vec_descr member is public, which allows external code to directly manipulate or destroy the descriptor, defeating the RAII pattern. This could lead to double-free, use-after-free, or leaked descriptors if external code interferes with the managed resource.

Apply this diff to make the member private and add a public accessor:

+private:
   cusparseDnVecDescr_t vec_descr{nullptr};

+public:
+  cusparseDnVecDescr_t get() const { return vec_descr; }
+
   cuoptdnvector_t() = default;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cusparseDnVecDescr_t vec_descr{nullptr};
private:
cusparseDnVecDescr_t vec_descr{nullptr};
public:
cusparseDnVecDescr_t get() const { return vec_descr; }
cuoptdnvector_t() = default;
🤖 Prompt for AI Agents
In cpp/src/dual_simplex/cusparse_dnvector.hpp at line 16, the public member
`cusparseDnVecDescr_t vec_descr{nullptr};` breaks RAII; change its visibility to
private, keep the variable name, and add a public accessor method (e.g.,
`cusparseDnVecDescr_t get_vec_descr() const noexcept`) to expose the handle
without allowing external mutation or destruction; ensure the class still
manages creation/destruction of the descriptor in its ctor/dtor and do not
expose non-const references or raw pointer setters that would allow external
code to take ownership.

Comment on lines 18 to 21
cuoptdnvector_t() = default;

cuoptdnvector_t(cuoptdnvector_t const& other) = delete;
cuoptdnvector_t& operator=(cuoptdnvector_t const& other) = delete;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add move constructor to complete move semantics.

The struct defines move assignment but lacks a move constructor. Without an explicit move constructor, the compiler generates a default one that copies vec_descr without nulling the source, which would cause a double-free when both objects are destroyed.

Apply this diff to add a move constructor:

   cuoptdnvector_t() = default;

+  cuoptdnvector_t(cuoptdnvector_t&& other) noexcept
+    : vec_descr(other.vec_descr)
+  {
+    other.vec_descr = nullptr;
+  }
+
   cuoptdnvector_t(cuoptdnvector_t const& other)            = delete;
   cuoptdnvector_t& operator=(cuoptdnvector_t const& other) = delete;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cuoptdnvector_t() = default;
cuoptdnvector_t(cuoptdnvector_t const& other) = delete;
cuoptdnvector_t& operator=(cuoptdnvector_t const& other) = delete;
cuoptdnvector_t() = default;
cuoptdnvector_t(cuoptdnvector_t&& other) noexcept
: vec_descr(other.vec_descr)
{
other.vec_descr = nullptr;
}
cuoptdnvector_t(cuoptdnvector_t const& other) = delete;
cuoptdnvector_t& operator=(cuoptdnvector_t const& other) = delete;
🤖 Prompt for AI Agents
In cpp/src/dual_simplex/cusparse_dnvector.hpp around lines 18 to 21, the struct
defines move assignment but lacks an explicit move constructor so the
compiler-generated one will copy vec_descr and not null out the source, risking
a double-free; add an explicit move constructor that steals the resource by
copying vec_descr from the source, sets the source.vec_descr to nullptr (or
equivalent empty state), and leaves other members moved/initialized
appropriately so ownership is transferred safely.

Comment on lines 23 to 29
cuoptdnvector_t& operator=(cuoptdnvector_t&& other) noexcept
{
if (vec_descr != nullptr) { RAFT_CUSPARSE_TRY(cusparseDestroyDnVec(vec_descr)); }
vec_descr = other.vec_descr;
other.vec_descr = nullptr;
return *this;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add self-assignment check to move assignment operator.

The move assignment operator doesn't guard against self-assignment. If this == &other, the existing descriptor is destroyed at line 25, then the null pointer is copied back, resulting in a leaked descriptor (the original value is lost before it can be transferred).

Apply this diff to add a self-assignment check:

   cuoptdnvector_t& operator=(cuoptdnvector_t&& other) noexcept
   {
+    if (this == &other) { return *this; }
     if (vec_descr != nullptr) { RAFT_CUSPARSE_TRY(cusparseDestroyDnVec(vec_descr)); }
     vec_descr       = other.vec_descr;
     other.vec_descr = nullptr;
     return *this;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cuoptdnvector_t& operator=(cuoptdnvector_t&& other) noexcept
{
if (vec_descr != nullptr) { RAFT_CUSPARSE_TRY(cusparseDestroyDnVec(vec_descr)); }
vec_descr = other.vec_descr;
other.vec_descr = nullptr;
return *this;
}
cuoptdnvector_t& operator=(cuoptdnvector_t&& other) noexcept
{
if (this == &other) { return *this; }
if (vec_descr != nullptr) { RAFT_CUSPARSE_TRY(cusparseDestroyDnVec(vec_descr)); }
vec_descr = other.vec_descr;
other.vec_descr = nullptr;
return *this;
}
🤖 Prompt for AI Agents
In cpp/src/dual_simplex/cusparse_dnvector.hpp around lines 23 to 29, the move
assignment operator destroys the current descriptor unconditionally which breaks
when assigning to itself; add a self-assignment guard (if (this != &other)) so
you only destroy and take ownership when the source is a different object, then
transfer other.vec_descr to vec_descr and null out other.vec_descr, and return
*this.

namespace cuopt::linear_programming::dual_simplex {

template <typename i_t, typename f_t>
struct cuoptdnvector_t {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
struct cuoptdnvector_t {
struct cuopt_cusparse_dnvector_t {

or
struct cusparse_dnvector_wrapper_t

~cusparse_view_t();

static cusparseDnVecDescr_t create_vector(const rmm::device_uvector<f_t>& vec);
cuoptdnvector_t<i_t, f_t> create_vector(const rmm::device_uvector<f_t>& vec);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a deep copy? you can rename it to something like copy_to_cusparse

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I deleted the copy constructor. I think it should use RVO or move constructor only

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
cpp/src/dual_simplex/cusparse_dnvector.hpp (3)

30-30: Public member still breaks RAII encapsulation.

The vec_descr member remains public, allowing external code to directly manipulate or destroy the descriptor, which defeats the RAII pattern and can cause double-free or use-after-free bugs.

This issue was flagged in a previous review but has not been addressed. Apply this diff to make the member private and add a public accessor:

+private:
   cusparseDnVecDescr_t vec_descr{nullptr};

+public:
+  cusparseDnVecDescr_t get() const { return vec_descr; }
+
   cuopt_cusparse_dnvector_t() = default;

Then update all usage sites (e.g., Line 288, 290, 331, 333 in cusparse_view.cu) to call .get() instead of accessing .vec_descr directly.


32-35: Add move constructor to prevent double-free.

The struct defines move assignment but lacks a move constructor. Without an explicit move constructor, the compiler generates a default one that copies vec_descr without nulling the source, causing a double-free when both objects are destroyed.

This issue was flagged in a previous review but has not been addressed. Apply this diff to add a move constructor:

   cuopt_cusparse_dnvector_t() = default;

+  cuopt_cusparse_dnvector_t(cuopt_cusparse_dnvector_t&& other) noexcept
+    : vec_descr(other.vec_descr)
+  {
+    other.vec_descr = nullptr;
+  }
+
   cuopt_cusparse_dnvector_t(cuopt_cusparse_dnvector_t const& other)            = delete;

37-43: Add self-assignment check to move assignment operator.

The move assignment operator doesn't guard against self-assignment. If this == &other, the descriptor at Line 39 is destroyed, then the null pointer is copied back, leaking the original descriptor.

This issue was flagged in a previous review but has not been addressed. Apply this diff to add a self-assignment check:

   cuopt_cusparse_dnvector_t& operator=(cuopt_cusparse_dnvector_t&& other) noexcept
   {
+    if (this == &other) { return *this; }
     if (vec_descr != nullptr) { RAFT_CUSPARSE_TRY(cusparseDestroyDnVec(vec_descr)); }
     vec_descr       = other.vec_descr;
     other.vec_descr = nullptr;
     return *this;
   }
🧹 Nitpick comments (1)
cpp/src/dual_simplex/cusparse_view.cu (1)

240-244: Consider using NO_THROW variant in destructor.

The destructor uses RAFT_CUSPARSE_TRY, which may throw exceptions. Destructors should generally be noexcept to avoid program termination during stack unwinding.

Consider using CUOPT_CUSPARSE_TRY_NO_THROW instead (as done in cusparse_info.hpp at lines 41-49):

 cusparse_view_t<i_t, f_t>::~cusparse_view_t()
 {
-  RAFT_CUSPARSE_TRY(cusparseDestroySpMat(A_));
-  RAFT_CUSPARSE_TRY(cusparseDestroySpMat(A_T_));
+  CUOPT_CUSPARSE_TRY_NO_THROW(cusparseDestroySpMat(A_));
+  CUOPT_CUSPARSE_TRY_NO_THROW(cusparseDestroySpMat(A_T_));
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd544e7 and 746339e.

📒 Files selected for processing (5)
  • cpp/src/dual_simplex/barrier.cu (4 hunks)
  • cpp/src/dual_simplex/cusparse_dnvector.hpp (1 hunks)
  • cpp/src/dual_simplex/cusparse_info.hpp (2 hunks)
  • cpp/src/dual_simplex/cusparse_view.cu (5 hunks)
  • cpp/src/dual_simplex/cusparse_view.hpp (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
cpp/src/dual_simplex/cusparse_view.cu (3)
cpp/src/dual_simplex/cusparse_view.hpp (7)
  • cusparse_view_t (28-28)
  • cusparse_view_t (29-29)
  • vec (31-31)
  • spmv (34-37)
  • alpha (38-41)
  • alpha (47-50)
  • transpose_spmv (43-46)
cpp/src/dual_simplex/cusparse_dnvector.hpp (4)
  • cuopt_cusparse_dnvector_t (32-32)
  • cuopt_cusparse_dnvector_t (34-34)
  • cuopt_cusparse_dnvector_t (45-49)
  • cuopt_cusparse_dnvector_t (51-54)
cpp/src/utilities/copy_helpers.hpp (8)
  • device_copy (237-243)
  • device_copy (237-238)
  • device_copy (254-260)
  • device_copy (254-256)
  • device_copy (271-277)
  • device_copy (271-272)
  • device_copy (286-303)
  • device_copy (286-286)
cpp/src/dual_simplex/cusparse_view.hpp (2)
cpp/src/dual_simplex/cusparse_view.cu (2)
  • cusparse_view_t (117-237)
  • cusparse_view_t (240-244)
cpp/src/dual_simplex/barrier.cu (13)
  • alpha (835-841)
  • alpha (835-838)
  • alpha (1198-1232)
  • alpha (1198-1207)
  • alpha (1235-1266)
  • alpha (1235-1239)
  • alpha (1269-1296)
  • alpha (1269-1272)
  • alpha (1518-1524)
  • alpha (1518-1521)
  • alpha (2195-2201)
  • alpha (2195-2198)
  • y (603-603)
cpp/src/dual_simplex/cusparse_dnvector.hpp (1)
cpp/src/dual_simplex/cusparse_view.hpp (1)
  • vec (31-31)
🪛 Clang (14.0.6)
cpp/src/dual_simplex/cusparse_info.hpp

[error] 10-10: 'dual_simplex/cusparse_dnvector.hpp' file not found

(clang-diagnostic-error)

cpp/src/dual_simplex/cusparse_view.hpp

[error] 9-9: 'dual_simplex/cusparse_dnvector.hpp' file not found

(clang-diagnostic-error)

cpp/src/dual_simplex/cusparse_dnvector.hpp

[error] 8-8: 'raft/sparse/detail/cusparse_wrappers.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: conda-cpp-build / 13.0.2, 3.10, arm64, rockylinux8
  • GitHub Check: conda-cpp-build / 12.9.1, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-sh-client / 13.0.2, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.12, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.11, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.13, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.12, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.13, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.11, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.10, amd64, rockylinux8
🔇 Additional comments (8)
cpp/src/dual_simplex/cusparse_info.hpp (2)

40-49: LGTM! Exception-safe destructor implementation.

The destructor now correctly uses CUOPT_CUSPARSE_TRY_NO_THROW for all cleanup operations and guards each with null checks, ensuring exception safety and preventing double-destroy.


52-55: LGTM! Defensive initialization.

Initializing all descriptor members to nullptr is good defensive practice and ensures well-defined state.

cpp/src/dual_simplex/cusparse_view.cu (2)

247-251: LGTM! Clean wrapper return.

The create_vector implementation correctly constructs and returns the wrapper by value, leveraging move semantics for efficient transfer of ownership.


260-266: LGTM! Consistent wrapper usage.

The host-side spmv overload correctly creates wrappers from device vectors and passes them to the descriptor-based overload.

cpp/src/dual_simplex/cusparse_view.hpp (1)

9-9: LGTM! Consistent API migration to wrappers.

The header correctly declares:

  • Destructor for cleanup (Line 29)
  • create_vector returning wrapper by value (Line 31) for move-enabled ownership transfer
  • spmv and transpose_spmv taking wrapper const references (Lines 38-41, 47-50) to avoid copies while maintaining const-correctness

Also applies to: 29-29, 31-31, 38-41, 47-50

cpp/src/dual_simplex/barrier.cu (3)

1389-1402: LGTM! Member descriptors migrated to RAII wrappers.

The member variables have been correctly migrated from raw cusparseDnVecDescr_t to cuopt_cusparse_dnvector_t<i_t, f_t> wrappers. This ensures automatic cleanup and addresses the memory leaks described in the PR objectives.


2282-2282: LGTM! Local wrapper with automatic cleanup.

Creating the wrapper with auto ensures proper RAII cleanup when the variable goes out of scope, preventing the descriptor leak.


3267-3273: LGTM! Member wrapper initialization.

The member wrappers are correctly initialized from device vectors in the constructor, establishing proper ownership for the lifetime of the iteration_data_t object.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
cpp/src/dual_simplex/cusparse_dnvector.hpp (3)

15-15: Public member still breaks RAII encapsulation.

The vec_descr member remains public, allowing external code to directly manipulate or destroy the descriptor. This defeats the RAII pattern and can lead to double-free, use-after-free, or leaked descriptors.

Apply this diff to make the member private with a public accessor:

+private:
   cusparseDnVecDescr_t vec_descr{nullptr};
+
+public:
+  cusparseDnVecDescr_t get() const noexcept { return vec_descr; }

17-17: Move constructor still missing.

The struct defines move assignment but lacks an explicit move constructor. The compiler-generated default move constructor will copy vec_descr without nulling the source, causing a double-free when both objects are destroyed.

Apply this diff to add a move constructor:

   cuopt_cusparse_dnvector_t() = default;

+  cuopt_cusparse_dnvector_t(cuopt_cusparse_dnvector_t&& other) noexcept
+    : vec_descr(other.vec_descr)
+  {
+    other.vec_descr = nullptr;
+  }
+
   cuopt_cusparse_dnvector_t(cuopt_cusparse_dnvector_t const& other)            = delete;

22-28: Self-assignment check still missing in move assignment.

The move assignment operator doesn't guard against self-assignment. If this == &other, the existing descriptor is destroyed at line 24, then the null pointer is copied back, resulting in a leaked descriptor.

Apply this diff to add a self-assignment check:

   cuopt_cusparse_dnvector_t& operator=(cuopt_cusparse_dnvector_t&& other) noexcept
   {
+    if (this == &other) { return *this; }
     if (vec_descr != nullptr) { RAFT_CUSPARSE_TRY(cusparseDestroyDnVec(vec_descr)); }
     vec_descr       = other.vec_descr;
     other.vec_descr = nullptr;
     return *this;
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 746339e and e8763c7.

📒 Files selected for processing (3)
  • cpp/src/dual_simplex/cusparse_dnvector.hpp (1 hunks)
  • cpp/src/dual_simplex/cusparse_info.hpp (1 hunks)
  • cpp/src/utilities/macros.cuh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpp/src/dual_simplex/cusparse_info.hpp
🧰 Additional context used
🧬 Code graph analysis (1)
cpp/src/dual_simplex/cusparse_dnvector.hpp (1)
cpp/src/dual_simplex/cusparse_view.hpp (1)
  • vec (31-31)
🪛 Clang (14.0.6)
cpp/src/dual_simplex/cusparse_dnvector.hpp

[error] 8-8: 'raft/sparse/detail/cusparse_wrappers.h' file not found

(clang-diagnostic-error)

🔇 Additional comments (3)
cpp/src/utilities/macros.cuh (1)

41-54: LGTM! Non-throwing error macro correctly implemented.

The CUOPT_CUSPARSE_TRY_NO_THROW macro provides exception-safe error handling for cusparse calls in destructors and other noexcept contexts. The implementation correctly checks status, formats error messages, and avoids throwing exceptions.

cpp/src/dual_simplex/cusparse_dnvector.hpp (2)

19-20: LGTM! Copy operations correctly deleted.

Deleting the copy constructor and copy assignment operator is appropriate for this RAII wrapper, ensuring the resource cannot be accidentally copied.


36-39: LGTM! Destructor now uses exception-safe cleanup.

The destructor correctly uses CUOPT_CUSPARSE_TRY_NO_THROW to ensure it doesn't throw exceptions during cleanup. This properly addresses the exception safety concern from previous reviews.

@anandhkb anandhkb added this to the 25.12 milestone Nov 16, 2025
@rgsl888prabhu rgsl888prabhu changed the base branch from main to release/25.12 November 17, 2025 21:34
@hlinsen hlinsen requested review from a team as code owners November 19, 2025 00:42
@hlinsen
Copy link
Contributor Author

hlinsen commented Nov 21, 2025

/merge

@rapids-bot rapids-bot bot merged commit 06ef927 into NVIDIA:release/25.12 Nov 21, 2025
177 of 180 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants