ole
(Ole Begemann)
1
I noticed that the Task.detached method does not have the @_implicitSelfCapture attribute that Task.init has. (Stdlib source: https://github.com/apple/swift/blob/230dfcc30c6ed24ed84e1e1ffb371415053c1617/stdlib/public/Concurrency/Task.swift#L684-L687)
And sure enough, the compiler does force you to use explicit self in a Task.detached closure.
Is this intentional or an oversight? SE-304: Structured Concurrency says implicit self should also apply to Task.detached:
Closures passed to the Task initializer are not required to explicitly acknowledge capture of self with self..
…
Note: The same applies to the closure passed to Task.detached and TaskGroup.addTask.
ole
(Ole Begemann)
2
I found an earlier post that noted this: Why Task { } doesn’t need explicit use of self?:
I think detached task initially wasn't required to explicitly mention self but was later changed.
Questions
- Does detached task have a
self requirement because of how it interacts with actors (it doesn't inherit the context and could run on a different executor)?
- Is there a way to reliably detect data races? TSAN sometimes catches it and sometimes seems to miss data races. (not sure others face this issue or I am missing something)
Does anybody have a source where this was decided (if it was formally decided)?
ktoso
(Konrad 'ktoso' Malawski 🐟🏴☠️)
3
I could not find it through a quick search but I'm sure it was in one of the SE reviews of the initial concurrency Task primitives.
I believe the argument made was that "Task{} runs on the enclosing actor so causing weird retain cycles is less of an issue, you're on the actor and it must be alive already anyway", while Task.detached does not run on the enclosing actor so it's the same amount of problematic as random escaping closures. At least that's how i foggy remember it, I don't think I was very involved in this discussion. Maybe others remember or can find the discussion.
3 Likes
In practice I've run into retain cycles often just using Task {} because that's the first thing I reach for when setting up listening loops to AsyncStreams. Would it be better to use Task.detached{} instead? Still, I've been hesitant to do that because I wanted to capture the task locals and such. I find the implicit self to be frustrating and wish we didn't allow it.
ole
(Ole Begemann)
5
Hm. I still can’t find any discussion about this in the forums (and there aren't too many results if you search for something like "detached implicit self", so I feel it should be findable).
Personally, I don't find this argument convincing. My understanding was always that implicit self capture exists because most tasks (whether detached or not) are not expected to be long-running/infinite, so any retain cycles would resolve automatically on task completion. I don't see why long-running/infinite tasks should be more or less lilely to be detached (if anything, maybe more likely?).
Anyway, as far as I can tell, this is the pull request that removed the @_implicitSelfCapture for detached tasks: [Concurrency] Alternative Task API. by DougGregor · Pull Request #37495 · apple/swift · GitHub
This PR also introduced the Task { … } and Task.detached { … } APIs for the first time, so it's a big change and difficult to parse. But:
-
Before this change, func asyncDetached had the @_implicitSelfCapture attribute. Screenshot from the PR (linking to the exact place is difficult):
-
After the change, Task.detached (the replacement for asyncDetached) doesn’t have the attribute:
-
(By the way, asyncDetached still exists today in SourceCompatibilityShims.swift and still has the attribute).
-
(There also used to be func detach(priority:operation:), which did not have the attribute. IIRC, this was the name before asyncDetached, so maybe it's not relevant to this discussion?
-
I can find nothing in the commit messages or PR notes that removing the attribute was intentional.
This, plus the fact that SE-0304 says detached tasks should capture self implicitly, makes me wonder if it was indeed an oversight. cc @Douglas_Gregor
2 Likes