@@ -58,6 +58,9 @@ const USED_ARROW_SCHEMA_CAPSULE_NAME: &CStr = c_str!("used_arrow_schema");
5858const ARROW_DEVICE_ARRAY_CAPSULE_NAME : & CStr = c_str ! ( "arrow_device_array" ) ;
5959const USED_ARROW_DEVICE_ARRAY_CAPSULE_NAME : & CStr = c_str ! ( "used_arrow_device_array" ) ;
6060
61+ // Owns a buffer export after we steal it from the source PyCapsule. `Bytes::from_owner`
62+ // keeps this guard alive for every cloned byte buffer and drops it to run the producer's
63+ // release callback once the reconstructed Vortex buffer is no longer referenced.
6164struct BufferExportGuard {
6265 export : NonNull < VortexBufferExport > ,
6366}
@@ -89,8 +92,9 @@ impl Drop for BufferExportGuard {
8992 }
9093}
9194
92- // The guard is moved into `Bytes::from_owner`, which requires `Send + Sync`. After import we disable
93- // the source capsule destructor and own the C export until this guard is dropped.
95+ // The guard is moved into `Bytes::from_owner`, which requires `Send + Sync`.
96+ // After import we disable the source capsule destructor and own the C export
97+ // until this guard is dropped.
9498unsafe impl Send for BufferExportGuard { }
9599unsafe impl Sync for BufferExportGuard { }
96100
@@ -461,6 +465,84 @@ fn release_exported(exported: &mut ArrowDeviceArrayWithSchema) {
461465 release_device_array ( & mut exported. array ) ;
462466}
463467
468+ /// Return non-owning details from Arrow Device capsules for Python-side smoke consumers.
469+ #[ pyfunction]
470+ fn _debug_arrow_device_array_capsule_summary < ' py > (
471+ py : Python < ' py > ,
472+ schema : Bound < ' py , PyCapsule > ,
473+ device_array : Bound < ' py , PyCapsule > ,
474+ ) -> PyResult < Bound < ' py , PyDict > > {
475+ let schema = unsafe {
476+ schema
477+ . pointer_checked ( Some ( ARROW_SCHEMA_CAPSULE_NAME ) ) ?
478+ . cast :: < FFI_ArrowSchema > ( )
479+ . as_ref ( )
480+ } ;
481+ let device_array = unsafe {
482+ device_array
483+ . pointer_checked ( Some ( ARROW_DEVICE_ARRAY_CAPSULE_NAME ) ) ?
484+ . cast :: < ArrowDeviceArray > ( )
485+ . as_ref ( )
486+ } ;
487+
488+ let summary = PyDict :: new ( py) ;
489+ summary. set_item ( "schema_live" , schema. release . is_some ( ) ) ?;
490+ summary. set_item ( "array_live" , device_array. array . release . is_some ( ) ) ?;
491+ summary. set_item ( "is_cuda" , device_array. device_type == ARROW_DEVICE_CUDA ) ?;
492+ summary. set_item ( "device_type" , device_array. device_type ) ?;
493+ summary. set_item ( "device_id" , device_array. device_id ) ?;
494+ summary. set_item ( "length" , device_array. array . length ) ?;
495+ summary. set_item ( "null_count" , device_array. array . null_count ) ?;
496+ summary. set_item ( "n_buffers" , device_array. array . n_buffers ) ?;
497+ summary. set_item ( "n_children" , device_array. array . n_children ) ?;
498+ Ok ( summary)
499+ }
500+
501+ /// Simulate a Python Arrow Device consumer taking ownership from the returned capsules.
502+ #[ pyfunction]
503+ fn _debug_consume_arrow_device_array_capsules (
504+ schema : Bound < ' _ , PyCapsule > ,
505+ device_array : Bound < ' _ , PyCapsule > ,
506+ ) -> PyResult < ( bool , bool , bool , bool , bool , bool ) > {
507+ let mut schema_ptr = schema
508+ . pointer_checked ( Some ( ARROW_SCHEMA_CAPSULE_NAME ) ) ?
509+ . cast :: < FFI_ArrowSchema > ( ) ;
510+ let mut device_array_ptr = device_array
511+ . pointer_checked ( Some ( ARROW_DEVICE_ARRAY_CAPSULE_NAME ) ) ?
512+ . cast :: < ArrowDeviceArray > ( ) ;
513+
514+ let schema_ref = unsafe { schema_ptr. as_mut ( ) } ;
515+ let device_array_ref = unsafe { device_array_ptr. as_mut ( ) } ;
516+ let schema_had_release = schema_ref. release . is_some ( ) ;
517+ let array_had_release = device_array_ref. array . release . is_some ( ) ;
518+
519+ release_schema ( schema_ref) ;
520+ release_device_array ( device_array_ref) ;
521+
522+ let schema_release_cleared = schema_ref. release . is_none ( ) ;
523+ let array_release_cleared = device_array_ref. array . release . is_none ( ) ;
524+
525+ set_capsule_name ( & schema, USED_ARROW_SCHEMA_CAPSULE_NAME ) ?;
526+ set_capsule_name ( & device_array, USED_ARROW_DEVICE_ARRAY_CAPSULE_NAME ) ?;
527+
528+ Ok ( (
529+ schema_had_release,
530+ array_had_release,
531+ schema_release_cleared,
532+ array_release_cleared,
533+ schema. is_valid_checked ( Some ( USED_ARROW_SCHEMA_CAPSULE_NAME ) ) ,
534+ device_array. is_valid_checked ( Some ( USED_ARROW_DEVICE_ARRAY_CAPSULE_NAME ) ) ,
535+ ) )
536+ }
537+
538+ fn set_capsule_name ( capsule : & Bound < ' _ , PyCapsule > , name : & CStr ) -> PyResult < ( ) > {
539+ let result = unsafe { ffi:: PyCapsule_SetName ( capsule. as_ptr ( ) , name. as_ptr ( ) ) } ;
540+ if result != 0 {
541+ return Err ( PyErr :: fetch ( capsule. py ( ) ) ) ;
542+ }
543+ Ok ( ( ) )
544+ }
545+
464546fn schema_capsule < ' py > (
465547 py : Python < ' py > ,
466548 schema : FFI_ArrowSchema ,
@@ -573,6 +655,14 @@ fn _lib(m: &Bound<PyModule>) -> PyResult<()> {
573655 m. add_function ( wrap_pyfunction ! ( cuda_available, m) ?) ?;
574656 m. add_function ( wrap_pyfunction ! ( _debug_array_metadata_dtype, m) ?) ?;
575657 m. add_function ( wrap_pyfunction ! ( _debug_array_metadata_display_values, m) ?) ?;
658+ m. add_function ( wrap_pyfunction ! (
659+ _debug_arrow_device_array_capsule_summary,
660+ m
661+ ) ?) ?;
662+ m. add_function ( wrap_pyfunction ! (
663+ _debug_consume_arrow_device_array_capsules,
664+ m
665+ ) ?) ?;
576666 m. add_function ( wrap_pyfunction ! ( export_device_array, m) ?) ?;
577667 Ok ( ( ) )
578668}
0 commit comments