Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Dip/DipLinuxTests/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import PackageDescription

let package = Package(
name: "DipLinuxTests",
targets: [],
dependencies: [
.Package(url: "https://github.com/AliSoftware/Dip", majorVersion: 4, minor: 2),
]
)

156 changes: 156 additions & 0 deletions Dip/DipLinuxTests/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -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 ==<T: ClientImp>(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 ==<T: ServiceImp>(lhs: T, rhs: T) -> Bool {
return lhs === rhs
}


typealias TMain = @convention(c) (UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<Void>

func dispatch_async(block: TMain) {
var pid: pthread_t = 0
pthread_create(&pid, nil, block, nil)
}

func dispatch_sync(block: TMain) -> UnsafeMutablePointer<Void> {
var pid: pthread_t = 0
var result: UnsafeMutablePointer<Void> = nil
pthread_create(&pid, nil, block, nil)
pthread_join(pid, &result)
return result
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't both of those be in Utils.swift?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's only a test code. I did't have a chance to checkout what is there in swift-core-libdispatch, if it is usable I will try to replace it.


private var resolvedServices = Set<ServiceImp>()
private var resolvedClients = Array<ClientImp>()

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<Void> = nil
clientPointer = dispatch_sync(resolveClient)
let client = Unmanaged<ClientImp>.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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to let al the threads finish. Of course joining each thread would be better (then dispatch_xxx should return thread's pid). Again it's only because of limitations of swift-core libs, in this case xctest does not provide async utils yet.


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()])
4 changes: 1 addition & 3 deletions Sources/Dip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// THE SOFTWARE.
//

import Foundation

// MARK: - DependencyContainer

/**
Expand All @@ -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
Expand Down
44 changes: 42 additions & 2 deletions Sources/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// THE SOFTWARE.
//

import Foundation

extension Dictionary {
subscript(key: Key?) -> Value? {
get {
Expand All @@ -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