objc2_core_foundation/
thread_safety.rs

1//! Thread-safety.
2
3use crate::*;
4
5// SAFETY: CFBundle is immutable, and can be retrieved from any thread.
6#[cfg(feature = "CFBundle")]
7unsafe impl Send for CFBundle {}
8#[cfg(feature = "CFBundle")]
9unsafe impl Sync for CFBundle {}
10
11// SAFETY: `NSNotificationCenter` and `NSDistributedNotificationCenter` are
12// thread-safe, and those build upon this, so it must be thread-safe too.
13//
14// Additionally, they can be retrieved from any thread.
15//
16// NOTE: added notification observers are sent to the main thread, so the
17// functions for doing that are not necessarily safe.
18#[cfg(feature = "CFNotificationCenter")]
19unsafe impl Send for CFNotificationCenter {}
20#[cfg(feature = "CFNotificationCenter")]
21unsafe impl Sync for CFNotificationCenter {}
22
23// SAFETY: CFUUID is immutable and just stores the 16 bytes.
24#[cfg(feature = "CFUUID")]
25unsafe impl Send for CFUUID {}
26#[cfg(feature = "CFUUID")]
27unsafe impl Sync for CFUUID {}
28
29// SAFETY: NSNumber is thread-safe, and this is toll-free bridged to that.
30#[cfg(feature = "CFNumber")]
31unsafe impl Send for CFBoolean {}
32#[cfg(feature = "CFNumber")]
33unsafe impl Sync for CFBoolean {}
34#[cfg(feature = "CFNumber")]
35unsafe impl Send for CFNumber {}
36#[cfg(feature = "CFNumber")]
37unsafe impl Sync for CFNumber {}
38
39// SAFETY: NSDate is thread-safe, and this is toll-free bridged to that.
40#[cfg(feature = "CFDate")]
41unsafe impl Send for CFDate {}
42#[cfg(feature = "CFDate")]
43unsafe impl Sync for CFDate {}
44
45// SAFETY: NSError is thread-safe, and this is toll-free bridged to that.
46// Also, Foundation's .swiftinterface adds a `@unchecked Swift.Sendable`
47// protocol conformance to CoreFoundation.CFError.
48#[cfg(feature = "CFError")]
49unsafe impl Send for CFError {}
50#[cfg(feature = "CFError")]
51unsafe impl Sync for CFError {}
52
53// SAFETY: NSLocale is thread-safe, and this is toll-free bridged to that.
54#[cfg(feature = "CFLocale")]
55unsafe impl Send for CFLocale {}
56#[cfg(feature = "CFLocale")]
57unsafe impl Sync for CFLocale {}
58
59// SAFETY: NSNull is thread-safe, and this is toll-free bridged to that.
60unsafe impl Send for CFNull {}
61unsafe impl Sync for CFNull {}
62
63// SAFETY: NSTimeZone is thread-safe, and this is toll-free bridged to that.
64#[cfg(feature = "CFDate")]
65unsafe impl Send for CFTimeZone {}
66#[cfg(feature = "CFDate")]
67unsafe impl Sync for CFTimeZone {}
68
69// SAFETY: NSURL is thread-safe, and this is toll-free bridged to that.
70#[cfg(feature = "CFURL")]
71unsafe impl Send for CFURL {}
72#[cfg(feature = "CFURL")]
73unsafe impl Sync for CFURL {}
74
75#[allow(unused_macros)]
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    macro_rules! not_thread_safe {
81        ($($ty:ty),*) => {$(
82            static_assertions::assert_not_impl_any!($ty: Send, Sync);
83        )?};
84    }
85
86    macro_rules! thread_safe {
87        ($($ty:ty),*) => {$(
88            // General assumption: Assumes that the allocator this was created
89            // with is also thread-safe; that's probably a fair assumption,
90            // otherwise nothing in CF would be thread-safe.
91            static_assertions::assert_impl_all!($ty: Send, Sync);
92        )?};
93    }
94
95    #[test]
96    fn base() {
97        // CFType can hold thread-unsafe objects.
98        // Just like AnyObject and NSObject aren't thread-safe.
99        not_thread_safe!(CFType, CFPropertyList);
100
101        // Only the built-ins are thread-safe, other allocators aren't
102        // guaranteed to be. Usually fine though, since they're placed in
103        // statics anyhow, and can thus already be accessed from all threads.
104        not_thread_safe!(CFAllocator);
105    }
106
107    /// Collections, mutable types and immutable types with mutable variants
108    /// aren't thread-safe:
109    /// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW9>
110    ///
111    /// Note that things that are known to be the immutable variant are
112    /// usually thread-safe (which is why e.g. `CFString` in statics is fine).
113    #[test]
114    fn mutable_or_mutable_variant() {
115        #[cfg(feature = "CFBag")]
116        not_thread_safe!(CFBag<u32>, CFMutableBag<u32>);
117        #[cfg(feature = "CFBinaryHeap")]
118        not_thread_safe!(CFBinaryHeap);
119        #[cfg(feature = "CFBitVector")]
120        not_thread_safe!(CFBitVector, CFMutableBitVector);
121        #[cfg(feature = "CFDateFormatter")]
122        not_thread_safe!(CFDateFormatter); // Explicitly stated to not be thread-safe
123        #[cfg(feature = "CFFileDescriptor")]
124        not_thread_safe!(CFFileDescriptor);
125        #[cfg(feature = "CFNumberFormatter")]
126        not_thread_safe!(CFNumberFormatter); // Explicitly stated to not be thread-safe
127        #[cfg(feature = "CFSocket")]
128        not_thread_safe!(CFSocket);
129        #[cfg(feature = "CFStringTokenizer")]
130        not_thread_safe!(CFStringTokenizer);
131        #[cfg(feature = "CFTree")]
132        not_thread_safe!(CFTree);
133        #[cfg(feature = "CFURLEnumerator")]
134        not_thread_safe!(CFURLEnumerator);
135        #[cfg(feature = "CFUserNotification")]
136        not_thread_safe!(CFUserNotification);
137
138        // Types are not marked as thread-safe if their toll-free
139        // bridged type is not thread safe either.
140        #[cfg(feature = "CFArray")]
141        not_thread_safe!(CFArray<u32>, CFMutableArray<u32>);
142        #[cfg(feature = "CFAttributedString")]
143        not_thread_safe!(CFAttributedString, CFMutableAttributedString);
144        #[cfg(feature = "CFCalendar")]
145        not_thread_safe!(CFCalendar);
146        #[cfg(feature = "CFCharacterSet")]
147        not_thread_safe!(CFCharacterSet, CFMutableCharacterSet);
148        #[cfg(feature = "CFData")]
149        not_thread_safe!(CFData, CFMutableData);
150        #[cfg(feature = "CFDictionary")]
151        not_thread_safe!(CFDictionary<u32, u32>, CFMutableDictionary<u32, u32>);
152        #[cfg(feature = "CFFileSecurity")]
153        not_thread_safe!(CFFileSecurity);
154        #[cfg(feature = "CFMachPort")]
155        not_thread_safe!(CFMachPort);
156        #[cfg(feature = "CFMessagePort")]
157        not_thread_safe!(CFMessagePort);
158        #[cfg(feature = "CFRunLoop")]
159        not_thread_safe!(CFRunLoopTimer);
160        #[cfg(feature = "CFSet")]
161        not_thread_safe!(CFSet<u32>, CFMutableSet<u32>);
162        #[cfg(feature = "CFString")]
163        not_thread_safe!(CFString, CFMutableString);
164        #[cfg(feature = "CFStream")]
165        not_thread_safe!(CFReadStream, CFWriteStream);
166    }
167
168    /// Immutable CF types are generally thread-safe.
169    #[test]
170    fn immutable() {
171        #[cfg(feature = "CFBundle")]
172        thread_safe!(CFBundle);
173
174        #[cfg(feature = "CFNotificationCenter")]
175        thread_safe!(CFNotificationCenter);
176
177        #[cfg(feature = "CFUUID")]
178        thread_safe!(CFUUID);
179
180        // Types whose toll-free bridged type is thread-safe are also marked
181        // as thread-safe.
182
183        #[cfg(feature = "CFNumber")]
184        thread_safe!(CFBoolean, CFNumber);
185        #[cfg(feature = "CFDate")]
186        thread_safe!(CFDate);
187        #[cfg(feature = "CFError")]
188        thread_safe!(CFError);
189        #[cfg(feature = "CFLocale")]
190        thread_safe!(CFLocale);
191        thread_safe!(CFNull);
192        #[cfg(feature = "CFDate")]
193        thread_safe!(CFTimeZone);
194        #[cfg(feature = "CFURL")]
195        thread_safe!(CFURL);
196    }
197
198    #[test]
199    fn uncertain() {
200        // Uncertain, so marked as thread-unsafe for now.
201        #[cfg(feature = "CFBundle")]
202        not_thread_safe!(CFPlugIn);
203        #[cfg(feature = "CFPlugIn")]
204        not_thread_safe!(CFPlugInInstance);
205
206        // Under discussion in https://github.com/madsmtm/objc2/issues/696.
207        #[cfg(feature = "CFRunLoop")]
208        not_thread_safe!(CFRunLoop, CFRunLoopSource, CFRunLoopObserver);
209    }
210}