Skip to content
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

[SR-8955] Can't use DispatchWorkItem with a non-escaping block #51460

Open
drewcrawford opened this issue Oct 9, 2018 · 5 comments
Open

[SR-8955] Can't use DispatchWorkItem with a non-escaping block #51460

drewcrawford opened this issue Oct 9, 2018 · 5 comments
Labels
access control Feature → modifiers: Access control and access levels bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself regression swift 4.2

Comments

@drewcrawford
Copy link
Contributor

Previous ID SR-8955
Radar rdar://problem/45226617
Original Reporter @drewcrawford
Type Bug
Environment

Xcode Version 10.0 (10A255)

Puzzlingly, only reproduces on certain devices

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, 4.2Regression
Assignee None
Priority Medium

md5: 15e0cca8d472b4dfe08f7a378260204f

Issue Description:

I am currently trying to do something like this (swift 4.1)

///api contract: block must be non-escaping here
func foo<T>(block: () throws -> T)
    var result: Result<T>! = nil
    withoutActuallyEscaping(block) { (block) -> () in
        let item = DispatchWorkItem(qos: qos, flags: []) {
            do {
                result = .t(try block())
            }
            catch {
                result = .error(error)
            }
        }
        queue.sync(execute: item)
    } 

This block should not escape in practice because it is sent to the queue with sync however swift's local inference rules are not powerful enough to understand my argument. withoutActuallyEscaping used to solve this, however on swift 4.2 this produces at runtime

closure argument was escaped in withoutActuallyEscaping block

I wondered how you guys solve this, and I checked and found there is an internal I-solmently-swear-I'm-up-to-no-good constructor:

// Used by DispatchQueue.synchronously<T> to provide a path through
    // dispatch_block_t, as we know the lifetime of the block in question.
    internal init(flags: DispatchWorkItemFlags = [], noescapeBlock: () -> Void) {
        _block = _swift_dispatch_block_create_noescape(
            __dispatch_block_flags_t(rawValue: flags.rawValue), noescapeBlock)
    }

IMO either this constructor should be public API, or there should be some language feature to disable this assert, or the assert should be smart enough to realize this doesn't actually escape (or am I the idiot and it escapes here somehow after all?)

@AnnaZaks
Copy link
Mannequin

AnnaZaks mannequin commented Oct 12, 2018

cc @jckarter,aschwaighofer@apple.com (JIRA User)

@aschwaighofer
Copy link
Member

@swift-ci create

@aschwaighofer
Copy link
Member

Repo

$  cat SR8955.swift
import Foundation
enum Result<T> {
  case error
  case t(T)
}

func foo<T>(block: () throws -> T) {
  let q = DispatchQueue(label: "Test")
  var result: Result<T>! = nil
  withoutActuallyEscaping(block) { (block) -> () in
      let item = DispatchWorkItem(qos: .unspecified, flags: []) {
          do {
              result = .t(try block())
          }
          catch {
              result = .error
          }
      }
      q.sync(execute: item)
  }
}


foo(block: { return 10})

@aschwaighofer
Copy link
Member

Our implementation of the DispatchWorkItem constructor temporarily stores the block in the autorelease pool. The autorelease pool optimization fails the first time around on some architectures so the block really ends up in the autorelease pool and therefore leaks out of the withoutActuallyEscaping block.

This can be worked around by inserting a autoreleasepool scope inside of the withoutActuallyEscaping closure.

import Foundation
enum Result<T> {
  case error
  case t(T)
}

func foo<T>(block: () throws -> T) {
  let q = DispatchQueue(label: "Test")
  var result: Result<T>! = nil
  withoutActuallyEscaping(block) { (block) -> () in
    autoreleasepool {
        let item = DispatchWorkItem(qos: .unspecified, flags: []) {
            do {
                result = .t(try block())
                print("executed block")
            }
            catch {
                result = .error
            }
        }
        q.sync(execute: item)
    }
  }
}


foo(block: { return 10})

@aschwaighofer
Copy link
Member

Fix #19859

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@AnthonyLatsis AnthonyLatsis added regression swift 4.2 access control Feature → modifiers: Access control and access levels and removed 4.2 regression labels Nov 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
access control Feature → modifiers: Access control and access levels bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself regression swift 4.2
Projects
None yet
Development

No branches or pull requests

3 participants