swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.6k stars 10.37k forks source link

[SR-12479] Inconsistent behavior when using DispatchQueue.sync(flags:execute:) in property getter. #54919

Open swift-ci opened 4 years ago

swift-ci commented 4 years ago
Previous ID SR-12479
Radar rdar://problem/62201587
Original Reporter kuanfajardo (JIRA User)
Type Bug
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: a00f1c1f960a76d1c8611f9999e59b4e

Issue Description:

When performing a `sync(flags:execute: )` operation in a property getter, I get the following error __only if I actually pass in any flags:

Thread 1: closure argument passed as @noescape to Objective-C has escaped: file , line 56, column 56

@propertyWrapper
public final class GCDSynchronized<Value: Any> {
  private var value: Value
  private let queue: DispatchQueue

  /* Various init methods */

  public var wrappedValue: Value {
    get {
->    queue.sync(flags: ***) { self.value }  // If *** is not [], crash!
    }

    /* Setter */
  }
}

It works just fine if I pass an empty array to the `flags` param, which to me is dumbfounding since it's the same method either way. Only thing I could think of is that the implementation of `sync(flags:execute)` calls `sync(execute: )` if `flags.isEmpty`, in which case my beef is with the former not handling my escaping closure 🙁

P.S. The line numbers from the error (56, 56) do not match my line numbers (which is 140).

beccadax commented 4 years ago

@swift-ci create

za-creature commented 1 year ago

This is still an issue with Xcode 14.3 (14E222b), though it might be restricted to .assignCurrentContext:

import Foundation

let queue = DispatchQueue(label: "I'm a train choo choo", attributes: .concurrent)

queue.sync(flags: .barrier) { print("eenie") }
queue.async(flags: .barrier) { print("meenie") }
queue.async(flags: .assignCurrentContext) { print("minie") }
queue.sync(flags: .assignCurrentContext) { print("no soup for you") }

Confirmed on both x86 and M1 with both serial and concurrent queues: closure argument passed as @noescape to Objective-C has escaped: file , line 61, column 57

For what it's worth, my behavioral expectations for queue.sync(flags: .assignCurrentContext, execute: callback) are:

  1. add intention to queue
  2. block current thread until all previously enqueued barriers are lifted
  3. resume thread to run return try callback(), potentially concurrently with other non-barrier threads (depending on queue attributes and the presence of the .barrier flag)
  4. job done, remove from queue and off to the pub