mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			257 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			257 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Swift
		
	
| /// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
 | |
| ///
 | |
| /// The below classes are an evolution of the old `Atomic<T>` types that we had implemented. A write-up on the need for
 | |
| /// the old class and it's approaches can be found at these links:
 | |
| /// https://www.vadimbulavin.com/atomic-properties/
 | |
| /// https://www.vadimbulavin.com/swift-atomic-properties-with-property-wrappers/
 | |
| ///
 | |
| /// We use the `ReadWriteLock` approach because the `DispatchQueue` approach means mutating the property
 | |
| /// occurs on a different thread, and `GRDB` requires it's changes to be executed on specific threads so using a lock
 | |
| /// is more compatible (and the `ReadWriteLock` allows for concurrent reads which shouldn't be a huge issue but could
 | |
| /// help reduce cases of blocking)
 | |
| 
 | |
| import Foundation
 | |
| 
 | |
| // MARK: - ThreadSafe
 | |
| 
 | |
| /// `ThreadSafe<Value>` is a wrapper providing a thread-safe way to get and set a value, it's limited to types which are marked
 | |
| /// as `ThreadSafeType` (reference types or structs which have `mutating` functions **should not** use this mechanism
 | |
| /// as it cannot ensure thread safety for those types)
 | |
| @propertyWrapper
 | |
| public class ThreadSafe<Value: ThreadSafeType> {
 | |
|     private var value: Value
 | |
|     private let lock: ReadWriteLock = ReadWriteLock()
 | |
| 
 | |
|     public var wrappedValue: Value {
 | |
|         get {
 | |
|             lock.readLock()
 | |
|             let result: Value = value
 | |
|             lock.unlock()
 | |
|             
 | |
|             return result
 | |
|         }
 | |
|         set {
 | |
|             lock.writeLock()
 | |
|             self.value = newValue
 | |
|             lock.unlock()
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // MARK: - Initialization
 | |
| 
 | |
|     public init(wrappedValue: Value) {
 | |
|         self.value = wrappedValue
 | |
|     }
 | |
|     
 | |
|     public init(_ wrappedValue: Value) {
 | |
|         self.value = wrappedValue
 | |
|     }
 | |
|     
 | |
|     // MARK: - Functions
 | |
| 
 | |
|     public func performUpdateAndMap<T>(_ closure: (Value) -> (Value, T)) -> T {
 | |
|         return try! performInternal { closure($0) }
 | |
|     }
 | |
|     
 | |
|     public func performUpdateAndMap<T>(_ closure: (Value) throws -> (Value, T)) throws -> T {
 | |
|         return try performInternal { try closure($0) }
 | |
|     }
 | |
|     
 | |
|     // MARK: - Internal Functions
 | |
|     
 | |
|     @discardableResult private func performInternal<T>(_ mutation: (Value) throws -> (Value, T)) throws -> T {
 | |
|         lock.writeLock()
 | |
|         defer { lock.unlock() }
 | |
|         
 | |
|         let (updatedValue, result) = try mutation(value)
 | |
|         self.value = updatedValue
 | |
|         return result
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// `ThreadSafeObject` is an implementation of `ThreadSafe` specifically for reference types, it avoids using `inout` within
 | |
| /// the `mutate` function (which can only really be done safely for reference types) and also supports reentrant access (ie. if we are
 | |
| /// already in a mutation on the current thread then there is no need to acquire another lock, just interact with the value directly)
 | |
| ///
 | |
| /// **Note:** There is some potential for confusing behaviour when using this type in a reentrant way - if the object is entirely replaced
 | |
| /// than previous instances provided in the closures won't be updated with the new instance (this is more problemmatic for mutating structs
 | |
| /// rather than objects as their isntances get replaced, there are critical logs for these cases)
 | |
| @propertyWrapper
 | |
| public final class ThreadSafeObject<Value> {
 | |
|     private var value: Value
 | |
|     private let lock: ReadWriteLock = ReadWriteLock()
 | |
|     @ThreadSafe private var mutationThreadId: UInt32? = nil
 | |
| 
 | |
|     public var wrappedValue: Value {
 | |
|         guard mutationThreadId != Thread.current.threadId else { return value }
 | |
|         
 | |
|         lock.readLock()
 | |
|         defer { lock.unlock() }
 | |
|         return value
 | |
|     }
 | |
|     
 | |
|     // MARK: - Initialization
 | |
| 
 | |
|     public init(wrappedValue: Value) {
 | |
|         self.value = wrappedValue
 | |
|     }
 | |
|     
 | |
|     public init(_ wrappedValue: Value) {
 | |
|         self.value = wrappedValue
 | |
|     }
 | |
|     
 | |
|     // MARK: - Functions
 | |
|     
 | |
|     /// Replace the current value with an updated one
 | |
|     public func set(to value: Value) {
 | |
|         guard mutationThreadId != Thread.current.threadId else {
 | |
|             if Value.self is any Collection || Value.self is DictionaryType.Type {
 | |
|                 Log.critical("ThreadSafeObject<\(Value.self)>.set(to:) called while in mutation, this could result in buggy behaviours")
 | |
|             }
 | |
|             
 | |
|             self.value = value
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         lock.writeLock()
 | |
|         self.value = value
 | |
|         lock.unlock()
 | |
|     }
 | |
|     
 | |
|     public func perform(_ closure: (Value) -> ()) {
 | |
|         try? performInternal { value in
 | |
|             closure(value)
 | |
|             return (value, ())
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public func perform(_ closure: (Value) throws -> ()) throws {
 | |
|         try performInternal { value in
 | |
|             try closure(value)
 | |
|             return (value, ())
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public func performUpdate(_ closure: (Value) -> Value) {
 | |
|         try? performInternal { (closure($0), ()) }
 | |
|     }
 | |
|     
 | |
|     public func performUpdate(_ closure: (Value) throws -> Value) throws {
 | |
|         try performInternal { (try closure($0), ()) }
 | |
|     }
 | |
| 
 | |
|     public func performMap<T>(_ closure: (Value) -> T) -> T {
 | |
|         return try! performInternal { ($0, closure($0)) }
 | |
|     }
 | |
|     
 | |
|     public func performMap<T>(_ closure: (Value) throws -> T) throws -> T {
 | |
|         return try performInternal { ($0, try closure($0)) }
 | |
|     }
 | |
|     
 | |
|     public func performUpdateAndMap<T>(_ closure: (Value) -> (Value, T)) -> T {
 | |
|         return try! performInternal { closure($0) }
 | |
|     }
 | |
|     
 | |
|     public func performUpdateAndMap<T>(_ closure: (Value) throws -> (Value, T)) throws -> T {
 | |
|         return try performInternal { try closure($0) }
 | |
|     }
 | |
|     
 | |
|     // MARK: - Internal Functions
 | |
|     
 | |
|     @discardableResult private func performInternal<T>(_ mutation: (Value) throws -> (Value, T)) throws -> T {
 | |
|         guard mutationThreadId != Thread.current.threadId else {
 | |
|             if Value.self is any Collection || Value.self is DictionaryType.Type {
 | |
|                 Log.critical("ThreadSafeObject<\(Value.self)> called in a reentrant way while in mutation, this could result in buggy behaviours")
 | |
|             }
 | |
|             
 | |
|             let (updatedValue, result) = try mutation(value)
 | |
|             self.value = updatedValue
 | |
|             return result
 | |
|         }
 | |
|         
 | |
|         lock.writeLock()
 | |
|         mutationThreadId = Thread.current.threadId
 | |
|         defer {
 | |
|             mutationThreadId = nil
 | |
|             lock.unlock()
 | |
|         }
 | |
|         
 | |
|         let (updatedValue, result) = try mutation(value)
 | |
|         self.value = updatedValue
 | |
|         return result
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - ReadWriteLock
 | |
| 
 | |
| class ReadWriteLock {
 | |
|     private var rwlock: pthread_rwlock_t
 | |
|     
 | |
|     // Need to do this in a proper init function instead of a lazy variable or it can indefinitely
 | |
|     // hang on XCode 15 when trying to retrieve a lock (potentially due to optimisations?)
 | |
|     init() {
 | |
|         rwlock = pthread_rwlock_t()
 | |
|         pthread_rwlock_init(&rwlock, nil)
 | |
|     }
 | |
|     
 | |
|     func writeLock() {
 | |
|         pthread_rwlock_wrlock(&rwlock)
 | |
|     }
 | |
|     
 | |
|     func readLock() {
 | |
|         pthread_rwlock_rdlock(&rwlock)
 | |
|     }
 | |
|     
 | |
|     func unlock() {
 | |
|         pthread_rwlock_unlock(&rwlock)
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - ThreadSafeType
 | |
| 
 | |
| /// The `ThreadSafe` type doesn't work with mutating function so we want to constrain it to "safe" types
 | |
| public protocol ThreadSafeType {}
 | |
| extension Int: ThreadSafeType {}
 | |
| extension Int8: ThreadSafeType {}
 | |
| extension Int16: ThreadSafeType {}
 | |
| extension Int32: ThreadSafeType {}
 | |
| extension Int64: ThreadSafeType {}
 | |
| extension UInt8: ThreadSafeType {}
 | |
| extension UInt16: ThreadSafeType {}
 | |
| extension UInt32: ThreadSafeType {}
 | |
| extension UInt64: ThreadSafeType {}
 | |
| extension Bool: ThreadSafeType {}
 | |
| extension Double: ThreadSafeType {}
 | |
| extension Date: ThreadSafeType {}
 | |
| extension UUID: ThreadSafeType {}
 | |
| extension Optional: ThreadSafeType where Wrapped: ThreadSafeType {}
 | |
| 
 | |
| @available(*, unavailable, message: "Use ThreadSafeObject instead")
 | |
| extension Array: ThreadSafeType {}
 | |
| @available(*, unavailable, message: "Use ThreadSafeObject instead")
 | |
| extension Set: ThreadSafeType {}
 | |
| @available(*, unavailable, message: "Use ThreadSafeObject instead")
 | |
| extension Dictionary: ThreadSafeType {}
 | |
| 
 | |
| // MARK: - CustomDebugStringConvertible
 | |
| 
 | |
| extension ThreadSafe: CustomDebugStringConvertible where Value: CustomDebugStringConvertible {
 | |
|     public var debugDescription: String {
 | |
|         return wrappedValue.debugDescription
 | |
|     }
 | |
| }
 | |
| 
 | |
| extension ThreadSafeObject: CustomDebugStringConvertible where Value: CustomDebugStringConvertible {
 | |
|     public var debugDescription: String {
 | |
|         return wrappedValue.debugDescription
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - Convenience
 | |
| 
 | |
| private extension Thread {
 | |
|     var threadId: UInt32 {
 | |
|         pthread_mach_thread_np(pthread_self())
 | |
|     }
 | |
| }
 |