Skip to content

Commit 031ffc0

Browse files
authored
Merge pull request #36359 from kavon/actor-lifetimes
2 parents 8d61861 + 11fa6b1 commit 031ffc0

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

stdlib/public/Concurrency/Actor.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -1094,8 +1094,15 @@ Job *DefaultActorImpl::claimNextJobOrGiveUp(bool actorIsOwned,
10941094
/// the current thread, and keep processing whatever actor we're
10951095
/// running when code returns back to us until we're not processing
10961096
/// any actors anymore.
1097+
///
1098+
/// \param currentActor is expected to be passed in as retained to ensure that
1099+
/// the actor lives for the duration of job execution.
1100+
/// Note that this may conflict with the retain/release
1101+
/// design in the DefaultActorImpl, but it does fix bugs!
10971102
static void processDefaultActor(DefaultActorImpl *currentActor,
10981103
RunningJobInfo runner) {
1104+
DefaultActorImpl *actor = currentActor;
1105+
10991106
// Register that we're processing a default actor in this frame.
11001107
ExecutorTrackingInfo trackingInfo;
11011108
auto activeTrackingInfo = trackingInfo.enterOrUpdate(
@@ -1142,6 +1149,8 @@ static void processDefaultActor(DefaultActorImpl *currentActor,
11421149
// If we still have an active actor, we should give it up.
11431150
if (currentActor)
11441151
currentActor->giveUpThread(runner);
1152+
1153+
swift_release(actor);
11451154
}
11461155

11471156
void ProcessInlineJob::process(Job *job, ExecutorRef _executor) {
@@ -1153,6 +1162,7 @@ void ProcessInlineJob::process(Job *job, ExecutorRef _executor) {
11531162
auto runner = RunningJobInfo::forInline(targetPriority);
11541163

11551164
// FIXME: force tail call
1165+
swift_retain(actor);
11561166
return processDefaultActor(actor, runner);
11571167
}
11581168

@@ -1168,6 +1178,7 @@ void ProcessOutOfLineJob::process(Job *job, ExecutorRef _executor) {
11681178
delete self;
11691179

11701180
// FIXME: force tail call
1181+
swift_retain(actor);
11711182
return processDefaultActor(actor, runner);
11721183
}
11731184

@@ -1179,6 +1190,7 @@ void ProcessOverrideJob::process(Job *job, ExecutorRef _executor) {
11791190
auto runner = RunningJobInfo::forOverride(self);
11801191

11811192
// FIXME: force tail call
1193+
swift_retain(actor);
11821194
return processDefaultActor(actor, runner);
11831195
}
11841196

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: %target-run-simple-swift(-parse-as-library -Xfrontend -enable-experimental-concurrency %import-libdispatch) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
// FIXME: this should pass! from rdar://73266050
8+
// XFAIL: *
9+
10+
// doesn't matter that it's bool identity function or not
11+
func boolIdentityFn(_ x : Bool) -> Bool { return x }
12+
13+
actor FirstActor {
14+
func startTest() { // whether startTest is async or sync doesn't matter
15+
16+
// do not remove this call or if-statement.
17+
if boolIdentityFn(true) {}
18+
19+
}
20+
21+
deinit() {
22+
// CHECK: called deinit
23+
print("called deinit")
24+
}
25+
}
26+
27+
@main struct RunIt {
28+
static func main() async {
29+
let actor = FirstActor()
30+
await actor.startTest() // do not remove this call
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: %target-run-simple-swift(-parse-as-library -Xfrontend -enable-experimental-concurrency %import-libdispatch) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
// this needs to match with the check count below.
8+
let NUM_TASKS : Int = 100
9+
10+
final class Capture : ConcurrentValue {
11+
func doSomething() { }
12+
deinit {
13+
// CHECK-COUNT-100: deinit was called!
14+
print("deinit was called!")
15+
}
16+
}
17+
18+
@main
19+
struct App {
20+
static func main() async {
21+
var n = 0
22+
for _ in 1...NUM_TASKS {
23+
let c = Capture()
24+
let r = Task.runDetached {
25+
c.doSomething()
26+
}
27+
await r.get()
28+
n += 1
29+
}
30+
print("test complete")
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// RUN: %target-run-simple-swift(-parse-as-library -Xfrontend -enable-experimental-concurrency %import-libdispatch) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
// for sleep
8+
#if canImport(Darwin)
9+
import Darwin
10+
#elseif canImport(Glibc)
11+
import Glibc
12+
#endif
13+
14+
class Runner {
15+
func run() async {
16+
while !Task.isCancelled {
17+
sleep(1)
18+
}
19+
}
20+
}
21+
22+
actor Container {
23+
var generation = 0
24+
var runners = [Int : Task.Handle<Void, Never>]()
25+
26+
func build(_ n: Int) {
27+
for _ in 0..<n {
28+
let id = generation
29+
generation += 1
30+
let t = Task.runDetached { [weak self] in
31+
let r = Runner()
32+
await r.run()
33+
await self?.remove(id)
34+
}
35+
runners[id] = t
36+
}
37+
}
38+
39+
func cancelAll() {
40+
var count = 0
41+
for (_, v) in runners {
42+
v.cancel()
43+
count += 1
44+
}
45+
print("Cancelled \(count) runners.")
46+
}
47+
48+
deinit {
49+
print("deinit Container with \(runners.count) runners")
50+
}
51+
52+
func remove(_ id: Int) {
53+
runners.removeValue(forKey: id)
54+
}
55+
}
56+
57+
// CHECK: starting
58+
// CHECK: Cancelled 5 runners.
59+
60+
// FIXME: this doesn't work until we have https://github.com/apple/swift/pull/36298
61+
// COM: deinit Container with {{[0-9]+}} runners
62+
63+
@main struct RunIt {
64+
static func startTest() async {
65+
let c = Container()
66+
await c.build(5)
67+
sleep(5)
68+
await c.cancelAll()
69+
}
70+
71+
static func main() async {
72+
print("starting")
73+
await RunIt.startTest()
74+
sleep(5)
75+
}
76+
}

0 commit comments

Comments
 (0)