Skip to content

Commit

Permalink
Get rid of CTS TSan warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
kean committed Dec 16, 2017
1 parent 9c0fd9c commit 6aa1d3e
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 23 deletions.
51 changes: 30 additions & 21 deletions Sources/CancellationToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,53 @@ import Foundation
///
/// All `CancellationTokenSource` methods are thread safe.
public final class CancellationTokenSource {
public private(set) var isCancelling = false
private var observers = [() -> Void]()
private let lock: Lock
/// Returns `true` if cancellation has been requested for this token.
public var isCancelling: Bool {
_lock.lock(); defer { _lock.unlock() }
return _observers == nil
}

private var _observers: [() -> Void]? = []

/// Creates a new token associated with the source.
public var token: CancellationToken { return CancellationToken(source: self) }

/// Initializes the `CancellationTokenSource` instance.
public init() { self.lock = Lock() }
public init() {}

/// Allows to create cts with a shared lock to avoid excessive allocations.
/// This is tricky to use thus `internal` access modifier.
internal init(lock: Lock) { self.lock = lock }

fileprivate func register(_ closure: @escaping () -> Void) {
guard !isCancelling else { closure(); return } // fast pre-lock check
lock.sync {
if isCancelling {
closure()
} else {
observers.append(closure)
}
if !_register(closure) {
closure()
}
}

private func _register(_ closure: @escaping () -> Void) -> Bool {
_lock.lock(); defer { _lock.unlock() }
_observers?.append(closure)
return _observers != nil
}

/// Communicates a request for cancellation to the managed token.
public func cancel() {
guard !isCancelling else { return } // fast pre-lock check
lock.sync {
if !isCancelling {
isCancelling = true
observers.forEach { $0() }
observers.removeAll()
}
if let observers = _cancel() {
observers.forEach { $0() }
}
}

private func _cancel() -> [() -> Void]? {
_lock.lock(); defer { _lock.unlock() }
let observers = _observers
_observers = nil // transition to `isCancelling` state
return observers
}
}

// We use the same lock across differnet tokens because the design of CTS
// prevents potential issues. For example, closures registered with a token
// are never executed inside a lock.
private let _lock = Lock()

/// Enables cooperative cancellation of operations.
///
/// You create a cancellation token by instantiating a `CancellationTokenSource`
Expand Down
3 changes: 1 addition & 2 deletions Sources/Manager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Foundation
public final class Manager: Loading {
public let loader: Loading
public let cache: Caching?
private let lock = Lock()

/// Shared `Manager` instance.
///
Expand Down Expand Up @@ -62,7 +61,7 @@ public final class Manager: Loading {
}

// Create context and associate it with a target
let cts = CancellationTokenSource(lock: lock)
let cts = CancellationTokenSource()
let context = Context(cts)
Manager.setContext(context, for: target)

Expand Down

0 comments on commit 6aa1d3e

Please sign in to comment.