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}