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