Skip to content

Commit 1f0696a

Browse files
authored
Merge pull request #80505 from mikeash/metadata-cycle-race-fix
[Runtime] Fix a false metadata cycle diagnostic when threads race to instantiate cyclical metadata.
2 parents 9d1d917 + 9ad534b commit 1f0696a

File tree

2 files changed

+83
-31
lines changed

2 files changed

+83
-31
lines changed

Diff for: stdlib/public/runtime/Metadata.cpp

+37-31
Original file line numberDiff line numberDiff line change
@@ -7807,44 +7807,50 @@ checkMetadataDependency(MetadataDependency dependency) {
78077807
void swift::blockOnMetadataDependency(MetadataDependency root,
78087808
MetadataDependency firstLink) {
78097809
std::vector<MetadataDependency> links;
7810-
auto checkNewLink = [&](MetadataDependency newLink) {
7811-
links.push_back(newLink);
7812-
for (auto i = links.begin(), e = links.end() - 1; i != e; ++i) {
7813-
if (i->Value == newLink.Value) {
7814-
diagnoseMetadataDependencyCycle(
7815-
llvm::makeArrayRef(&*i, links.end() - i));
7816-
}
7817-
}
7818-
};
7819-
78207810
links.push_back(root);
78217811

78227812
// Iteratively add each link, checking for a cycle, until we reach
78237813
// something without a known dependency.
7824-
checkNewLink(firstLink);
7825-
while (true) {
7814+
7815+
// Start out with firstLink. The initial NewState value won't be
7816+
// used, so just initialize it to an arbitrary value.
7817+
MetadataStateWithDependency currentCheckResult{
7818+
PrivateMetadataState::Allocating, firstLink};
7819+
7820+
// If there isn't a known dependency, we can't do any more checking.
7821+
while (currentCheckResult.Dependency) {
7822+
// Add this dependency to our links.
7823+
links.push_back(currentCheckResult.Dependency);
7824+
78267825
// Try to get a dependency for the metadata in the last link we added.
7827-
auto checkResult = checkMetadataDependency(links.back());
7828-
7829-
// If there isn't a known dependency, we can't do any more checking.
7830-
if (!checkResult.Dependency) {
7831-
// In the special case where it's the first link that doesn't have
7832-
// a known dependency and its current metadata state now satisfies
7833-
// the dependency leading to it, we can skip waiting.
7834-
if (links.size() == 2 &&
7835-
satisfies(checkResult.NewState, links.back().Requirement))
7836-
return;
7837-
7838-
// Otherwise, just make a blocking request for the first link in
7839-
// the chain.
7840-
auto request = MetadataRequest(firstLink.Requirement);
7841-
swift_checkMetadataState(request, firstLink.Value);
7842-
return;
7843-
}
7826+
currentCheckResult = checkMetadataDependency(links.back());
78447827

7845-
// Check the new link.
7846-
checkNewLink(checkResult.Dependency);
7828+
// Check the last link against the rest of the list.
7829+
for (auto i = links.begin(), e = links.end() - 1; i != e; ++i) {
7830+
if (i->Value == links.back().Value) {
7831+
// If there's a cycle but the new link's current state is now satisfied,
7832+
// then this is a stale dependency, not a cycle. This can happen when
7833+
// threads race to build a type in a fulfillable cycle.
7834+
if (!satisfies(currentCheckResult.NewState, links.back().Requirement))
7835+
diagnoseMetadataDependencyCycle(
7836+
llvm::makeArrayRef(&*i, links.end() - i));
7837+
}
7838+
}
78477839
}
7840+
7841+
// We didn't find any cycles. Make a blocking request if appropriate.
7842+
7843+
// In the special case where it's the first link that doesn't have
7844+
// a known dependency and its current metadata state now satisfies
7845+
// the dependency leading to it, we can skip waiting.
7846+
if (links.size() == 2 &&
7847+
satisfies(currentCheckResult.NewState, links.back().Requirement))
7848+
return;
7849+
7850+
// Otherwise, just make a blocking request for the first link in
7851+
// the chain.
7852+
auto request = MetadataRequest(firstLink.Requirement);
7853+
swift_checkMetadataState(request, firstLink.Value);
78487854
}
78497855

78507856
/***************************************************************************/

Diff for: test/Interpreter/metadata_cycles_threaded.swift

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// RUN: %target-run-simple-swift(%import-libdispatch)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: libdispatch
5+
// UNSUPPORTED: use_os_stdlib
6+
// UNSUPPORTED: back_deployment_runtime
7+
8+
import Dispatch
9+
10+
@_optimize(none) @inline(never) func forceTypeInstantiation(_: Any.Type) {}
11+
12+
struct AnyFoo<T, U> {
13+
var thing: U
14+
}
15+
16+
struct S<T> {
17+
var thing: T
18+
var next: AnyFoo<S, T>?
19+
}
20+
21+
// We want to ensure that the runtime handles legal metadata cycles when threads
22+
// race to instantiate the cycle. We have a cycle between S and AnyFoo, but it's
23+
// resolvable because AnyFoo doesn't depend on S's layout. This tests a fix for
24+
// a bug where the runtime's cycle detection could be overeager when multiple
25+
// threads raced, and flag a legal.
26+
//
27+
// Since this is a multithreading test, failures are probabilistic and each type
28+
// can only be tested once. The recursiveTry construct generates a large number
29+
// of distinct types so we can do many tests.
30+
func tryWithType<T>(_ t: T.Type) {
31+
DispatchQueue.concurrentPerform(iterations: 5) { n in
32+
forceTypeInstantiation(AnyFoo<S<T>, T>?.self)
33+
}
34+
}
35+
36+
struct One<T> {}
37+
struct Two<T> {}
38+
39+
func recursiveTry<T>(_ t: T.Type, depth: Int = 0) {
40+
if depth > 10 { return }
41+
tryWithType(T.self)
42+
recursiveTry(One<T>.self, depth: depth + 1)
43+
recursiveTry(Two<T>.self, depth: depth + 1)
44+
}
45+
46+
recursiveTry(Int.self)

0 commit comments

Comments
 (0)