1use super::{
2 invalid_resource, validate_runtime_descriptor_for_admission, Arc, AtomicBool, BufferDescriptor,
3 BufferRequest, DeviceCapacityClaim, DeviceId, DeviceRuntime, Ordering, PhantomData, RefCell,
4 ResourceAbandonSignal, ResourceDriverFailure, ResourceId, ResourcePoolIdentity,
5 ResourceReservation, ResourceReservationBatch, ResourceTransactionAction,
6 ResourceTransactionIdentity, ResourceTransactionState, StaticProvisioningBinding, VNextError,
7};
8
9#[derive(Debug, Clone, Copy)]
10pub(super) struct ResourceActionCursor {
11 pub(super) order: usize,
12 pub(super) action: ResourceTransactionAction,
13 pub(super) before: ResourceTransactionState,
14 pub(super) allocation_authorized: bool,
15}
16
17pub struct ResourceTransactionContext<'a, R>
18where
19 R: DeviceRuntime,
20{
21 pub(super) runtime: &'a Arc<R>,
22 pub(super) identity: &'a ResourceTransactionIdentity,
23 pub(super) binding: &'a StaticProvisioningBinding,
24 pub(super) reservations: &'a ResourceReservationBatch,
25 pub(super) cursor: Option<ResourceActionCursor>,
26 pub(super) allocation_authority: Option<&'a AtomicBool>,
27 pub(super) pending_allocation: Option<&'a RefCell<Option<CoreOwnedAllocation<R::Buffer>>>>,
28}
29
30impl<'a, R> ResourceTransactionContext<'a, R>
31where
32 R: DeviceRuntime,
33{
34 pub fn identity(&self) -> &ResourceTransactionIdentity {
35 self.identity
36 }
37
38 pub fn admission(&self) -> &StaticProvisioningBinding {
39 self.binding
40 }
41
42 pub fn reservations(&self) -> &ResourceReservationBatch {
43 self.reservations
44 }
45
46 fn allocation_permit<'permit>(
47 &'permit self,
48 request: &'permit BufferRequest,
49 ) -> Result<DeviceAllocationPermit<'permit>, VNextError> {
50 let cursor = self
51 .cursor
52 .filter(|cursor| {
53 cursor.action == ResourceTransactionAction::Commit
54 && cursor.before == ResourceTransactionState::Reserved
55 && cursor.allocation_authorized
56 })
57 .ok_or_else(|| {
58 invalid_resource("device allocation is authorized only during an exact commit")
59 })?;
60 let reservation = &self.reservations.reservations[cursor.order];
61 if request.resource_id() != reservation.resource_id()
62 || request.size_bytes() != reservation.size_bytes()
63 || request.alignment_bytes() != reservation.alignment_bytes()
64 || request.usage() != reservation.usage()
65 || request.element_type() != reservation.element_type()
66 {
67 return Err(invalid_resource(
68 "buffer request differs from the active admitted allocation",
69 ));
70 }
71 self.allocation_authority
72 .ok_or_else(|| invalid_resource("commit allocation authority is unavailable"))?
73 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
74 .map_err(|_| {
75 invalid_resource(
76 "the active resource action already consumed its allocation permit",
77 )
78 })?;
79 Ok(DeviceAllocationPermit {
80 identity: self.identity,
81 binding: self.binding,
82 reservation,
83 request,
84 seal: AllocationSeal,
85 })
86 }
87
88 pub fn allocate<'commit>(
93 &'commit self,
94 request: &BufferRequest,
95 ) -> Result<DeviceAllocationReceipt<'commit>, DeviceAllocationError<R::Error>> {
96 validate_runtime_descriptor_for_admission(
97 self.runtime.descriptor(),
98 self.binding,
99 "allocation preflight",
100 )
101 .map_err(DeviceAllocationError::Contract)?;
102 let permit = self
103 .allocation_permit(request)
104 .map_err(DeviceAllocationError::Contract)?;
105 let resource_id = permit.resource_id().clone();
106 let generation = permit.generation();
107 let allocation = self
108 .runtime
109 .allocate(permit)
110 .map_err(DeviceAllocationError::Runtime)?;
111 let reservation = &self.reservations.reservations[self
112 .cursor
113 .expect("allocation permit requires an action cursor")
114 .order];
115 let pending = self.pending_allocation.ok_or_else(|| {
116 DeviceAllocationError::Contract(invalid_resource(
117 "core pending allocation storage is unavailable",
118 ))
119 })?;
120 if pending.borrow().is_some() {
121 return Err(DeviceAllocationError::Contract(invalid_resource(
122 "core pending allocation storage is already occupied",
123 )));
124 }
125 let expected_descriptor = BufferDescriptor {
126 resource_id: reservation.resource_id.clone(),
127 size_bytes: reservation.size_bytes,
128 alignment_bytes: reservation.alignment_bytes,
129 usage: reservation.usage,
130 element_type: reservation.element_type,
131 };
132 pending.replace(Some(CoreOwnedAllocation {
133 resource_id: resource_id.clone(),
134 generation,
135 descriptor: expected_descriptor,
136 buffer: allocation,
137 }));
138 let descriptor = {
139 let pending = pending.borrow();
140 self.runtime.buffer_descriptor(
141 &pending
142 .as_ref()
143 .expect("allocation was installed before descriptor inspection")
144 .buffer,
145 )
146 };
147 pending
148 .borrow_mut()
149 .as_mut()
150 .expect("allocation remains core-owned during descriptor inspection")
151 .descriptor = descriptor.clone();
152 validate_runtime_descriptor_for_admission(
153 self.runtime.descriptor(),
154 self.binding,
155 "allocation completion",
156 )
157 .map_err(DeviceAllocationError::Contract)?;
158 Ok(DeviceAllocationReceipt {
159 resource_id,
160 generation,
161 descriptor,
162 scope: PhantomData,
163 })
164 }
165}
166
167pub(super) struct AllocationSeal;
168
169#[must_use = "a device allocation permit must be consumed by DeviceRuntime::allocate"]
170pub struct DeviceAllocationPermit<'a> {
171 pub(super) identity: &'a ResourceTransactionIdentity,
172 pub(super) binding: &'a StaticProvisioningBinding,
173 pub(super) reservation: &'a ResourceReservation,
174 pub(super) request: &'a BufferRequest,
175 pub(super) seal: AllocationSeal,
176}
177
178impl<'a> DeviceAllocationPermit<'a> {
179 pub fn identity(&self) -> &ResourceTransactionIdentity {
180 self.identity
181 }
182
183 pub fn admission(&self) -> &StaticProvisioningBinding {
184 self.binding
185 }
186
187 pub fn reservation(&self) -> &ResourceReservation {
188 self.reservation
189 }
190
191 pub fn request(&self) -> &BufferRequest {
192 self.request
193 }
194
195 pub fn resource_id(&self) -> &ResourceId {
196 self.reservation.resource_id()
197 }
198
199 pub const fn generation(&self) -> u64 {
200 self.reservation.generation()
201 }
202
203 pub fn into_request(self) -> &'a BufferRequest {
204 let _ = self.seal;
205 self.request
206 }
207}
208
209#[derive(Debug)]
210pub enum DeviceAllocationError<E> {
211 Contract(VNextError),
212 Runtime(E),
213}
214
215impl<E> DeviceAllocationError<E> {
216 pub fn contract_error(&self) -> Option<&VNextError> {
217 match self {
218 Self::Contract(error) => Some(error),
219 Self::Runtime(_) => None,
220 }
221 }
222
223 pub fn runtime_error(&self) -> Option<&E> {
224 match self {
225 Self::Contract(_) => None,
226 Self::Runtime(error) => Some(error),
227 }
228 }
229}
230
231#[must_use = "an allocation receipt must be returned by the active commit call"]
232pub struct DeviceAllocationReceipt<'commit> {
233 resource_id: ResourceId,
234 generation: u64,
235 descriptor: BufferDescriptor,
236 scope: PhantomData<&'commit mut ()>,
237}
238
239impl DeviceAllocationReceipt<'_> {
240 pub fn resource_id(&self) -> &ResourceId {
241 &self.resource_id
242 }
243
244 pub const fn generation(&self) -> u64 {
245 self.generation
246 }
247
248 pub fn descriptor(&self) -> &BufferDescriptor {
249 &self.descriptor
250 }
251}
252
253pub(super) struct DriverCommitAcknowledgement {
254 resource_id: ResourceId,
255 generation: u64,
256 descriptor: BufferDescriptor,
257}
258
259impl DriverCommitAcknowledgement {
260 pub(super) fn from_receipt(receipt: &DeviceAllocationReceipt<'_>) -> Self {
261 Self {
262 resource_id: receipt.resource_id.clone(),
263 generation: receipt.generation,
264 descriptor: receipt.descriptor.clone(),
265 }
266 }
267
268 pub(super) fn matches<B>(&self, allocation: &CoreOwnedAllocation<B>) -> bool {
269 self.resource_id == allocation.resource_id
270 && self.generation == allocation.generation
271 && self.descriptor == allocation.descriptor
272 }
273}
274
275pub(super) struct CoreOwnedAllocation<B> {
276 pub(super) resource_id: ResourceId,
277 pub(super) generation: u64,
278 pub(super) descriptor: BufferDescriptor,
279 pub(super) buffer: B,
280}
281
282impl<B> CoreOwnedAllocation<B> {
283 pub(super) fn matches(&self, reservation: &ResourceReservation) -> bool {
284 self.resource_id == reservation.resource_id
285 && self.generation == reservation.generation
286 && reservation.matches_descriptor(&self.descriptor)
287 }
288}
289
290pub struct ResourceCommitView<'a, B> {
293 pub(super) resource_id: &'a ResourceId,
294 pub(super) generation: u64,
295 pub(super) descriptor: &'a BufferDescriptor,
296 pub(super) buffer: &'a B,
297}
298
299impl<'a, B> ResourceCommitView<'a, B> {
300 pub fn resource_id(&self) -> &ResourceId {
301 self.resource_id
302 }
303
304 pub const fn generation(&self) -> u64 {
305 self.generation
306 }
307
308 pub fn descriptor(&self) -> &BufferDescriptor {
309 self.descriptor
310 }
311
312 pub fn buffer(&self) -> &B {
313 self.buffer
314 }
315}
316
317#[must_use = "owned quarantine buffers must be retained until backend cleanup"]
318pub struct ResourceOwnedBuffer<B> {
319 pub(super) order: usize,
320 pub(super) expected_resource_id: ResourceId,
321 pub(super) actual_resource_id: ResourceId,
322 pub(super) expected_generation: u64,
323 pub(super) actual_generation: u64,
324 pub(super) expected_descriptor: BufferDescriptor,
325 pub(super) actual_descriptor: BufferDescriptor,
326 pub(super) buffer: B,
327}
328
329impl<B> ResourceOwnedBuffer<B> {
330 pub fn resource_id(&self) -> &ResourceId {
331 &self.expected_resource_id
332 }
333
334 pub const fn generation(&self) -> u64 {
335 self.expected_generation
336 }
337
338 pub fn actual_resource_id(&self) -> &ResourceId {
339 &self.actual_resource_id
340 }
341
342 pub const fn actual_generation(&self) -> u64 {
343 self.actual_generation
344 }
345
346 pub fn expected_descriptor(&self) -> &BufferDescriptor {
347 &self.expected_descriptor
348 }
349
350 pub fn actual_descriptor(&self) -> &BufferDescriptor {
351 &self.actual_descriptor
352 }
353
354 pub fn buffer(&self) -> &B {
355 &self.buffer
356 }
357
358 pub(super) fn into_allocation(self) -> (usize, CoreOwnedAllocation<B>) {
359 (
360 self.order,
361 CoreOwnedAllocation {
362 resource_id: self.actual_resource_id,
363 generation: self.actual_generation,
364 descriptor: self.actual_descriptor,
365 buffer: self.buffer,
366 },
367 )
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub enum ResourceOwnershipReason {
373 Quarantine,
374 Abandon,
375}
376
377#[must_use = "resource ownership must remain durable until cleanup completes"]
381pub struct ResourcePoolOwnership<R>
382where
383 R: DeviceRuntime,
384{
385 pub(super) buffers: Vec<ResourceOwnedBuffer<R::Buffer>>,
389 pub(super) capacity_claim: Option<DeviceCapacityClaim>,
390 pub(super) pool_identity: ResourcePoolIdentity,
391 pub(super) reason: ResourceOwnershipReason,
392 pub(super) signal: Option<ResourceAbandonSignal>,
393 pub(super) runtime: Arc<R>,
394}
395
396impl<R> ResourcePoolOwnership<R>
397where
398 R: DeviceRuntime,
399{
400 pub fn runtime(&self) -> &R {
401 &self.runtime
402 }
403
404 pub fn pool_identity(&self) -> &ResourcePoolIdentity {
405 &self.pool_identity
406 }
407
408 pub const fn reason(&self) -> ResourceOwnershipReason {
409 self.reason
410 }
411
412 pub fn abandon_signal(&self) -> Option<&ResourceAbandonSignal> {
413 self.signal.as_ref()
414 }
415
416 pub fn buffers(&self) -> &[ResourceOwnedBuffer<R::Buffer>] {
417 &self.buffers
418 }
419
420 pub fn claimed_bytes(&self) -> u64 {
421 self.capacity_claim
422 .as_ref()
423 .map_or(0, DeviceCapacityClaim::bytes)
424 }
425
426 fn must_retain_on_drop(&self) -> bool {
427 std::thread::panicking()
428 }
429}
430
431impl<R> Drop for ResourcePoolOwnership<R>
432where
433 R: DeviceRuntime,
434{
435 fn drop(&mut self) {
436 if !self.must_retain_on_drop() {
437 return;
438 }
439
440 for buffer in std::mem::take(&mut self.buffers) {
445 std::mem::forget(buffer);
446 }
447 if let Some(claim) = self.capacity_claim.take() {
448 std::mem::forget(claim);
449 }
450 std::mem::forget(Arc::clone(&self.runtime));
455 }
456}
457
458#[must_use = "a failed ownership transfer must be returned to core"]
459pub struct ResourceOwnershipTransferFailure<R>
460where
461 R: DeviceRuntime,
462{
463 failure: ResourceDriverFailure,
464 ownership: ResourcePoolOwnership<R>,
465}
466
467impl<R> ResourceOwnershipTransferFailure<R>
468where
469 R: DeviceRuntime,
470{
471 pub fn new(failure: ResourceDriverFailure, ownership: ResourcePoolOwnership<R>) -> Self {
472 Self { failure, ownership }
473 }
474
475 pub fn failure(&self) -> &ResourceDriverFailure {
476 &self.failure
477 }
478
479 pub fn ownership(&self) -> &ResourcePoolOwnership<R> {
480 &self.ownership
481 }
482
483 pub(super) fn into_parts(self) -> (ResourceDriverFailure, ResourcePoolOwnership<R>) {
484 (self.failure, self.ownership)
485 }
486}
487
488pub trait ResourceTransactionDriver: Send {
492 type Buffer: Send + Sync + 'static;
493 type Runtime: DeviceRuntime<Buffer = Self::Buffer>;
494
495 fn runtime(&self) -> &Arc<Self::Runtime>;
496
497 fn device_id(&self) -> &DeviceId;
498
499 fn device_runtime_implementation_fingerprint(&self) -> &str;
500
501 fn device_capacity_bytes(&self) -> u64;
502
503 fn reserve_resource(
504 &mut self,
505 context: &ResourceTransactionContext<'_, Self::Runtime>,
506 reservation: &ResourceReservation,
507 ) -> Result<(), ResourceDriverFailure>;
508
509 fn commit_resource<'commit>(
510 &mut self,
511 context: &'commit ResourceTransactionContext<'_, Self::Runtime>,
512 reservation: &ResourceReservation,
513 ) -> Result<DeviceAllocationReceipt<'commit>, ResourceDriverFailure>;
514
515 fn compensate_reserve_resource(
516 &mut self,
517 context: &ResourceTransactionContext<'_, Self::Runtime>,
518 reservation: &ResourceReservation,
519 ) -> Result<(), ResourceDriverFailure>;
520
521 fn compensate_commit_resource(
522 &mut self,
523 context: &ResourceTransactionContext<'_, Self::Runtime>,
524 reservation: &ResourceReservation,
525 buffer: &Self::Buffer,
526 ) -> Result<(), ResourceDriverFailure>;
527
528 fn rollback_resource(
529 &mut self,
530 context: &ResourceTransactionContext<'_, Self::Runtime>,
531 reservation: &ResourceReservation,
532 ) -> Result<(), ResourceDriverFailure>;
533
534 fn release_resource(
535 &mut self,
536 context: &ResourceTransactionContext<'_, Self::Runtime>,
537 reservation: &ResourceReservation,
538 buffer: &Self::Buffer,
539 ) -> Result<(), ResourceDriverFailure>;
540
541 fn reconcile_commit_outcome(
542 &mut self,
543 context: &ResourceTransactionContext<'_, Self::Runtime>,
544 expected: &ResourceReservation,
545 actual: ResourceCommitView<'_, Self::Buffer>,
546 ) -> Result<(), ResourceDriverFailure>;
547
548 fn quarantine_transaction(
549 &mut self,
550 context: &ResourceTransactionContext<'_, Self::Runtime>,
551 ownership: ResourcePoolOwnership<Self::Runtime>,
552 ) -> Result<(), ResourceOwnershipTransferFailure<Self::Runtime>>;
553
554 fn abandon_transaction(&mut self, ownership: ResourcePoolOwnership<Self::Runtime>);
555}