gpu-handle-types 0.2.0

Typed, owned native GPU resource handles (Vulkan, D3D11/12, Metal, OpenGL, CUDA, OpenCL, DMA-BUF, IOSurface, AHardwareBuffer, WebGPU, ...), cross-API sync points and video pixel formats, for passing GPU resources between libraries.
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// `SyncPoint::CudaEvent` shape + waiter-drop discipline.
// A real CUDA-driver-backed waiter lives in a producer crate; this test
// only exercises the leaf-crate contract:
//
//   1. The variant constructs with the declared field set
//      (`event`, `context`, `device`, `waiter`).
//   2. The generic `SyncPoint::wait_blocking` / `is_signaled` /
//      `backend` accessors dispatch through the inner `SyncWaiter`
//      via the unified `waiter()` accessor.
//   3. The `CudaEventWaiter` supertrait's drop discipline contract:
//      `Drop` runs exactly once when the last `Arc` clone is
//      released. (The push-context-before-destroy ordering is an
//      implementation detail of a real CUDA-driver-backed waiter;
//      this test only asserts that the Drop hook is reachable.)
//   4. `wait_on_foreign_stream` is dispatch-able on the trait
//      object — a smoke test that the trait method signature is
//      object-safe.

use std::any::Any;
use std::ffi::c_void;
use std::ptr;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::Duration;

use gpu_handle_types::{BackendKind, CudaEventWaiter, Error, SyncPoint, SyncWaiter};

/// Stub CUDA-event waiter — counts Drop calls into a shared
/// `AtomicBool` so the test can observe the discipline contract.
struct StubCudaEventWaiter {
    dropped: Arc<AtomicBool>,
    foreign_stream_calls: Arc<AtomicUsize>,
    last_foreign_stream: parking_lot::Mutex<*mut c_void>,
}

// SAFETY: the only `*mut c_void` this stub caches is the value last passed to
// `wait_on_foreign_stream`; it is never dereferenced by this stub.
unsafe impl Send for StubCudaEventWaiter {}
unsafe impl Sync for StubCudaEventWaiter {}

impl Drop for StubCudaEventWaiter {
    fn drop(&mut self) {
        self.dropped.store(true, Ordering::SeqCst);
    }
}

impl SyncWaiter for StubCudaEventWaiter {
    fn wait(&self, _timeout: Duration) -> Result<(), Error> {
        Ok(())
    }

    fn is_signaled(&self) -> Result<bool, Error> {
        Ok(true)
    }

    fn backend(&self) -> BackendKind {
        // BackendKind::Cuda is gated behind feature = "cuda"; the
        // variant itself is not. Cpu is the conservative fallback for
        // shape tests that do not opt into the cuda feature.
        BackendKind::Cpu
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

impl CudaEventWaiter for StubCudaEventWaiter {
    fn wait_on_foreign_stream(&self, foreign_stream: *mut c_void) -> Result<(), Error> {
        self.foreign_stream_calls.fetch_add(1, Ordering::SeqCst);
        *self.last_foreign_stream.lock() = foreign_stream;
        Ok(())
    }
}

fn make_sync_point(waiter_inner: Arc<StubCudaEventWaiter>) -> (SyncPoint, *mut c_void, *mut c_void) {
    // Fake event / context pointers — non-null sentinels distinct
    // from each other so the variant fields are observably set.
    let event: *mut c_void = 0x1000 as *mut c_void;
    let context: *mut c_void = 0x2000 as *mut c_void;
    // Upcast the concrete-type Arc to the generic `Arc<dyn SyncWaiter>`
    // the variant stores. Cross-API bridges downcast back to
    // `&dyn CudaEventWaiter` via `SyncWaiter::as_any`.
    let waiter: Arc<dyn SyncWaiter> = waiter_inner;
    let sp = SyncPoint::CudaEvent { event, context, device: 0, waiter };
    (sp, event, context)
}

#[test]
fn cuda_event_variant_fields_round_trip() {
    let stub = Arc::new(StubCudaEventWaiter {
        dropped: Arc::new(AtomicBool::new(false)),
        foreign_stream_calls: Arc::new(AtomicUsize::new(0)),
        last_foreign_stream: parking_lot::Mutex::new(ptr::null_mut()),
    });
    let (sp, expect_event, expect_context) = make_sync_point(stub);

    match &sp {
        SyncPoint::CudaEvent { event, context, device, waiter: _ } => {
            assert_eq!(*event, expect_event);
            assert_eq!(*context, expect_context);
            assert_eq!(*device, 0);
        }
        _ => panic!("expected CudaEvent variant"),
    }
}

#[test]
fn cuda_event_generic_dispatch_routes_to_inner_waiter() {
    let stub = Arc::new(StubCudaEventWaiter {
        dropped: Arc::new(AtomicBool::new(false)),
        foreign_stream_calls: Arc::new(AtomicUsize::new(0)),
        last_foreign_stream: parking_lot::Mutex::new(ptr::null_mut()),
    });
    let (sp, _, _) = make_sync_point(stub);

    // Generic accessors must work via the unified `.waiter()` path.
    assert_eq!(sp.backend(), BackendKind::Cpu);
    assert!(sp.is_signaled().expect("stub is_signaled returns Ok"));
    sp.wait_blocking().expect("stub wait returns Ok");
}

#[test]
fn cuda_event_drop_discipline_fires_on_last_clone() {
    let dropped = Arc::new(AtomicBool::new(false));
    let stub = Arc::new(StubCudaEventWaiter {
        dropped: dropped.clone(),
        foreign_stream_calls: Arc::new(AtomicUsize::new(0)),
        last_foreign_stream: parking_lot::Mutex::new(ptr::null_mut()),
    });
    let (sp, _, _) = make_sync_point(stub);

    // While a SyncPoint clone is alive, the waiter is not dropped.
    let clone = sp.clone();
    assert!(!dropped.load(Ordering::SeqCst));
    drop(sp);
    assert!(!dropped.load(Ordering::SeqCst));
    drop(clone);
    assert!(dropped.load(Ordering::SeqCst), "CudaEventWaiter Drop must fire when the last SyncPoint clone is released");
}

#[test]
fn cuda_event_waiter_trait_object_dispatch_foreign_stream() {
    // Reach the `wait_on_foreign_stream` extension method via the
    // documented `SyncWaiter::as_any` downcast — the route a cross-API
    // bridge can use. Confirms `CudaEventWaiter: SyncWaiter`
    // upcasting works through `Arc<dyn SyncWaiter>`.
    let foreign_calls = Arc::new(AtomicUsize::new(0));
    let stub = Arc::new(StubCudaEventWaiter {
        dropped: Arc::new(AtomicBool::new(false)),
        foreign_stream_calls: foreign_calls.clone(),
        last_foreign_stream: parking_lot::Mutex::new(ptr::null_mut()),
    });
    let (sp, _, _) = make_sync_point(stub);

    let waiter = sp.waiter().expect("CudaEvent carries a waiter");
    let concrete =
        waiter.as_any().downcast_ref::<StubCudaEventWaiter>().expect("downcast to concrete CudaEventWaiter impl");
    concrete.wait_on_foreign_stream(0xdead_beef_usize as *mut c_void).expect("stub forwards Ok");

    assert_eq!(foreign_calls.load(Ordering::SeqCst), 1);
    assert_eq!(*concrete.last_foreign_stream.lock(), 0xdead_beef_usize as *mut c_void);
}