-
-
Notifications
You must be signed in to change notification settings - Fork 369
Swift 6 #318
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
Swift 6 #318
Conversation
|
Thank you for the PR!! This is great work. I will review Thanks especially for the test runner cleanup. That was a pain point in maintaining this
This is done for performance reasons to avoid copying data when it can be avoided. If this adds friction, please add both [UInt8] and ArraySlice, and avoid using [UInt8] when ArraySlice will suffice for internally-used methods. In fact I would like to change more [UInt8] usage over to ArraySlice where possible, though it is more challenging to work with so it should probably only be for lib-internal use |
|
Reverted some changes to keep using ArraySlice 👌 |
|
@makoni just one more question for you above re: MainActor... thanks again |
|
@makoni tests are failing due to
(should be an easy fix, I can help later on...) |
|
@makoni thank you for the hard work on these updates I see there are 2 failures on the Ubuntu build: I'm not sure at the moment what's causing this It would also be great to retain the test suite on Swift 5.9 for backwards compatibility - many (including myself) still haven't upgraded to Swift 6. But maybe that doesn't matter since the individual module can be built on Swift 6 and used in a Swift 5 codebase I guess (haven't investigated how this works yet) |
|
It looks like
Once you set |
|
@makoni thanks for your hard work! looks good now |
|
Not easy to solve this. One way would be to make all properties Also relates to #271. |
|
@DarkDust Thanks for your comments. I haven't approached this problem yet but starting with locks sounds sensible to achieve safety first if it's not a huge task... Do you have a sense of what would be slowed? Would it just be concurrent access that's slowed or would synchronous use also become much slower? I don't personally mutate SwiftSoup across threads (afaik) so I wouldn't care as long as single-thread use remains fast I've barely begun transitioning to Swift 6 myself so I don't have a lot of expertise here. Appreciate any guidance and contribution |
|
You'd have to protect/wrap all writes and reads to mutable properties, so single-thread use would suffer, unfortunately. I'd like to play with making |
|
@DarkDust thanks for clarifying. Personally I continue to invest in this library only because performance is most important to me - if I wasn't able to optimize it, I would have had to abandon it and move to wrapping a non-Swift solution. So if correctness required a substantial perf regression, I'd vote for breaking API with a "v3" release to move to immutability, if a performant approach is possible. Unfortunately I don't have the time or apparent need to do this work myself but I would be glad to assist with code review and releasing, if you are interested in contributing it. edit: I'm also open to making this API non-sendable, or adding some kind of immutable snapshot API to create copies for sendability... |
|
I don't think there's a good way to make the main classes truly Frame challenge: why should One obvious way to make the nodes sendable are locks. Using them correctly so that operations are atomic and do not deadlock is not trivial but can be done without any (breaking) API changes, I think. The other way is make Another route might be a mix: make as much immutable as possible, use locks for the rest. As far as I can see, all routes would degrade performance, and all require a lot of work. So maybe the better solution is to ditch |
|
I support removing |
|
The reason for adding Sendable conformance was to make the compiler happy. In my experience, Swift 6’s concurrency model doesn’t like inheritance and requires the use of One of actor MyActor {
var myString = ""
}
let myActor = MyActor()
// in some context the Swift compiler would not let you change var directly
myActor.myString = "foo"Instead you would have to do something like this: actor MyActor {
var myString = ""
func updateMyString(_ val: String) async {
myString = val
}
}
let myActor = MyActor()
// some context...
await myActor.updateMyString("foo")Actors give you thread safety but with a performance penalty when there’s context switching. But it feels like Apple is pushing that model forward (global actors, etc.). There's an interesting thread on Swift Forums about that: https://forums.swift.org/t/overhead-of-using-actors-at-scale/79466 Any option (making things immutable or switching to actors) feels like breaking the current API, but Swift evolution looks like one day you'll have to do it anyway. |
Just to be clear, I didn't mean to use actors in SwiftSoup, my suggestion is that if you require to use SwiftSoup in an asynchronous environment, you can wrap all necessary SwiftSoup operations in an actor of your own so other code only calls the actor for "high-level" operations. Leaving the current So, if we would remove
That applies to Once you allow inheritance, e.g. |
The changes that were made were just adding
Here's a simple example: final class Counter: Sendable {
internal init(value: Int) {
self.value = value
}
let value: Int
func increment() -> Self {
return .init(value: value + 1)
}
}
let counter = Counter(value: 0)
Task {
counter.increment()
}remove And same problem if you'll wrap it into an actor (removing actor MyActor {
func doSomething(_ block: @Sendable () -> Void) async {
block()
}
}
final class Counter: Sendable {
internal init(value: Int) {
self.value = value
}
let value: Int
@discardableResult func increment() -> Self {
return .init(value: value + 1)
}
}
let counter = Counter(value: 0)
let myActor = MyActor()
Task {
await myActor.doSomething {
counter.increment()
}
} |
|
In both of your examples, the Here's an example with SwiftSoup: (For this example, I've removed the You don't need an actor, actually: you can use your own To aid with passing non-sendable instances around there's also the |
Summary of Changes
Issue: #271
1. Swift Concurrency & Thread Safety
@unchecked Sendableconformance to many core classes and structs (e.g., Element, Document, Node, Tag, Entities, etc.), allowing safe usage in concurrent contexts and aligning with Swift's evolving concurrency model.TagandEntitiesto use thread-safe access (e.g., locks and registries), preventing race conditions and improving reliability in multi-threaded usage.2. Package & Platform Updates
Package.swiftfrom 5.9 to 6.0, enabling the latest Swift language features and tooling improvements.3. Code Quality & Consistency
letinstead ofvarfor immutability and performance.[UInt8]instead ofArraySlice<UInt8>, simplifying usage and consistency.@MainActorannotations to various methods and tests, clarifying main-thread affinity where necessary.4. Test Suite Improvements
LinuxMain.swiftfile and the manualallTestsarrays from all test classes, as these are no longer needed with modern SwiftPM/XCTest.