Skip to main content

hurray_ffi/
sync.rs

1//! Synchronization mode handoff cross-checks for the Hurray C ABI.
2//!
3//! Before consuming a buffer produced by another party, the consumer MUST
4//! call the appropriate handoff function to verify that the buffer's declared
5//! [`SyncMode`] matches the payload they are providing. This prevents silent
6//! race conditions caused by producer/consumer sync-mode disagreement.
7//!
8//! See `docs/spec/buffer-protocol.md § Synchronization Mode` and ADR-018.
9
10use std::ffi::c_void;
11
12use hurray_core::SyncMode;
13
14use crate::{
15    buffer::HurrayBuffer,
16    panic::{catch, null_check},
17    status::{HurrayStatus, HURRAY_ERR_SYNC_MODE_MISMATCH, HURRAY_OK},
18};
19
20// ── C-visible payload types ───────────────────────────────────────────────────
21
22/// Optional callback invoked to release a sync event handle.
23///
24/// The first argument is the event handle; the second is the
25/// `event_release_context` pointer. MUST NOT call back into the Hurray C ABI.
26pub type HurrayEventReleaseFn = Option<unsafe extern "C" fn(*mut c_void, *mut c_void)>;
27
28/// Payload for the `Event` sync mode handoff.
29///
30/// The consumer supplies this struct when calling
31/// [`hurray_buffer_handoff_event`] to provide the device event that the
32/// producer recorded and on which the consumer will wait.
33#[repr(C)]
34pub struct HurraySyncEventPayload {
35    /// Non-null device-event handle (e.g., `cudaEvent_t`).
36    pub sync_handle: *mut c_void,
37    /// Wire byte of the device on which `sync_handle` was recorded.
38    pub sync_handle_device_tag: u8,
39    /// Release callback for `sync_handle`; MUST be non-null.
40    pub event_release_fn: HurrayEventReleaseFn,
41    /// Opaque context forwarded to `event_release_fn`; MAY be null.
42    pub event_release_context: *mut c_void,
43}
44
45// SAFETY: Single-thread-at-a-time per the C ABI contract; the payload is only
46// passed as a const pointer and is not mutated across threads.
47unsafe impl Send for HurraySyncEventPayload {}
48
49/// Payload for the `ConsumerStream` sync mode handoff.
50///
51/// The consumer supplies this struct when calling
52/// [`hurray_buffer_handoff_consumer_stream`] to declare the stream on which
53/// they intend to use the buffer.
54#[repr(C)]
55pub struct HurraySyncConsumerStreamPayload {
56    /// Non-null consumer stream handle (e.g., `cudaStream_t`).
57    pub consumer_stream: *mut c_void,
58    /// Wire byte of the device that owns `consumer_stream`.
59    pub consumer_stream_device_tag: u8,
60}
61
62// SAFETY: Single-thread-at-a-time per the C ABI contract; same rationale as
63// HurraySyncEventPayload.
64unsafe impl Send for HurraySyncConsumerStreamPayload {}
65
66// ── Handoff functions ─────────────────────────────────────────────────────────
67
68/// Validates an `Event`-mode sync handoff from producer to consumer.
69///
70/// Checks that:
71/// - The buffer's declared sync mode is [`SyncMode::Event`].
72/// - `payload.sync_handle` is non-null.
73/// - `payload.event_release_fn` is non-null.
74/// - `payload.sync_handle_device_tag` matches the buffer's declared device tag.
75///
76/// Returns [`HURRAY_OK`] if all checks pass; [`HURRAY_ERR_SYNC_MODE_MISMATCH`]
77/// otherwise.
78///
79/// # Safety
80///
81/// `buffer` and `payload` MUST be valid, non-null pointers pointing to live
82/// objects.
83#[no_mangle]
84pub unsafe extern "C" fn hurray_buffer_handoff_event(
85    buffer: *const HurrayBuffer,
86    payload: *const HurraySyncEventPayload,
87) -> HurrayStatus {
88    catch(|| {
89        null_check!(buffer, payload);
90
91        // SAFETY: buffer and payload are non-null and point to live objects.
92        let sync_mode = (*buffer).handle.sync_mode();
93        if sync_mode != SyncMode::Event {
94            return HURRAY_ERR_SYNC_MODE_MISMATCH;
95        }
96
97        if (*payload).sync_handle.is_null() {
98            return HURRAY_ERR_SYNC_MODE_MISMATCH;
99        }
100
101        if (*payload).event_release_fn.is_none() {
102            return HURRAY_ERR_SYNC_MODE_MISMATCH;
103        }
104
105        // Device tag on the event must match the buffer's declared device.
106        let buffer_device_byte = (*buffer).handle.device_tag().to_byte();
107        if (*payload).sync_handle_device_tag != buffer_device_byte {
108            return HURRAY_ERR_SYNC_MODE_MISMATCH;
109        }
110
111        HURRAY_OK
112    })
113}
114
115/// Validates a `ConsumerStream`-mode sync handoff from producer to consumer.
116///
117/// Checks that:
118/// - The buffer's declared sync mode is [`SyncMode::ConsumerStream`].
119/// - `payload.consumer_stream` is non-null.
120/// - `payload.consumer_stream_device_tag` matches the buffer's declared device
121///   tag.
122///
123/// Returns [`HURRAY_OK`] if all checks pass; [`HURRAY_ERR_SYNC_MODE_MISMATCH`]
124/// otherwise.
125///
126/// # Safety
127///
128/// `buffer` and `payload` MUST be valid, non-null pointers pointing to live
129/// objects.
130#[no_mangle]
131pub unsafe extern "C" fn hurray_buffer_handoff_consumer_stream(
132    buffer: *const HurrayBuffer,
133    payload: *const HurraySyncConsumerStreamPayload,
134) -> HurrayStatus {
135    catch(|| {
136        null_check!(buffer, payload);
137
138        // SAFETY: buffer and payload are non-null and point to live objects.
139        let sync_mode = (*buffer).handle.sync_mode();
140        if sync_mode != SyncMode::ConsumerStream {
141            return HURRAY_ERR_SYNC_MODE_MISMATCH;
142        }
143
144        if (*payload).consumer_stream.is_null() {
145            return HURRAY_ERR_SYNC_MODE_MISMATCH;
146        }
147
148        // Device tag on the consumer stream must match the buffer's declared device.
149        let buffer_device_byte = (*buffer).handle.device_tag().to_byte();
150        if (*payload).consumer_stream_device_tag != buffer_device_byte {
151            return HURRAY_ERR_SYNC_MODE_MISMATCH;
152        }
153
154        HURRAY_OK
155    })
156}
157
158/// Validates a `ProducerSynced`-mode sync handoff.
159///
160/// Checks that the buffer's declared sync mode is
161/// [`SyncMode::ProducerSynced`]. This mode requires no payload — calling
162/// this function is itself the assertion that the producer has already issued
163/// a host-side wait.
164///
165/// Returns [`HURRAY_OK`] if the buffer is `ProducerSynced`;
166/// [`HURRAY_ERR_SYNC_MODE_MISMATCH`] otherwise.
167///
168/// # Safety
169///
170/// `buffer` MUST be a valid, non-null pointer to a live [`HurrayBuffer`].
171#[no_mangle]
172pub unsafe extern "C" fn hurray_buffer_handoff_producer_synced(
173    buffer: *const HurrayBuffer,
174) -> HurrayStatus {
175    catch(|| {
176        null_check!(buffer);
177
178        // SAFETY: buffer is non-null and points to a live HurrayBuffer.
179        if (*buffer).handle.sync_mode() != SyncMode::ProducerSynced {
180            return HURRAY_ERR_SYNC_MODE_MISMATCH;
181        }
182
183        HURRAY_OK
184    })
185}