Skip to content

Commit 68ec2b1

Browse files
committed
[K/N][tests] Reproduce tryRetain deadlock
^KT-79384
1 parent 3eccb11 commit 68ec2b1

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// KIND: STANDALONE_NO_TR
2+
// DISABLE_NATIVE: isAppleTarget=false
3+
// forceNativeThreadStateForFunctions binary option is incompatible with caches.
4+
// IGNORE_NATIVE: cacheMode=STATIC_EVERYWHERE
5+
// IGNORE_NATIVE: cacheMode=STATIC_ONLY_DIST
6+
// IGNORE_NATIVE: cacheMode=STATIC_PER_FILE_EVERYWHERE
7+
// FREE_COMPILER_ARGS: -opt-in=kotlin.native.SymbolNameIsInternal -Xbinary=forceNativeThreadStateForFunctions=dereferenceWeak
8+
9+
// MODULE: cinterop
10+
// FILE: cinterop.def
11+
language = Objective-C
12+
headers = cinterop.h
13+
14+
// FILE: cinterop.h
15+
#import <Foundation/Foundation.h>
16+
17+
#ifdef __cplusplus
18+
extern "C" {
19+
#endif
20+
21+
void prepare(id objTemplate, void (^threadInit)());
22+
void checkpoint(void);
23+
24+
#ifdef __cplusplus
25+
}
26+
#endif
27+
28+
// FILE: cinterop.mm
29+
#import "cinterop.h"
30+
31+
#include <chrono>
32+
#include <cstdint>
33+
#include <iostream>
34+
35+
namespace {
36+
37+
constexpr int OBJECT_COUNT = 1000;
38+
constexpr int RUN_LOOP_COUNT = 10;
39+
40+
id objs[OBJECT_COUNT];
41+
__weak id weakObjs[OBJECT_COUNT];
42+
43+
constexpr auto maxCheckpointWaitTime = std::chrono::seconds(10);
44+
constexpr auto maxTestDuration = std::chrono::seconds(30);
45+
46+
std::chrono::steady_clock::time_point startedAt;
47+
std::atomic<std::chrono::steady_clock::time_point> checkpointAt;
48+
49+
std::atomic<uint64_t> sink = 0;
50+
51+
void (^threadInit)() = nullptr;
52+
53+
void derefWeak(uint64_t index) {
54+
if (weakObjs[index % OBJECT_COUNT])
55+
sink.fetch_add(1, std::memory_order_relaxed);
56+
}
57+
58+
void checkpointChecker() {
59+
auto now = std::chrono::steady_clock::now();
60+
if (now - checkpointAt.load() > maxCheckpointWaitTime) {
61+
std::cerr << "Timeout waiting for checkpoint\n";
62+
std::abort();
63+
}
64+
auto checkerInterval = std::chrono::nanoseconds(maxCheckpointWaitTime) / 2;
65+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, checkerInterval.count()),
66+
dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
67+
checkpointChecker();
68+
});
69+
}
70+
71+
void runLoop(uint64_t index, bool doThreadInit) {
72+
if (doThreadInit)
73+
threadInit();
74+
derefWeak(index);
75+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ runLoop(index + 1, doThreadInit); });
76+
}
77+
78+
}
79+
80+
extern "C" void prepare(id objTemplate, void (^threadInit)()) {
81+
::threadInit = threadInit;
82+
Class kClass = [objTemplate class];
83+
for (int i = 0; i < OBJECT_COUNT; ++i) {
84+
id obj = i % 2 ? [kClass new] : [NSObject new];
85+
objs[i] = obj;
86+
weakObjs[i] = obj;
87+
}
88+
for (int i = 0; i < RUN_LOOP_COUNT; ++i) {
89+
// Spread starting indices.
90+
uint64_t index = OBJECT_COUNT / RUN_LOOP_COUNT * i + (i % 2);
91+
// TODO(KT-80770): `threadInit` is being retained in `runLoop` in a runnable state, which may cause a deadlock.
92+
// Fix this and then replace `false` with `i % 2` or similar.
93+
bool doThreadInit = false;
94+
runLoop(index, doThreadInit);
95+
}
96+
startedAt = std::chrono::steady_clock::now();
97+
checkpointAt = startedAt;
98+
checkpointChecker();
99+
}
100+
101+
extern "C" void checkpoint(void) {
102+
auto now = std::chrono::steady_clock::now();
103+
checkpointAt = now;
104+
auto testDuration = now - startedAt;
105+
if (testDuration >= maxTestDuration) {
106+
std::cerr << "Done: " << sink << "\n";
107+
std::exit(0);
108+
}
109+
}
110+
111+
// Used by Kotlin via SymbolName.
112+
extern "C" void dereferenceWeak(uint64_t index) {
113+
derefWeak(index * 2); // Obj-C objects are on even places, can't dereference Kotlin object, because thread state assertion will fail
114+
}
115+
116+
// MODULE: main(cinterop)
117+
// FILE: main.kt
118+
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class, kotlin.native.runtime.NativeRuntimeApi::class)
119+
120+
import cinterop.*
121+
import kotlin.time.Duration.Companion.microseconds
122+
123+
@SymbolName("dereferenceWeak")
124+
private external fun dereferenceWeak(index: ULong)
125+
126+
fun main() {
127+
// Force GC to run almost non-stop.
128+
kotlin.native.runtime.GC.regularGCInterval = 10.microseconds
129+
kotlin.native.runtime.GC.collect() // to apply the new GC interval immediately.
130+
prepare(Any()) {}
131+
var index = 0UL
132+
while(true) {
133+
dereferenceWeak(index++)
134+
// checkpoint will exit itself, when enough time has passed.
135+
checkpoint()
136+
}
137+
}

native/native.tests/stress/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeStressTestGenerated.java

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

native/native.tests/stress/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeStressTestGenerated.java

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)