Non-sendable value type can be mutated concurrently

the code below it seems it can cause data race.

struct NonSendable {
    var value = 0
}

@available(*, unavailable)
extension NonSendable: Sendable {}

func test() {
  var ns = NonSendable()

  let closure: @MainActor @Sendable () -> () = { @MainActor in
    ns.value = 1
    print(ns.value)
  }

  Task {
    await closure()
  }

  ns.value = 0
}

Am I missing something? or is this a bug?

Indeed, it can. As such, you should be getting warning messages, even with -strict-concurrency=minimal:

Test.swift:12:5: warning: capture of 'ns' with non-sendable type 'NonSendable' in a `@Sendable` closure; this is an error in the Swift 6 language mode
 1 | struct NonSendable {
   |        `- note: consider making struct 'NonSendable' conform to the 'Sendable' protocol
 2 |     var value = 0
 3 | }
   :
10 |
11 |   let closure: @MainActor @Sendable () -> () = { @MainActor in
12 |     ns.value = 1
   |     `- warning: capture of 'ns' with non-sendable type 'NonSendable' in a `@Sendable` closure; this is an error in the Swift 6 language mode
13 |     print(ns.value)
14 |   }

Test.swift:12:5: warning: mutation of captured var 'ns' in concurrently-executing code; this is an error in the Swift 6 language mode
10 |
11 |   let closure: @MainActor @Sendable () -> () = { @MainActor in
12 |     ns.value = 1
   |     `- warning: mutation of captured var 'ns' in concurrently-executing code; this is an error in the Swift 6 language mode
13 |     print(ns.value)
14 |   }

Test.swift:13:11: warning: reference to captured var 'ns' in concurrently-executing code; this is an error in the Swift 6 language mode
11 |   let closure: @MainActor @Sendable () -> () = { @MainActor in
12 |     ns.value = 1
13 |     print(ns.value)
   |           `- warning: reference to captured var 'ns' in concurrently-executing code; this is an error in the Swift 6 language mode
14 |   }
15 |

If you aren't, can you share how you're compiling your code?

no warning. no error. complied it with no issue. So, nothing i can share.

what i can share is:
Swift version 6.0.1 (swift-6.0.1-RELEASE)
Target: aarch64-unknown-linux-gnu

Are you building with SPM? Did you compile this in a single file on its own?

I got the above by copying your code and pasting into a blank Test.swift, then running swiftc Test.swift.

$ swift --version
Swift version 6.0.3 (swift-6.0.3-RELEASE)
Target: x86_64-unknown-linux-gnu

build with spm.

swift run

This will compile in Swift 5 mode, producing warnings, while with Swift 6 mode there are no warnings or errors at all.

It looks like RBI issue paired with SE-0434. I've reproduced missing warnings in Swift 5 mode with RegionBasedIsolation and GlobalActorIsolatedTypesUsability features turned on.

1 Like

oh yes, i am using swift 6 mode.

this seems like a bug. it appears quite similar in structure to these existing reports (though uses a struct with an unavailable Sendable conformance rather than a class):

out of curiosity, does TSAN identify the race at runtime?

sorry, what is TSAN? I haven't run it multiple times to test if i can race.

my apologies for the jargon. TSAN is LLVM's 'thread sanitizer' runtime data race detection tool. you can enable it when building swift code via the -sanitize=thread flag. there are some docs on it in various places; here is one blog post i found (a bit dated, but maybe still useful).

Ah, yeah, that's why I asked about the invocation — presumably this is a newer SPM project that picked up Swift 6 by default rather than Swift 5

I'm not at a computer to test, but I wonder if with RBI, test picked up an implicit @MainActor being a top-level function in a main file. (Otherwise, this does seem like a bug.)

That was my thought as well, so I’ve tried example with it explicitly marked as nonisolated and got the same result.

2 Likes

I am actually on vacation. That would be great if anyone can test for me😂

the flag, -sanitize=thread, doesn't seem to work on swift 6.

I need to write:

let csettings: [CSetting]? = [
    .unsafeFlags([
        "-fsanitize=thread"
    ])
]

inside the package. but it doesn't seem to show anything.