Coverage for cuda/core/_utils/_weak_handles.pyx: 75.00%

20 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-19 01:12 +0000

1# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 1a

2# SPDX-License-Identifier: Apache-2.0 

3  

4"""Test-only weak handles for resource-handle lifetime checks. 

5  

6This module is **not** part of the public ``cuda.core`` API. It is built into 

7the package (like other private ``_utils`` modules) purely so the test suite can 

8observe, deterministically, when the strong references that keep a CUDA resource 

9alive have all been released -- without relying on driver- or hardware-specific 

10side effects (for example, whether freed device memory happens to remain 

11readable). 

12  

13Every resource handle is owned by a C++ ``std::shared_ptr``. A **weak handle** 

14is a non-owning ``std::weak_ptr`` observer of that control block: truthy while 

15some strong owner remains, falsy once the last one is gone. Use :func:`weak_handle` 

16to obtain a weak handle from a supported front-end object. 

17  

18To support another type, add a ``cdef _weak_from_<type>`` that reads its ``cdef`` 

19handle field (see ``*.pxd``), assigns to :ctype:`OpaqueHandle`, and extend the 

20``isinstance`` chain in :func:`weak_handle`. Types whose slots hold arbitrary 

21Python owners via ``make_opaque_py`` are not covered here -- use 

22:class:`weakref.ref` on a weak-referenceable owner object in tests instead. 

23""" 

24  

25from cuda.core._memory._buffer cimport Buffer 

26from cuda.core._resource_handles cimport OpaqueHandle 

27  

28  

29# Cython cannot spell ``weak_ptr[const void]`` inline (the ``const void`` 

30# template argument fails to parse), so the weak type and its one constructor 

31# are provided by a small inline C++ shim local to this test-only module. This 

32# keeps the production resource_handles translation units untouched. 

33cdef extern from *: 

34 """ 

35 #include <memory> 

36 namespace cuda_core_test { 

37 using OpaqueWeakHandle = std::weak_ptr<const void>; 

38 static inline OpaqueWeakHandle make_weak(const std::shared_ptr<const void>& h) { 

39 return OpaqueWeakHandle(h); 

40 } 

41 } // namespace cuda_core_test 

42 """ 

43 cppclass OpaqueWeakHandle "cuda_core_test::OpaqueWeakHandle": 

44 OpaqueWeakHandle() 

45 bint expired() 

46 long use_count() 

47 OpaqueWeakHandle make_weak "cuda_core_test::make_weak" (const OpaqueHandle& h) 

48  

49  

50cdef class WeakHandle: 

51 """Non-owning weak handle for a resource's shared control block. 

52  

53 Truthy while some strong owner of the underlying resource handle remains, 

54 falsy once the last strong reference is released. Obtain instances via 

55 :func:`weak_handle` rather than constructing directly. 

56 """ 

57  

58 cdef OpaqueWeakHandle _w 

59  

60 def __bool__(self): 

61 return not self._w.expired() 1a

62  

63 def expired(self): 1a

64 """Return ``True`` once every strong owner of the handle is gone.""" 

65 return self._w.expired() 

66  

67 def use_count(self): 1a

68 """Number of strong owners currently sharing the handle.""" 

69 return self._w.use_count() 

70  

71  

72cdef WeakHandle _weak_from_opaque(OpaqueHandle h): 1a

73 # Build the weak handle from a (temporary) strong handle. The strong copy 

74 # lives only for the duration of this call, so it does not perturb the 

75 # reference count the weak handle later reports. 

76 cdef WeakHandle wh = WeakHandle.__new__(WeakHandle) 1a

77 wh._w = make_weak(h) 1a

78 return wh 1a

79  

80  

81cdef WeakHandle _weak_from_buffer(Buffer buf): 1a

82 cdef OpaqueHandle h = buf._h_ptr 1a

83 if not h: 1a

84 raise ValueError("Buffer has no active allocation") 

85 return _weak_from_opaque(h) 1a

86  

87  

88def weak_handle(obj): 1a

89 """Return a :class:`WeakHandle` observing the resource behind ``obj``. 

90  

91 Currently supports :class:`~cuda.core.Buffer` (device allocation handle). 

92 See the module docstring for how to add more types. 

93  

94 Raises 

95 ------ 

96 ValueError 

97 If ``obj`` is a :class:`~cuda.core.Buffer` with no active allocation. 

98 TypeError 

99 If ``obj`` is not a supported type. 

100 """ 

101 if isinstance(obj, Buffer): 1a

102 return _weak_from_buffer(obj) 1a

103 raise TypeError( 

104 f"weak_handle() does not support {type(obj).__name__!r}; " 

105 "supported types: Buffer" 

106 )