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}