Skip to content

Runtime crash when passing and executing non-Sendable closure to a class from swiftLanguageMode v5 enabled package #84483

@akbashev

Description

@akbashev

Description

  • Simple main.swift (this is important) app with swift-tools-version: 6.0 package
  • Having a old target without any Sendable mark yet with .swiftLanguageMode(.v5) enabled
  • Running at top level in main.swift file

App will run but crash when trying to execute this closure with an error Task 1: EXC_BREAKPOINT (code=1, subcode=0x1004cbbc4)

Reproduction

Package.swift

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
  name: "App",
  platforms: [.macOS(.v15)],
  targets: [
    .target(
      name: "System",
      swiftSettings: [.swiftLanguageMode(.v5)]
    ),
    .executableTarget(
      name: "App",
      dependencies: [ "System" ]
    ),
  ]
)

System/System.file

public class System {
  
  public struct Settings { public var name: String = "" }
  
  public let settings: Settings

  public convenience init(configureWith configure: (inout Settings) -> () = { _ in () }) async {
    var settings = Settings()
    configure(&settings) // runtime crash here with `Task 1: EXC_BREAKPOINT (code=1, subcode=0x1004cbbc4)`
    self.init(settings: settings)
  }
  
  public init(settings: Settings) {
    self.settings = settings
  }
  
  public func run() async throws {
    try await Task.sleep(for: .seconds(3))
  }
}

App/main.swift

import System

let system = await System {
  $0.name = "Demo"
}

print(system.settings.name)
try await system.run()
print("Done")

Stack dump

thread #1, queue = 'com.apple.main-thread'
    frame #0: 0x000000018f8b1c34 libsystem_kernel.dylib`mach_msg2_trap + 8
    frame #1: 0x000000018f8c4028 libsystem_kernel.dylib`mach_msg2_internal + 76
    frame #2: 0x000000018f8ba98c libsystem_kernel.dylib`mach_msg_overwrite + 484
    frame #3: 0x000000018f8b1fb4 libsystem_kernel.dylib`mach_msg + 24
    frame #4: 0x000000018f993c80 CoreFoundation`__CFRunLoopServiceMachPort + 160
    frame #5: 0x000000018f9925d8 CoreFoundation`__CFRunLoopRun + 1188
    frame #6: 0x000000018fa50898 CoreFoundation`_CFRunLoopRunSpecificWithOptions + 532
    frame #7: 0x000000018f9e9a44 CoreFoundation`CFRunLoopRun + 64
    frame #8: 0x0000000287fd7ee8 libswift_Concurrency.dylib`CFMainExecutor.run() + 48
    frame #9: 0x0000000287fd7ba0 libswift_Concurrency.dylib`protocol witness for RunLoopExecutor.run() in conformance DispatchMainExecutor + 48
    frame #10: 0x0000000287fd7f94 libswift_Concurrency.dylib`swift_task_asyncMainDrainQueueImpl + 108
    frame #11: 0x0000000287ff8170 libswift_Concurrency.dylib`swift_task_asyncMainDrainQueue + 92
    frame #12: 0x0000000100000c34 App`main at main.swift:0
    frame #13: 0x000000018f535d54 dyld`start + 7184
* thread #3, name = 'Task 1', queue = 'com.apple.root.default-qos.cooperative', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1004cbbc4)
    frame #0: 0x00000001004cbbc4 libdispatch.dylib`_dispatch_assert_queue_fail + 120
    frame #1: 0x0000000100506d30 libdispatch.dylib`dispatch_assert_queue$V2.cold.1 + 116
    frame #2: 0x00000001004cbb48 libdispatch.dylib`dispatch_assert_queue + 108
    frame #3: 0x0000000287f900a4 libswift_Concurrency.dylib`_swift_task_checkIsolatedSwift + 48
    frame #4: 0x0000000287ff0df4 libswift_Concurrency.dylib`swift_task_isCurrentExecutorWithFlagsImpl(swift::SerialExecutorRef, swift::swift_task_is_current_executor_flag) + 356
    frame #5: 0x0000000100001344 App`closure #1($0=(name = "")) -> () in App at main.swift:0
  * frame #6: 0x00000001000016e0 App`System.__allocating_init(configure=0x00000001000012f0 App`closure #1 (inout System.System.Settings) -> () in App at main.swift:3) at System.swift:9:5
    frame #7: 0x0000000100000cec App`async_MainTQ0_ at main.swift:3:20

Expected behavior

If you change main.swift to App.swift file, like:

import System

@main
struct App {
  
  static func main() async throws {
    let system = await System { // Sending value of non-Sendable type '(inout System.Settings) -> ()' risks causing data races
      $0.name = "Demo"
    }
    
    print(system.settings.name)
    try await system.run()
    print("Done")
  }
}

Compiler will fire an error Sending value of non-Sendable type '(inout System.Settings) -> ()' risks causing data races.
Guess should be same when compiled at top level in main.swift.

Environment

macOS 26, Xcode 26.0.1, Swift 6.2 (6.2.0.19.9)

Additional information

I know this an edge case which can be fixed by marking closure as sending or Sendable, just sometimes you don't have this option with 3rd party frameworks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.crashBug: A crash, i.e., an abnormal termination of softwaretriage neededThis issue needs more specific labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions