Skip to content

Commit 9ad534b

Browse files
committed
[Runtime] Fix a false metadata cycle diagnostic when threads race to instantiate cyclical metadata.
The metadata creation system detects cycles where metadata depends on other metadata which depends on the first one again and raises a fatal error if the cycle can't be fulfilled. Some cycles can be fulfilled. The cycle may involve a requirement for a metadata state less than full transitive completeness which can be reached without resolving the entire cycle. We only want to raise a fatal error when we detect a cycle that can't be fulfilled. Normally this happens because the cycle checking in `blockOnMetadataDependency` only sees a cycle when it can't be fulfilled. Metadata initialization is advanced as far as it can be at each stage, so a cycle that can be fulfilled will see a fulfilling state and won't generate the dependency in the first place, since we only generate dependencies that haven't yet been met. However, when two threads race to create types in a cycle, we can end up with such a dependency, because the dependency may be generated before another thread fulfilled yet. The cycle checker doesn't account for this and incorrectly raises a fatal error in that case. Fix this by checking the cyclic dependency against the metadata's current state. If we have a dependency that's already been fulfilled, then there isn't really a dependency cycle. In that case, don't raise a fatal error. rdar://135036243
1 parent 4bddb3a commit 9ad534b

File tree

2 files changed

+83
-31
lines changed

2 files changed

+83
-31
lines changed

stdlib/public/runtime/Metadata.cpp

+37-31
Original file line numberDiff line numberDiff line change
@@ -7811,44 +7811,50 @@ checkMetadataDependency(MetadataDependency dependency) {
78117811
void swift::blockOnMetadataDependency(MetadataDependency root,
78127812
MetadataDependency firstLink) {
78137813
std::vector<MetadataDependency> links;
7814-
auto checkNewLink = [&](MetadataDependency newLink) {
7815-
links.push_back(newLink);
7816-
for (auto i = links.begin(), e = links.end() - 1; i != e; ++i) {
7817-
if (i->Value == newLink.Value) {
7818-
diagnoseMetadataDependencyCycle(
7819-
llvm::makeArrayRef(&*i, links.end() - i));
7820-
}
7821-
}
7822-
};
7823-
78247814
links.push_back(root);
78257815

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

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

78547860
/***************************************************************************/
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)