diff --git a/Dip/DipLinuxTests/Package.swift b/Dip/DipLinuxTests/Package.swift new file mode 100644 index 0000000..a530535 --- /dev/null +++ b/Dip/DipLinuxTests/Package.swift @@ -0,0 +1,10 @@ +import PackageDescription + +let package = Package( + name: "DipLinuxTests", + targets: [], + dependencies: [ + .Package(url: "https://github.com/AliSoftware/Dip", majorVersion: 4, minor: 2), + ] +) + diff --git a/Dip/DipLinuxTests/Sources/main.swift b/Dip/DipLinuxTests/Sources/main.swift new file mode 100644 index 0000000..6fa2417 --- /dev/null +++ b/Dip/DipLinuxTests/Sources/main.swift @@ -0,0 +1,156 @@ +import Glibc +import XCTest +import Dip + + +private protocol Service: class { + var client: Client? { get set } +} + +private protocol Client: class { + var service: Service { get } +} + +private class ClientImp: Client, Equatable { + var service: Service + init(service: Service) { + self.service = service + } +} + +private func ==(lhs: T, rhs: T) -> Bool { + return lhs === rhs +} + +private class ServiceImp: Service, Hashable { + weak var client: Client? + init() {} + + var hashValue: Int { + return unsafeAddressOf(self).hashValue + } +} + +private func ==(lhs: T, rhs: T) -> Bool { + return lhs === rhs +} + + +typealias TMain = @convention(c) (UnsafeMutablePointer) -> UnsafeMutablePointer + +func dispatch_async(block: TMain) { + var pid: pthread_t = 0 + pthread_create(&pid, nil, block, nil) +} + +func dispatch_sync(block: TMain) -> UnsafeMutablePointer { + var pid: pthread_t = 0 + var result: UnsafeMutablePointer = nil + pthread_create(&pid, nil, block, nil) + pthread_join(pid, &result) + return result +} + +private var resolvedServices = Set() +private var resolvedClients = Array() + +private var lock: pthread_spinlock_t = 0 +pthread_spin_init(&lock, 0) + +private var container: DependencyContainer! + +class ThreadSafetyTests: XCTestCase { + + var allTests: [(String, () throws -> Void)] { + return [ + ("testSingletonThreadSafety", testSingletonThreadSafety), + ("testFactoryThreadSafety", testFactoryThreadSafety), + ("testCircularReferenceThreadSafety", testCircularReferenceThreadSafety) + ] + } + + func setUp() { + container = DependencyContainer() + } + + func tearDown() { + } + + func testSingletonThreadSafety() { + container.register(.Singleton) { ServiceImp() as Service } + + for _ in 0..<100 { + dispatch_async { _ in + let service = try! container.resolve() as Service + pthread_spin_lock(&lock) + resolvedServices.insert(service as! ServiceImp) + pthread_spin_unlock(&lock) + return nil + } + } + + sleep(1) + XCTAssertEqual(resolvedServices.count, 1, "Should create only one instance") + resolvedServices.removeAll() + } + + + func testFactoryThreadSafety() { + container.register { ServiceImp() as Service } + + for _ in 0..<100 { + dispatch_async { _ in + let service = try! container.resolve() as Service + pthread_spin_lock(&lock) + resolvedServices.insert(service as! ServiceImp) + pthread_spin_unlock(&lock) + return nil + } + } + + sleep(1) + XCTAssertEqual(resolvedServices.count, 100, "All instances should be different") + resolvedServices.removeAll() + } + + + func testCircularReferenceThreadSafety() { + container.register(.ObjectGraph) { ClientImp(service: try container.resolve()) as Client } + + let resolveClient: TMain = { _ in + let resolved = try! container.resolve() as Client + return UnsafeMutablePointer(Unmanaged.passUnretained(resolved as! ClientImp).toOpaque()) + } + container.register(.ObjectGraph) { ServiceImp() as Service } + .resolveDependencies { container, service in + var clientPointer: UnsafeMutablePointer = nil + clientPointer = dispatch_sync(resolveClient) + let client = Unmanaged.fromOpaque(COpaquePointer(clientPointer)).takeUnretainedValue() + service.client = client + } + + for _ in 0..<100 { + dispatch_async { _ in + let client = try! container.resolve() as Client + pthread_spin_lock(&lock) + resolvedClients.append(client as! ClientImp) + pthread_spin_unlock(&lock) + return nil + } + } + + sleep(2) + + XCTAssertEqual(resolvedClients.count, 100, "Instances should be not reused in different object graphs") + for client in resolvedClients { + let service = client.service as! ServiceImp + let serviceClient = service.client as! ClientImp + XCTAssertEqual(serviceClient, client, "Instances should be reused when resolving single object graph") + } + + resolvedClients.removeAll() + } + +} + +XCTMain([ThreadSafetyTests()]) diff --git a/Sources/Dip.swift b/Sources/Dip.swift index 888732e..140378a 100644 --- a/Sources/Dip.swift +++ b/Sources/Dip.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import Foundation - // MARK: - DependencyContainer /** @@ -43,7 +41,7 @@ public final class DependencyContainer { var definitions = [DefinitionKey : Definition]() let resolvedInstances = ResolvedInstances() - let lock = NSRecursiveLock() + let lock = RecursiveLock() /** Designated initializer for a DependencyContainer diff --git a/Sources/Utils.swift b/Sources/Utils.swift index a90f3a2..ccc2a3d 100644 --- a/Sources/Utils.swift +++ b/Sources/Utils.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import Foundation - extension Dictionary { subscript(key: Key?) -> Value? { get { @@ -42,3 +40,45 @@ extension Optional { return self.map { "\($0)" } ?? "nil" } } + +#if os(Linux) + import Glibc + class RecursiveLock { + private var _lock = _initializeRecursiveMutex() + + func lock() { + _lock.lock() + } + + func unlock() { + _lock.unlock() + } + + deinit { + pthread_mutex_destroy(&_lock) + } + + } + + private func _initializeRecursiveMutex() -> pthread_mutex_t { + var mutex: pthread_mutex_t = pthread_mutex_t() + var mta: pthread_mutexattr_t = pthread_mutexattr_t() + pthread_mutexattr_init(&mta) + pthread_mutexattr_settype(&mta, Int32(PTHREAD_MUTEX_RECURSIVE)) + pthread_mutex_init(&mutex, &mta) + return mutex + } + + extension pthread_mutex_t { + mutating func lock() { + pthread_mutex_trylock(&self) + } + mutating func unlock() { + pthread_mutex_unlock(&self) + } + } + +#else + import Foundation + typealias RecursiveLock = NSRecursiveLock +#endif