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_error trio.
- 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 only err tells them apart. With CUresult 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_params already does this with restore_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
Refs: #2758 (error-handling policy RFC), #2759 (implementation), review discussion on _cpp/DESIGN.md in #2759.
Summary
The C++ handle layer (
cuda_core/cuda/core/_cpp/resource_handles.*) uses two status conventions. Factories return the handle and stash theCUresultin thread-localerr, read back withget_last_error(). Functions that do not produce a handle (context_synchronize,context_get_device,graph_node_set_params, thegraph_*_attachmentfamily,deviceptr_alloc_raw, ...) return theCUresultdirectly 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
erris that factories can be called fromnogilcode without acquiring the GIL on the success path.HANDLE_RETURNis itselfnogiland only takes the GIL on a non-success status, so aCUresult-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
CUresultreturns would:get_last_error/peek_last_error/clear_last_errortrio.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.err, return an empty handle".graph_node_set_paramsalready does this withrestore_status).Proposed rule
Anything that can fail returns
CUresultand delivers its result through an out-parameter, mirroring the driver. Accessors that cannot fail (get_stream_context,get_event_device_id, the*_refconstructors, ...) 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 readerr, 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
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.mdin #2759.