Skip to content

Comments

Fix #1641: Handle cleanup when exceptions are thrown#1669

Open
mdboom wants to merge 2 commits intoNVIDIA:mainfrom
mdboom:handle-cleanup
Open

Fix #1641: Handle cleanup when exceptions are thrown#1669
mdboom wants to merge 2 commits intoNVIDIA:mainfrom
mdboom:handle-cleanup

Conversation

@mdboom
Copy link
Contributor

@mdboom mdboom commented Feb 20, 2026

In driver, runtime and nvrtc, the generated code follows the following pattern:

def func(args):
    $init
    $precall
    $call
    $postcall
    $return

For certain argument types, $precall and $postcall are required to be pairs, such as malloc/free. If an exception is thrown when parsing one of the other arguments in $precall, $postcall will not get called, leaking memory or other resources.

This reorganizes the code (whenever $postcall is non-empty) to:

def func(args):
    $init
    try:
        $precall
        $call
    finally:
        $postcall
    $return

This diff is larger than it otherwise might need to be since Cython only allows cdef declarations at the top level. So all the cdef's that used to be part of $precall need to be moved to $init.

Performance impact

try/finally is implemented by Cython with gotos. It generates two copies of the finally clause: one for success and one for failure, so there is a cost in overall code size. (This is similar to CPython's bytecode implementation of try/finally). However, the runtime performance penalty is below the noise threshold in the #659 benchmark. There is a small performance penalty of around 200ns for the error case, but that's to be expected -- the old implementation leaked memory.

Alternatives considered

We could use C++ RAII to automatically free memory. In fact, some of our code in these files already does that in the use of std::vector. However, this is not generally applicable as a solution, since it's not possible to implement a custom RAII struct Cython that is not also a PyObject. This would make the HelperVoidPtrStruct optimization impossible.

We could require that $init does all validation (but not any resource allocation), and the $precall would never raise, so $postcall would always be guaranteed to run. It seems like this may have been part of the original design, but then has not been strictly enforced everywhere over time. Even if we could get to that, it has two problems. (1) Some exceptions in $precall are unavoidable, even if the inputs are valid, such as malloc failing. (2) Doing a separate validation and conversion pass on all the arguments is never going to be as performant as a single pass.

@copy-pr-bot
Copy link
Contributor

copy-pr-bot bot commented Feb 20, 2026

Auto-sync is disabled for ready for review pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@mdboom
Copy link
Contributor Author

mdboom commented Feb 20, 2026

/ok to test

@github-actions
Copy link

@mdboom
Copy link
Contributor Author

mdboom commented Feb 20, 2026

/ok to test

Comment on lines -1038 to -1045
cdef _HelperInputVoidPtrStruct cycallbackHelper
cdef void* cycallback = _helper_input_void_ptr(callback, &cycallbackHelper)
cdef _HelperInputVoidPtrStruct cypayloadHelper
cdef void* cypayload = _helper_input_void_ptr(payload, &cypayloadHelper)
with nogil:
err = cynvrtc.nvrtcSetFlowCallback(cyprog, cycallback, cypayload)
_helper_input_void_ptr_free(&cycallbackHelper)
_helper_input_void_ptr_free(&cypayloadHelper)
Copy link
Member

Choose a reason for hiding this comment

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

Q: Maybe we should encapsulate _helper_input_void_ptr and _helper_input_void_ptr_free as a C++ class (not Cython cdef class, which was the previous approach IIRC)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: API function calls do not cleanup correctly when a Python exception is thrown

2 participants