1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// SPDX-License-Identifier: MIT OR Apache-2.0
use super::CudaBufferPoolInner;
use crate::{
error::{select_resource_release_error, CudaError},
execution::completion::select_uncertain_completion_error,
};
use std::sync::Arc;
#[derive(Debug)]
#[doc(hidden)]
pub struct CudaBufferPoolReuseGuard {
pub(super) pool: Arc<CudaBufferPoolInner>,
pub(super) active: bool,
}
impl CudaBufferPoolReuseGuard {
/// Release this hold after completion has been established.
pub fn release(mut self) -> Result<(), CudaError> {
if let Err(error) = self.release_inner() {
if self.active {
// A poisoned pool cannot safely transition deferred
// allocations back to reusable state. Retain the pool and its
// active hold.
std::mem::forget(self);
}
return Err(error);
}
Ok(())
}
/// Synchronize the owning pool context.
pub fn synchronize_pool_context(&self) -> crate::execution::CudaSynchronizationOutcome {
self.pool.context.synchronize_for_resource_release()
}
/// Synchronize the owning context and release the hold.
pub fn synchronize_and_release(self) -> Result<(), CudaError> {
let outcome = self.synchronize_pool_context();
if !outcome.completion_established() {
let result = outcome.into_result();
self.abandon();
return result;
}
self.release()
}
/// Establish completion, release the hold, and preserve a primary error.
pub fn synchronize_then_error<T>(self, error: CudaError) -> Result<T, CudaError> {
if self.pool.context.inner.resource_lifetimes_poisoned() {
self.abandon();
return Err(select_uncertain_completion_error(error, None));
}
if let Err(completion_error) = self.synchronize_pool_context().into_result() {
self.abandon();
return Err(select_uncertain_completion_error(
error,
Some(completion_error),
));
}
match self.release() {
Ok(()) => Err(error),
Err(release_error) => Err(select_resource_release_error(error, release_error)),
}
}
/// Release after a recoverable operation already established completion.
pub fn release_after_recoverable_operation_error<T>(
self,
primary_error: CudaError,
) -> Result<T, CudaError> {
// A failed recoverable driver operation already attempted context-wide
// completion while holding the lifecycle gate. Do not synchronize a
// second time after another thread could submit later work.
if self.pool.context.inner.resource_lifetimes_poisoned() {
self.abandon();
return Err(primary_error);
}
match self.release() {
Ok(()) => Err(primary_error),
Err(release_error) => Err(select_resource_release_error(primary_error, release_error)),
}
}
/// Permanently retain the hold when completion cannot be established.
pub fn abandon(self) {
// Completion could not be established. Leaking the guard keeps the
// pool and its reuse hold alive, so deferred allocations cannot be
// recycled or freed while CUDA might still reference them.
std::mem::forget(self);
}
fn release_inner(&mut self) -> Result<(), CudaError> {
if !self.active {
return Ok(());
}
match self.pool.release_reuse_hold() {
Ok(()) => {
self.active = false;
Ok(())
}
Err(error @ CudaError::HostAllocationFailed { .. }) => {
// The hold reaches zero before deferred buffers enter the
// completed-work cache. Cache-allocation failure may drop
// those buffers safely, so this guard must not leak the pool.
self.active = false;
Err(error)
}
Err(error) => Err(error),
}
}
}
impl Drop for CudaBufferPoolReuseGuard {
fn drop(&mut self) {
if self.active {
// Last-resort protection for abandoned queued work or unwinding.
// Normal Result paths establish actual completion, then call
// `release` so failures can be surfaced. If completion cannot be
// established here, leave the hold active rather than make
// possibly referenced allocations reusable.
let outcome = self.pool.context.synchronize_for_resource_release();
if outcome.completion_established() {
if self.release_inner().is_err() && self.active {
std::mem::forget(self.pool.clone());
}
} else {
std::mem::forget(self.pool.clone());
}
}
}
}