vulkan_layer/lazy_collection.rs
1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16 borrow::Cow,
17 cell::RefCell,
18 collections::BTreeMap,
19 marker::PhantomData,
20 ops::{Deref, DerefMut},
21};
22
23use once_cell::unsync::OnceCell;
24
25/// The [CheckEmpty] trait allows to check if a collection is empty.
26pub trait CheckEmpty: Default + Clone {
27 /// Returns `true` if the collection contains no elements.
28 fn is_empty(&self) -> bool;
29}
30
31impl<T: Clone> CheckEmpty for Vec<T> {
32 fn is_empty(&self) -> bool {
33 Vec::<T>::is_empty(self)
34 }
35}
36
37impl<K: Clone, V: Clone> CheckEmpty for BTreeMap<K, V> {
38 fn is_empty(&self) -> bool {
39 BTreeMap::<K, V>::is_empty(self)
40 }
41}
42
43/// A collection wrapper, that guarantees that an empty collection can be trivially destructed.
44///
45/// This makes it easy to use collections in a global object that requires trivially destructible.
46/// When using global objects in a dynamic link library that allow a process to load and unload the
47/// dynamic link library multiple times, no OS provides reliable way to teardown the global
48/// resources allocated, so all global resources used should be trivially destructible.
49///
50/// This is especially true for an Android Vulkan layer. When querying the capabilities of Vulkan
51/// layers, the Android Vulkan loader will load the shared object and call into the exposed
52/// introspection queries, and unload the shared object once the task is done. The Android Vulkan
53/// loader may load the shared object later again if the layer is activated. However, on Android
54/// there is no reliable way to register a callback when the shared object is actually unloaded.
55///
56/// Similar to [RefCell], even if `T` implements [Sync], [`LazyCollection<T>`] is not [Sync],
57/// because a single-threaded way is used to test if the underlying collection is empty and destroy
58/// the allocation
59#[derive(Default)]
60pub struct LazyCollection<T: CheckEmpty> {
61 inner: OnceCell<T>,
62 // Mark this type !Sync even if T implements Sync.
63 marker: PhantomData<RefCell<T>>,
64}
65
66impl<T: CheckEmpty> LazyCollection<T> {
67 /// Creates a new `LazyCollection` containing `value`.
68 ///
69 /// # Examples
70 /// ```
71 /// # use vulkan_layer::unstable_api::LazyCollection;
72 /// let c = LazyCollection::new(vec![42]);
73 /// ```
74 #[allow(dead_code)]
75 // Only test uses this function.
76 pub fn new(value: T) -> Self {
77 Self {
78 inner: OnceCell::with_value(value),
79 ..Default::default()
80 }
81 }
82
83 /// Gets the reference to the underlying collection. Returns an owned empty T if the underlying
84 /// collection is empty.
85 ///
86 /// # Examples
87 /// ```
88 /// # use vulkan_layer::unstable_api::LazyCollection;
89 /// let vec = LazyCollection::new(vec![42]);
90 /// let vec1 = vec.get();
91 /// assert_eq!(*vec1, vec![42]);
92 /// let vec2 = vec.get();
93 /// // vec1 and vec2 point to the same location.
94 /// assert!(std::ptr::eq(&*vec1, &*vec2));
95 /// ```
96 pub fn get(&self) -> Cow<T> {
97 // The destructor for None is a no-op, while this is not guaranteed for an empty T.
98 // Therefore, we can't use &T as the return type and return a reference to a static empty T
99 // when the underlying collection is empty.
100 match self.inner.get() {
101 Some(collection) => Cow::Borrowed(collection),
102 None => Cow::Owned(Default::default()),
103 }
104 }
105
106 /// Gets a mutable reference to the underlying collection, create an empty collection if the
107 /// underlying collection was empty.
108 ///
109 /// # Examples
110 /// ```
111 /// # use vulkan_layer::unstable_api::LazyCollection;
112 /// let mut vec = LazyCollection::<Vec<u32>>::default();
113 /// let mut mut_vec = vec.get_mut_or_default();
114 /// mut_vec.push(42);
115 /// assert_eq!(*mut_vec, vec![42]);
116 /// drop(mut_vec);
117 ///
118 /// let mut mut_vec = vec.get_mut_or_default();
119 /// mut_vec.remove(0);
120 /// assert_eq!(*mut_vec, vec![]);
121 /// drop(mut_vec);
122 /// // This won't cause a memory leak.
123 /// std::mem::forget(vec);
124 /// ```
125 pub fn get_mut_or_default(&mut self) -> CollectionRefMut<'_, T> {
126 // Ensure that inner is initialized.
127 self.inner.get_or_init(Default::default);
128 CollectionRefMut(&mut self.inner)
129 }
130}
131
132/// A wrapper type for a mutably borrowed value from a [LazyCollection].
133#[derive(Debug)]
134pub struct CollectionRefMut<'a, T: CheckEmpty>(&'a mut OnceCell<T>);
135
136impl<T: CheckEmpty> Drop for CollectionRefMut<'_, T> {
137 fn drop(&mut self) {
138 let should_destroy = self
139 .0
140 .get()
141 .map(|collection| collection.is_empty())
142 .unwrap_or(true);
143 if !should_destroy {
144 return;
145 }
146 self.0.take();
147 }
148}
149
150impl<T: CheckEmpty> Deref for CollectionRefMut<'_, T> {
151 type Target = T;
152 fn deref(&self) -> &Self::Target {
153 // CollectionRefMut will always be initialized. get_mut_or_default is the only place we
154 // initialize CollectionRefMut, and we never mutate it.
155 self.0.get().unwrap()
156 }
157}
158
159impl<T: CheckEmpty> DerefMut for CollectionRefMut<'_, T> {
160 fn deref_mut(&mut self) -> &mut Self::Target {
161 // CollectionRefMut will always be initialized. get_mut_or_default is the only place we
162 // initialize CollectionRefMut, and we never mutate it.
163 self.0.get_mut().unwrap()
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_empty_get_shouldnt_leak() {
173 // We rely on the Miri test to detect the resource leak.
174 let lazy_vec = LazyCollection::<Vec<u32>>::new(vec![]);
175 let empty_vec = lazy_vec.get();
176 assert!(empty_vec.is_empty());
177 std::mem::forget(lazy_vec);
178
179 let lazy_vec = LazyCollection::<Vec<u32>>::default();
180 let empty_vec = lazy_vec.get();
181 assert!(empty_vec.is_empty());
182 std::mem::forget(lazy_vec);
183 }
184
185 #[test]
186 fn test_non_empty_get_should_point_to_the_same_location() {
187 let lazy_vec = LazyCollection::new(vec![42]);
188 let vec1 = lazy_vec.get();
189 assert_eq!(*vec1, vec![42]);
190 let vec2 = lazy_vec.get();
191 assert!(std::ptr::eq(&*vec1, &*vec2));
192 }
193
194 #[test]
195 fn test_get_mut_should_get_the_content() {
196 let mut lazy_vec = LazyCollection::new(vec![64]);
197 {
198 let mut vec = lazy_vec.get_mut_or_default();
199 assert_eq!(*vec, vec![64]);
200 vec.push(33);
201 assert_eq!(*vec, vec![64, 33]);
202 }
203 let vec = lazy_vec.get_mut_or_default();
204 assert_eq!(*vec, vec![64, 33]);
205 }
206
207 #[test]
208 fn test_get_mut_empty_should_return_an_empty_collection() {
209 let mut lazy_vec = LazyCollection::<Vec<u32>>::default();
210 assert!(lazy_vec.get_mut_or_default().is_empty());
211 }
212
213 #[test]
214 fn test_get_mut_empty_shouldnt_leak() {
215 // We rely on the Miri test to detect the resource leak.
216 let mut lazy_vec = LazyCollection::<Vec<u32>>::default();
217 {
218 let vec = lazy_vec.get_mut_or_default();
219 assert!(vec.is_empty());
220 }
221 std::mem::forget(lazy_vec);
222 }
223
224 #[test]
225 fn test_get_mut_insert_and_clear_shouldnt_leak() {
226 // We rely on the Miri test to detect the resource leak.
227 // 2 test cases. One on the same CollectionRefMut. One on 2 different CollectionRefMut's.
228 {
229 let mut lazy_vec = LazyCollection::<Vec<u32>>::default();
230 let mut vec = lazy_vec.get_mut_or_default();
231 vec.push(42);
232 assert!(!vec.is_empty());
233 vec.remove(0);
234 assert!(vec.is_empty());
235 drop(vec);
236 std::mem::forget(lazy_vec);
237 }
238 {
239 let mut lazy_vec = LazyCollection::<Vec<u32>>::default();
240 let mut vec = lazy_vec.get_mut_or_default();
241 vec.push(42);
242 assert!(!vec.is_empty());
243 drop(vec);
244 let mut vec = lazy_vec.get_mut_or_default();
245 vec.remove(0);
246 assert!(vec.is_empty());
247 drop(vec);
248 std::mem::forget(lazy_vec);
249 }
250 }
251}