-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Mark Operator Closures as @Sendable #2639
base: main
Are you sure you want to change the base?
Conversation
…ethods are all called on the same thread.
…introduced warnings
# Conflicts: # RxSwift/Schedulers/VirtualTimeScheduler.swift
Added PR #2610 here |
@freak4pc Hi! Just checking in to see if there’s any chance this PR could be reviewed. Getting this merged is essential for updating my project to Swift 6, and I imagine others may be in a similar situation. I’m eager to get feedback or make any adjustments needed to move it forward. Thanks so much for your time! |
Hey @AndreiArdelean1 - |
# Conflicts: # RxSwift/Schedulers/VirtualTimeScheduler.swift
Rather than making these Here is the relevant forum thread. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many Sendable conformances are unsafe, especially the generic ones. Rather than marking the generic types as @unchecked Sendable
, which will cause issues when their generic types are non-Sendable
. Instead, they should be conditionally Sendable
, i.e.:
extension GenericClass: Sendable where Element1: Sendable, Element2: Sendable /* ... */ {}
Many (most?) of the class conformance's do not need to be marked @unchecked
. From the Sendable documentation:
To satisfy the requirements of the Sendable protocol, a class must:
- Be marked final
- Contain only stored properties that are immutable and sendable
- Have no superclass or have NSObject as the superclass
Classes marked with @mainactor are implicitly sendable, because the main actor coordinates all access to its state. These classes can have stored properties that are mutable and nonsendable.
Classes that don’t meet the requirements above can be marked as @unchecked Sendable, disabling compile-time correctness checks, after you manually verify that they satisfy the Sendable protocol’s semantic requirements.
The closure typealiases do not need to be marked as @Sendable
, only their usage as parameters need to be marked as sending
var elements = [Element]() | ||
var error: Swift.Error? | ||
private func materializeResult(max: Int? = nil, predicate: @escaping @Sendable (Element) throws -> Bool = { _ in true }) -> MaterializedSequenceResult<Element> { | ||
nonisolated(unsafe) var elements = [Element]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do these need to be nonisolated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because elements
is accessed both in the function body and in the subscribe
closure. The subscribe
closure is not necessarily executed in sync or on the same thread as materializeResult
private final class AnonymousDisposable : DisposeBase, Cancelable { | ||
public typealias DisposeAction = () -> Void | ||
private final class AnonymousDisposable : DisposeBase, Cancelable, @unchecked Sendable { | ||
public typealias DisposeAction = @Sendable () -> Void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this needs to be @Sendable
, the parameter can simply be marked as sending
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented on the PR:
From what I understand, sending is to be used only when transferring between isolated contexts. Most classes in RxSwift are not isolated, and shouldn't be and thus sending keyword is not ideal. Also sending can not be applied on closure property, even though they may be called from multiple threads and therefore should be sendable.
@@ -36,7 +36,7 @@ | |||
/// next(Banana) | |||
/// next(Blueberry) | |||
/// ``` | |||
public struct GroupedObservable<Key, Element> : ObservableType { | |||
public struct GroupedObservable<Key, Element> : ObservableType, @unchecked Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this unchecked? If this is going to be marker as Sendable
, then it should be conditionally Sendable
based on it's parameters:
extension GroupedObservable: Sendable where Key: Sendable, Element: Sendable {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would argue that Key
and Element
should be Sendable
, but it's be too large of a change for now.
public struct GroupedObservable<Key: Sendable, Element: Sendable> : ObservableType, Sendable {
@@ -6,7 +6,7 @@ | |||
// Copyright © 2015 Krunoslav Zaher. All rights reserved. | |||
// | |||
|
|||
struct SubscriptionDisposable<T: SynchronizedUnsubscribeType> : Disposable { | |||
struct SubscriptionDisposable<T: SynchronizedUnsubscribeType> : Disposable, @unchecked Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to make this @unchecked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes because DisposeKey
and SynchronizedUnsubscribeType
are not currently marked as Sendable.
@@ -7,15 +7,15 @@ | |||
// | |||
|
|||
/// Represents an object that immediately schedules units of work. | |||
public protocol ImmediateSchedulerType { | |||
public protocol ImmediateSchedulerType: Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to be a requirement of the protocol?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All Schedulers in RxSwift are threadsafe and thus can be safely marked as Sendable.
As an example, when creating an observable with an .observe(on: scheduler)
from inside a sendable closure, the compiler complains that scheduler
is not sendable.
@@ -12,7 +12,7 @@ | |||
|
|||
public typealias RxObservable<Element> = RxSwift.Observable<Element> | |||
|
|||
public class Observable<Element> : ObservableType { | |||
public class Observable<Element> : ObservableType, @unchecked Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this needs to be @unchecked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it needs to be @unchecked
because class Observable
is not final
@@ -7,7 +7,7 @@ | |||
// | |||
|
|||
/// Type that can be converted to observable sequence (`Observable<Element>`). | |||
public protocol ObservableConvertibleType { | |||
public protocol ObservableConvertibleType: Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to be a requirement of the protocol?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All classes that conform to ObservableConvertibleType
should be threadsafe and thus should be marked as Sendable.
@@ -130,8 +130,8 @@ extension ObservableType { | |||
import Foundation | |||
|
|||
extension Hooks { | |||
public typealias DefaultErrorHandler = (_ subscriptionCallStack: [String], _ error: Error) -> Void | |||
public typealias CustomCaptureSubscriptionCallstack = () -> [String] | |||
public typealias DefaultErrorHandler = @Sendable (_ subscriptionCallStack: [String], _ error: Error) -> Void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These don;'t need to be @Sendable
, but their use as parameters should be marked as sending
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defining
public static var defaultErrorHandler: sending DefaultErrorHandler {
gives the error 'sending' may only be used on parameters and results
so it can't be added here.
assigning defaultErrorHandler
from a main actor isolated context, without marking the closure as Sendable, and calling it from a non-main thread, would cause a runtime crash in Swift 6.
@MainActor
func test() {
Hooks.defaultErrorHandler = { _, _ in }
}
test()
DispatchQueue.global().async(execute: { () in Hooks.defaultErrorHandler([], RxError.unknown) })
So it is safer to have the DefaultErrorHandler closure as sendable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Meant to mark my previous comment as Request Changes)
From what I understand,
Most RxSwift classes should be Sendable. Let's take Ideally would be to use |
Looking at what happens with Combine my current instinct is consumers of RxSwift should Happy for other thoughts on this, but wondering if it's a futile attempt to even try going down this path. |
@freak4pc I would agree with you, but the reason I added this changes is that in Swift 6 the code below causes a runtime crash on @preconcurrency import RxSwift
@MainActor
func test() -> any Disposable {
return Observable<Int>.just(1)
// any non main thread scheduler
.observe(on: RxSwift.SerialDispatchQueueScheduler(qos: .background))
.map({ $0 + 1 })
.subscribe()
} When the map closure is called, it expects to be executed on main thread because it is inside a function marked with |
…it properties are called on the main thread in DelegateProxyType
@freak4pc I'm not sure it's futile, but I am not sure it's worthwhile trying to bring RxSwift to the Swift 6. Presumably I think it does make sense to introduce more annotations to make it easier for those to adopt without having to entirely opt-out with That being said, I'm not sure this PR in particular helps to achieve improving a migration story. I am curious how @AndreiArdelean1 is running into issues. |
All Given you desire to make many parameters |
I agree that they can be |
The problem with Here is an example: let subject = PublishSubject<Int>()
subject
.map({ $0 + 1 })
.subscribe()
.disposed(by: disposeBag)
DispatchQueue.global().async(execute: { subject.onNext(0) })
DispatchQueue.global().async(execute: { subject.onNext(1) }) In this case, the closure of the let mappedElement = try self.transform(element)
self.forwardOn(.next(mappedElement)) There is no lock around the Ideally even the
On top of the above example, RxSwift shouldn't add this constraint. If devs want to still use non-Sendable values inside closures, they can do so by either using |
Mark Operator Closures as @sendable to Prevent Crashes in Swift 6 Isolated Contexts #2638
In Swift 6, closures created in an isolated context automatically inherit the isolation of that context, unless they are marked as @sendable. Because of this, creating an operation like map, flatMap, distinctUntilChanged, etc. from an isolated context, but calling it from a different thread, causes the call to crash.