Repository navigation
cuda.core: return CUresult from C++ handle factories instead of thread-local error state #2760
Description
Summary
The C++ handle layer (cuda_core/cuda/core/_cpp/resource_handles.*) uses two status conventions. Factories return the handle and stash the CUresult in thread-local err, read back with get_last_error(). Functions that do not produce a handle (context_synchronize, context_get_device, graph_node_set_params, the graph_*_attachment family, deviceptr_alloc_raw, ...) return the CUresult directly with results in out-parameters, like the driver API. Proposal: use the second convention everywhere and remove the thread-local error state.
Why
The documented justification for thread-local err is that factories can be called from nogil code without acquiring the GIL on the success path. HANDLE_RETURN is itself nogil and only takes the GIL on a non-success status, so a CUresult-returning factory has the same property. What is left is expression-style ergonomics (h = create_stream_handle(...)), which would only pay off with a real exception channel, and this layer cannot have one.
Unifying on direct CUresult returns would:
- Remove the read-before-clobber footgun and the
get_last_error/peek_last_error/clear_last_errortrio. - Remove an ambiguity that exists today: an empty handle can mean a legitimate "none" (
get_current_context()with no current context,get_context_green_ctx()on a plain context) or a failure, and onlyerrtells them apart. WithCUresult f(Handle* out, ...), success plus an empty out-handle is unambiguous. - Make internal composition explicit propagation instead of "the callee already set
err, return an empty handle". - Give secondary statuses a natural home as out-parameters (
graph_node_set_paramsalready does this withrestore_status).
Proposed rule
Anything that can fail returns CUresult and delivers its result through an out-parameter, mirroring the driver. Accessors that cannot fail (get_stream_context, get_event_device_id, the *_ref constructors, ...) keep returning values directly. One rule, no exceptions keyed on return type.
Cython call sites become:
cdef StreamHandle h
HANDLE_RETURN(create_stream_handle(&h, h_ctx, flags, priority))Scope
Rough counts on the current tree: 66 handle-returning factory signatures, 37 internal err = assignments in the C++ layer, and 21 Cython sites in 13 modules that read err, plus every Cython factory call. Mechanical and behavior-neutral, but it touches every module, so it should land as a standalone change with no functional edits mixed in, after #2750 and #2759 merge. It is independent of the planned follow-up on calling raw driver function pointers instead of the Cython wrappers.
Non-goals
- No behavior change; every status that is raised or reported today continues to be raised or reported.
- The thread-local
last_error_detailbuffer added by cuda.core: define the error handling policy and report failures that cannot be raised #2759 is ancillary message text consumed only at the raise point, not control-flow state. It can stay as is; folding it into a status struct is a possible later step.
Refs: #2758 (error-handling policy RFC), #2759 (implementation), review discussion on _cpp/DESIGN.md in #2759.
Activity
Andy-Jost commentedon Sep 14, 2026
Scope change: remove the thread-local detail buffer as well.
The issue text above says the
last_error_detail/take_last_error_detail()buffer can stay. Review of #2759 showed it has the same weakness as thread-localerr: a caller that drops the status leaves the detail behind, and the nextCUDAErrorraised on that thread with the same status code picks it up. Brandon's analysis and reproducer:Event._initgets an empty handle and raises a genericRuntimeErrorwithout consuming the status, so the recorded detail is never attached.create_event()is reported nowhere, the thread is left on the wrong context, and the "could not be restored" note later attaches to an unrelatedcuDeviceGetAttributeerror.Eventsite is the result.Plan for this issue:
err,get_last_error(),peek_last_error(),clear_last_error(), and the detail buffer (last_error_detail,take_last_error_detail(),clear_last_error_detail()).CUresult. Context-scoped helpers return the restoration status through a second out-parameter, asgraph_node_set_paramsalready does, and the Cython layer attaches the restoration note itself. A dropped status can then lose information, but it can never attach to a different error.Eventsites get fixed by the conversion, since they have to handle the returned status. Brandon's reproducer becomes the regression test.context_restore_faultinjection stays thread-local; it is not error state.#2759 ships with the thread-local detail in place and a note in
DESIGN.mdpointing here.cuda.core: name the resource in cleanup reports, export CUDAError, no…
cuda.core docs: which reporting channel to use, and what pw_ really does
cuda.core: define the error handling policy and report failures that …