Skip to content

New concurrency warnings when building with Swift 5.10 #1560

@allevato

Description

@allevato

As a consequence of SE-0412: Strict concurrency for global variables, we're getting new concurrency warnings when building swift-protobuf. They can be narrowed down to two places:

Default instances of storage classes:

swift-protobuf/Sources/SwiftProtobuf/AnyMessageStorage.swift:144:14: error: static property 'defaultInstance'
is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this
is an error in Swift 6
  static let defaultInstance = AnyMessageStorage()
             ^

swift-protobuf/Sources/SwiftProtobuf/descriptor.pb.swift:3111:16: error: static property 'defaultInstance' is
not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this is
an error in Swift 6
    static let defaultInstance = _StorageClass()
             ^

The _NameMap of each generated message:

swift-protobuf/Sources/SwiftProtobuf/any.pb.swift:195:21: error: static property '_protobuf_nameMap' is not
concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this is an
error in Swift 6
  public static let _protobuf_nameMap: _NameMap = [
                    ^

SE-0412 added the requirements that:

Under strict concurrency checking, require every global variable to either be isolated to a global actor or be both:

  1. immutable
  2. of Sendable type

Neither the storage classes nor _NameMap are Sendable. I need some more eyes who are better versed with Swift concurrency than I am 🙂

Storage classes

For the storage classes, we could just drop the defaultInstance property entirely and just allocate a new empty instance. However, it is a nice optimization to defer allocation of storage until the message is actually mutated. It's not super important if you're creating a new message and then immediately parsing or mutating it, but anywhere that we return an empty message as a default value of an unset message-typed field, it's very nice to not have to allocate storage if the user is only going to read from it.

Can we just mark the storage classes as Sendable on @unchecked Sendable since the messages themselves already conform to that, and the storage types can't be accessed from outside the message?

_NameMap

_NameMap looked more challenging because of the string interning logic and storage of Unsafe*Pointers, but upon further reflection, a _NameMap can never be mutated after its been initialized, so I think we could just declare _NameMap to be @unchecked Sendable and call it a day, right?

As a follow-up, now that Swift Strings are UTF-8 backed, we could probably revisit the UTF-8 buffer interning approach and just store Strings directly. The UTF-8 buffer interning dates back to 2017, so it was when Swift native strings weren't guaranteed to be UTF-8 backed, which I believe was the original motivation for the buffer approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions