Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes some issues related to locking. #859

Merged
merged 4 commits into from
Nov 17, 2022
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# master
*Please add new entries at the top.*

1. Fix some issues related to locking, bumped min OS versions to iOS 10, macOS 10.12, tvOS 10, watchOS 3 (#859, kudos to @mluisbrown)
1. Add `async` helpers to Schedulers (#857, kudos to @p4checo)
1. Add primary associated types to SignalProducerConvertible & SignalProducerProtocol (#855, kudos to @braker1nine)
2. Refactor Github Actions to cover more swift versions (#858, kudos to @braker1nine)
1.Use `OSAllocatedUnfairLock` instead of `os_unfair_lock` on supported Apple platforms (#856, kudos to @mluisbrown)
1. Use `OSAllocatedUnfairLock` instead of `os_unfair_lock` on supported Apple platforms (#856, kudos to @mluisbrown)

# 7.0.0
1. The UnidirectionalBinding operator `<~` returns non optional values. (#834, kudos to @NicholasTD07)
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "ReactiveSwift",
platforms: [
.macOS(.v10_10), .iOS(.v9), .tvOS(.v9), .watchOS(.v2)
.macOS(.v10_12), .iOS(.v10), .tvOS(.v10), .watchOS(.v3)
],
products: [
.library(name: "ReactiveSwift", targets: ["ReactiveSwift"]),
Expand Down
10 changes: 5 additions & 5 deletions ReactiveSwift.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ Pod::Spec.new do |s|
s.license = { :type => "MIT", :file => "LICENSE.md" }
s.author = "ReactiveCocoa"

s.ios.deployment_target = "9.0"
s.osx.deployment_target = "10.9"
s.watchos.deployment_target = "2.0"
s.tvos.deployment_target = "9.0"
s.ios.deployment_target = "10.0"
s.osx.deployment_target = "10.12"
s.watchos.deployment_target = "3.0"
s.tvos.deployment_target = "10.0"
s.source = { :git => "https://github.com/ReactiveCocoa/ReactiveSwift.git", :tag => "#{s.version}" }
# Directory glob for all Swift files
s.source_files = ["Sources/*.{swift}", "Sources/**/*.{swift}"]

s.pod_target_xcconfig = {"OTHER_SWIFT_FLAGS[config=Release]" => "$(inherited) -suppress-warnings" }

s.cocoapods_version = ">= 1.7.0"
s.swift_versions = ["5.1", "5.2"]
s.swift_versions = ["5.2", "5.3" "5.4", "5.5", "5.6", "5.7"]
end
44 changes: 22 additions & 22 deletions ReactiveSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1516,16 +1516,16 @@
CODE_SIGNING_REQUIRED = NO;
CURRENT_PROJECT_VERSION = 1;
ENABLE_TESTABILITY = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MACOSX_DEPLOYMENT_TARGET = 10.9;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO;
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 9.0;
SWIFT_VERSION = 5.2;
TVOS_DEPLOYMENT_TARGET = 10.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 2.0;
WATCHOS_DEPLOYMENT_TARGET = 3.0;
};
name = Debug;
};
Expand All @@ -1536,16 +1536,16 @@
BITCODE_GENERATION_MODE = bitcode;
CODE_SIGNING_REQUIRED = NO;
CURRENT_PROJECT_VERSION = 1;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MACOSX_DEPLOYMENT_TARGET = 10.9;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO;
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 9.0;
SWIFT_VERSION = 5.2;
TVOS_DEPLOYMENT_TARGET = 10.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 2.0;
WATCHOS_DEPLOYMENT_TARGET = 3.0;
};
name = Release;
};
Expand Down Expand Up @@ -1578,7 +1578,7 @@
baseConfigurationReference = E6124BA9267DF505005A3490 /* macOS-XCTest.xcconfig */;
buildSettings = {
INFOPLIST_FILE = Tests/ReactiveSwiftTests/Info.plist;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_NAME = "$(PROJECT_NAME)Tests";
};
name = Debug;
Expand All @@ -1588,7 +1588,7 @@
baseConfigurationReference = E6124BA9267DF505005A3490 /* macOS-XCTest.xcconfig */;
buildSettings = {
INFOPLIST_FILE = Tests/ReactiveSwiftTests/Info.plist;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_NAME = "$(PROJECT_NAME)Tests";
};
name = Release;
Expand Down Expand Up @@ -1638,16 +1638,16 @@
BITCODE_GENERATION_MODE = bitcode;
CODE_SIGNING_REQUIRED = NO;
CURRENT_PROJECT_VERSION = 1;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MACOSX_DEPLOYMENT_TARGET = 10.9;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO;
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 9.0;
SWIFT_VERSION = 5.2;
TVOS_DEPLOYMENT_TARGET = 10.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 2.0;
WATCHOS_DEPLOYMENT_TARGET = 3.0;
};
name = Profile;
};
Expand Down Expand Up @@ -1699,16 +1699,16 @@
BITCODE_GENERATION_MODE = bitcode;
CODE_SIGNING_REQUIRED = NO;
CURRENT_PROJECT_VERSION = 1;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MACOSX_DEPLOYMENT_TARGET = 10.9;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)-Tests";
PRODUCT_NAME = "$(PROJECT_NAME)";
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO;
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 9.0;
SWIFT_VERSION = 5.2;
TVOS_DEPLOYMENT_TARGET = 10.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 2.0;
WATCHOS_DEPLOYMENT_TARGET = 3.0;
};
name = Test;
};
Expand Down
81 changes: 33 additions & 48 deletions Sources/Atomic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ internal struct UnsafeAtomicState<State: RawRepresentable> where State.RawValue
/// - returns: `true` if the transition succeeds. `false` otherwise.
internal func tryTransition(from expected: State, to next: State) -> Bool {
return OSAtomicCompareAndSwap32Barrier(expected.rawValue,
next.rawValue,
value)
next.rawValue,
value)
}
#else
private let value: Atomic<Int32>
Expand Down Expand Up @@ -108,10 +108,6 @@ internal struct UnsafeAtomicState<State: RawRepresentable> where State.RawValue
/// fallback.
internal class Lock: LockProtocol {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
@available(iOS 10.0, *)
@available(macOS 10.12, *)
@available(tvOS 10.0, *)
@available(watchOS 3.0, *)
internal final class UnfairLock: Lock {
private let _lock: os_unfair_lock_t

Expand Down Expand Up @@ -140,33 +136,30 @@ internal class Lock: LockProtocol {
}
#endif

#if compiler(>=5.7)
#if !targetEnvironment(macCatalyst)
Copy link
Contributor Author

@mluisbrown mluisbrown Nov 17, 2022

Choose a reason for hiding this comment

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

Now that macOS 13 and Xcode 14 are released, we can remove this silly macCatalyst dance and simplify the conditions a lot.

#if os(iOS) || os(tvOS) || os(watchOS)
@available(iOS 16.0, *)
@available(tvOS 16.0, *)
@available(watchOS 9.0, *)
internal final class AllocatedUnfairLock: Lock {
private let _lock = OSAllocatedUnfairLock()

override init() {
super.init()
}

override func lock() {
_lock.lock()
}

override func unlock() {
_lock.unlock()
}

override func `try`() -> Bool {
_lock.lockIfAvailable()
}
}
#endif
#endif
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
@available(iOS 16.0, *)
@available(macOS 13.0, *)
@available(tvOS 16.0, *)
@available(watchOS 9.0, *)
internal final class AllocatedUnfairLock: Lock {
private let _lock = OSAllocatedUnfairLock()

override init() {
super.init()
}

override func lock() {
_lock.lock()
}

override func unlock() {
_lock.unlock()
}

override func `try`() -> Bool {
_lock.lockIfAvailable()
}
}
#endif

internal final class PthreadLock: Lock {
Expand Down Expand Up @@ -227,23 +220,15 @@ internal class Lock: LockProtocol {
}

static func make() -> Self {
#if compiler(>=5.7)
#if !targetEnvironment(macCatalyst)
#if os(iOS) || os(tvOS) || os(watchOS)
if #available(*, iOS 16.0, tvOS 16.0, watchOS 9.0) {
return AllocatedUnfairLock() as! Self
}
#endif
#endif
#endif

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
if #available(*, macOS 11.12, iOS 10.0, tvOS 10.0, watchOS 3.0) {
return UnfairLock() as! Self
}
#endif
guard #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) else {
return UnfairLock() as! Self
}

return PthreadLock() as! Self
return AllocatedUnfairLock() as! Self
#else
return PthreadLock() as! Self
#endif
}

private init() {}
Expand Down
18 changes: 7 additions & 11 deletions Sources/Scheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ public final class TestScheduler: DateScheduler {
@MainActor
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, *)
public func advance(by interval: DispatchTimeInterval) async {
await advance(to: lock.withLock({ currentDate.addingTimeInterval(interval) }))
await advance(to: lock.sync({ currentDate.addingTimeInterval(interval) }))
}

/// Advances the virtualized clock by the given interval, dequeuing and
Expand All @@ -753,7 +753,7 @@ public final class TestScheduler: DateScheduler {
@MainActor
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, *)
public func advance(by interval: TimeInterval) async {
await advance(to: lock.withLock({ currentDate.addingTimeInterval(interval) }))
await advance(to: lock.sync({ currentDate.addingTimeInterval(interval) }))
}

/// Advances the virtualized clock to the given future date, dequeuing and
Expand All @@ -764,12 +764,12 @@ public final class TestScheduler: DateScheduler {
@MainActor
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, *)
public func advance(to newDate: Date) async {
assert(lock.withLock { _currentDate <= newDate })
assert(lock.sync { _currentDate <= newDate })

while lock.withLock({ _currentDate }) <= newDate {
while lock.sync({ _currentDate }) <= newDate {
await Task.megaYield()

let `return` = lock.withLock {
let `return` = lock.sync {
guard
let next = scheduledActions.first,
newDate >= next.date
Expand Down Expand Up @@ -799,15 +799,11 @@ public final class TestScheduler: DateScheduler {
#endif
}

@available(macOS, obsoleted: 13, message: "`NSLocking` now provides `withLocking`, so this is no longer needed")
@available(iOS, obsoleted: 13, message: "`NSLocking` now provides `withLocking`, so this is no longer needed")
@available(watchOS, obsoleted: 9, message: "`NSLocking` now provides `withLocking`, so this is no longer needed")
@available(macCatalyst, obsoleted: 16, message: "`NSLocking` now provides `withLocking`, so this is no longer needed")
extension NSRecursiveLock {
fileprivate func withLock<T>(_ body: () throws -> T) rethrows -> T {
fileprivate func sync<T>(_ operation: () -> T) -> T {
self.lock()
defer { self.unlock() }
return try body()
return operation()
}
}

Expand Down
14 changes: 4 additions & 10 deletions Tests/ReactiveSwiftTests/DateSchedulerAsyncTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,29 @@ final class DateSchedulerAsyncTestCase: XCTestCase {
}

func test_sleepFor_shouldSleepForTheDefinedIntervalBeforeReturning() async throws {
let expectation = self.expectation(description: "sleep")

Task {
let task = Task {
try await scheduler.sleep(for: .seconds(5))
XCTAssertEqual(scheduler.currentDate, startDate.addingTimeInterval(5))
expectation.fulfill()
}

XCTAssertEqual(scheduler.currentDate, startDate)
await scheduler.advance(by: .seconds(5))

await waitForExpectations(timeout: 0.1)
let _ = await task.result
}

func test_sleepUntil_shouldSleepForTheDefinedIntervalBeforeReturning() async throws {
let expectation = self.expectation(description: "sleep")

let sleepDate = startDate.addingTimeInterval(5)

Task {
let task = Task {
try await scheduler.sleep(until: sleepDate)
XCTAssertEqual(scheduler.currentDate, sleepDate)
expectation.fulfill()
}

XCTAssertEqual(scheduler.currentDate, startDate)
await scheduler.advance(by: .seconds(5))

await waitForExpectations(timeout: 0.1)
let _ = await task.result
}

func test_timer_shouldSendTheCurrentDateAtTheGivenInterval() async throws {
Expand Down