-
Notifications
You must be signed in to change notification settings - Fork 40
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
Adopt Sendable throughout API to support -strict-concurrency=complete #22
Conversation
@MahdiBM Is this PR ready for us to review? |
@swift-server-bot add to allowlist |
@czechboy0 yes 🙂 |
@MahdiBM Is this agains an option we could enable in Package.swift for the whole library, to prepare us for Swift 6, similar to apple/swift-openapi-generator#99? If we're making these changes, I'd like to make sure we don't regress the stricter concurrency mode in the future. |
So adding @sendable can only help for better concurrency checks. Adding @preconcurrency though, we won't be able to remove it without it counting as a Breaking Change, which means it needs a major release, and that'll probably be when Swift 6 comes out, because Swift 6 comes with the default settings of basically @preconcurrency is useful to ease people's migration to use concurrency stuff. It basically turns Sendable errors of declarations to just warnings. I personally don't need it as my projects are for the most part already in compliance with at least |
That is not true. You can free add and remove that attribute whenever required. Especially if you version guard it. Overall, I agree though we should try to be strict concurrency warning free in this library. @czechboy0 We are currently in the process of turning on strict concurrency checking in our CI in all our NIO libraries so feel free to add it here on the CI as well. You can simple pass this flag |
If we can go all the way to |
is it not true that removing |
AFAIK, this will only add warnings and not errors. So it is safe to remove this. However, I don't think we should or need these attributes here in the first place |
Yeah, for this PR i'll be removing the @preconcurrencys. I'm just trying to see what i'm wrong about. Imagine having this. It'll show a warning: struct NotSendable {
var a = ""
}
@preconcurrency
public func something(_: @Sendable () -> Void) { }
func another() async throws {
var notSendable = NotSendable()
something {
notSendable.a = ""
/// ^~~~ Warning: Mutation of captured var 'notSendable' in concurrently-executing code; this is an error in Swift 6
}
} If you remove the public func something(_: @Sendable () -> Void) { }
func another() async throws {
var notSendable = NotSendable()
something {
notSendable.a = ""
/// ^~~~ Error: Mutation of captured var 'notSendable' in concurrently-executing code
}
} So if we publish the package with functions having @preconcurrency, then someone writes some code that has warning like above, removing @preconcurrency will make it so those warnings turn into errors ... I think that qualifies as a "breaking change"? |
One thing to note is that we'll need to add the flag only in CI files like you requested. We can add it as That being said, I'll check to see if we can use that flag without too many changes. |
The usage of @czechboy0 maybe it is enough if we start using the new experimental feature flag in 5.9 and above only. So we don't have to pass flags in the CI scripts. In 5.9 and above we can just declare them in the |
@czechboy0 This should be ready.
Honestly that might have been smart to have, but i just checked, and you can't silence the errors from functions of another module, using @preconcurrency on the the import. It just acts as if your @preconcurrency on the import is unrelated and shows a warning to remove @preconcurrency.
I think it's intended as we figured in the ongoing work of @0xTim to make Vapor stuff more Sendable. |
Sure for existing code this might be the case and you have to handle this. However, this package has not yet hit a 1.0.0 and we can safely introduce this without adding |
Chiming in to add as a new package there should be no |
Yeah you're right in that sense, i've already removed all the @preconcurrencys 🙂 |
That said - we do guarantee stability between minor versions until we hit 1.0, so this PR would be considered breaking the API. That's okay, we just want to mark it as such and make sure it's rolled out in a new minor version, instead of a patch. |
@MahdiBM, please also add the CLI flag for the strict concurrency checking to the CI script (the docker-compose file), to see that it's all passing before we start reviewing the PR again, thanks! 🙏 |
@MahdiBM please lmk once the CI is green again and I'll do one more review 🙏 |
@czechboy0 the PR (expectedly) contains some breaking changes so i can't really make the CI green 😅: 15 breaking changes detected in OpenAPIRuntime:
💔 API breakage: constructor OpenAPIValueContainer.init(unvalidatedValue:) has parameter 0 type change from Any? to (Swift.Sendable)?
💔 API breakage: var ClientError.operationInput has declared type change from Any to Swift.Sendable
💔 API breakage: accessor ClientError.operationInput.Get() has return type change from Any to Swift.Sendable
💔 API breakage: accessor ClientError.operationInput.Set() has parameter 0 type change from Any to Swift.Sendable
💔 API breakage: constructor ClientError.init(operationID:operationInput:request:baseURL:response:underlyingError:) has parameter 1 type change from Any to Swift.Sendable
💔 API breakage: var ServerError.operationInput has declared type change from Any? to (Swift.Sendable)?
💔 API breakage: accessor ServerError.operationInput.Get() has return type change from Any? to (Swift.Sendable)?
💔 API breakage: accessor ServerError.operationInput.Set() has parameter 0 type change from Any? to (Swift.Sendable)?
💔 API breakage: var ServerError.operationOutput has declared type change from Any? to (Swift.Sendable)?
💔 API breakage: accessor ServerError.operationOutput.Get() has return type change from Any? to (Swift.Sendable)?
💔 API breakage: accessor ServerError.operationOutput.Set() has parameter 0 type change from Any? to (Swift.Sendable)?
💔 API breakage: constructor ServerError.init(operationID:request:requestMetadata:operationInput:operationOutput:underlyingError:) has parameter 3 type change from Any? to (Swift.Sendable)?
💔 API breakage: constructor ServerError.init(operationID:request:requestMetadata:operationInput:operationOutput:underlyingError:) has parameter 4 type change from Any? to (Swift.Sendable)?
💔 API breakage: func UniversalClient.send(input:forOperation:serializer:deserializer:) has generic signature change from <OperationInput, OperationOutput> to <OperationInput, OperationOutput where OperationInput : Swift.Sendable, OperationOutput : Swift.Sendable>
💔 API breakage: func UniversalServer.handle(request:with:forOperation:using:deserializer:serializer:) has generic signature change from <APIHandler, OperationInput, OperationOutput where APIHandler : Swift.Sendable> to <APIHandler, OperationInput, OperationOutput where APIHandler : Swift.Sendable, OperationInput : Swift.Sendable, OperationOutput : Swift.Sendable> They are all complaining about Sendable. |
Ah ofc, I didn't realize. Let me talk with @simonjbeaumont tomorrow about how we'll land and release this breaking change. |
I already labelled this PR as The current state of CI is misleading. The integration test for this PR is actually passing. It's being run in soundness right now because this PR hasn't been rebased since #23 was merged. This means the Github status for this PR is currently showing a pipeline for the integration test that is failing because the Compose "service" the pipeline is trying to run doesn't exist on this branch. I'll update this branch, which should then at least make the Github checks pass for this PR. However, the integration test does produce warnings (because there's a conversion of a non-sendable closure to a sendable one), which I imagine would be an error for an adopter if they were running with a stricter compiler flag. |
Let me investigate this. But for now, the good news is there is no stricter concurrency flag and i doubt there will be one. If you want to cut a release and do release notes, i can also go the existential-any PR first, so you can do them together if it matters (probably/hopefully that won't need as much time/conversation as this, as it's just some |
So considering the fact that i don't think the integration-tests test-package is public, and also my lack of knowledge about the generator package, i couldn't come to a definite conclusion. But:
|
Thanks @MahdiBM. I think you've managed to glean what's going on. Let me summarise for you. We have an In CI we clone this package and use So, as expected, for this PR we see the following in the CI logs:
Depending on the PR this information might be gating or just informational, just like the In this case it just serves to confirm that we need to bump the minor version (it'd be major if we were 1.0, ofc) and folks can expect a potentially breaking change if they choose to upgrade. And hopefully the upgrade will be an explicit action on the adopters part because they'll hopefully be using Specifically, on your notes:
Correct. I guess the release flow here would need to be:
Right, that's what I meant. I think we should probably add that to the integration test when we can. Is this something you wanted to chase down as you shepherd this feature through? |
I think that means updating the Dockerfile with the strict concurrency flag? |
Right. My expectation is, if we were to do that in this PR, we'd see it fail. Might be worth doing to hammer home the nature of this change (at least we'd see that pipeline and the API breakage pipeline agree). |
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.
lgtm
hmm adding the flag did not make the tests fail which is weird 🤔 But the good news is now that i'm testing the openapi-generator with this branch of openapi-runtime, i can at least see the warnings. |
I wonder if this a diagnosis issue by Swift: using: APIHandler.updatePet,
/// Warning: Converting non-sendable function value to '@Sendable (APIHandler) -> ((Operations.updatePet.Input) async throws -> Operations.updatePet.Output)' may introduce data races but: using: { APIHandler.updatePet($0) },
/// No warnings! Both with |
After more investigations, i think it's either a Swift diagnosis error that there are those warnings, or it's a correct warning that we can't do much about anyway, other than working around it like in the comment above. The function is declared in APIProtocol: public protocol APIProtocol: Sendable {
...
func updatePet(_ input: Operations.updatePet.Input) async throws -> Operations.updatePet.Output
} Then UniversalServer uses the function like so: using: APIHandler.updatePet, And there is nothing you can do to make the warning go away, without touching what is in UniversalServer. Even marking everything (functions, types, etc...) with using handlerMethod: @Sendable @escaping (APIHandler) -> ((OperationInput) async throws -> OperationOutput), |
I think this is a known bug in Sendable checking right now. Just to be sure, do you mind creating a small reproducer and filing an issue over in the Swift repository |
I found an existing issue for very similar behavior, it should be enough considering we now know there is an issue for it: swiftlang/swift#64388 |
Hmm i guess i'll file a new issue considering what we're seeing here and what is in that issue have some differences, although the warning is the same. |
Filed the issue. To reiterate and be clear, the warnings seem to always be/remain completely harmless, although we should still resolve them in openapi-generator after this PR is merged. |
Thanks @MahdiBM for pushing this over the line! Discussed this with the wider team and changes to sendable aren't currently considered breaking from a semver perspective. @FranzBusch can you confirm? |
Motivation
When using
-strict-concurrency=targeted
,ClientMiddleware
will emit warnings if an actor is conformed to it:The
intercept
function will emit the following warning with the current implementation:Repro package: https://github.com/MahdiBM/OAPIRClientWarningsRepro
You can change between this PR and the main branch by commenting out the dependency declarations i've already put in the
Package.swift
.Then observe there are no warnings with this PR, unlike with the main branch.
Modifications
Generally adopt Sendable throughout the whole API to support -strict-concurrency=complete.
For the most part, this means using @sendable for closures, and using
any Sendable
instead ofAny
.As an example for closures, currently we have:
With this PR we'll have:
Result
Safer Sendable code + being more ready for Swift 6 + no more warnings when using
-strict-concurrency=targeted
then conforming an actor toClientMiddleware
.Test Plan
Adopted all tests to the changes and added the strict-concurrency flag to CI.