Files
swift-mirror/test/Interpreter/metadata_cycles_threaded.swift
Mike Ash 9ad534bc4a [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
2025-04-04 17:49:55 -04:00

47 lines
1.3 KiB
Swift

// RUN: %target-run-simple-swift(%import-libdispatch)
// REQUIRES: executable_test
// REQUIRES: libdispatch
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
import Dispatch
@_optimize(none) @inline(never) func forceTypeInstantiation(_: Any.Type) {}
struct AnyFoo<T, U> {
var thing: U
}
struct S<T> {
var thing: T
var next: AnyFoo<S, T>?
}
// We want to ensure that the runtime handles legal metadata cycles when threads
// race to instantiate the cycle. We have a cycle between S and AnyFoo, but it's
// resolvable because AnyFoo doesn't depend on S's layout. This tests a fix for
// a bug where the runtime's cycle detection could be overeager when multiple
// threads raced, and flag a legal.
//
// Since this is a multithreading test, failures are probabilistic and each type
// can only be tested once. The recursiveTry construct generates a large number
// of distinct types so we can do many tests.
func tryWithType<T>(_ t: T.Type) {
DispatchQueue.concurrentPerform(iterations: 5) { n in
forceTypeInstantiation(AnyFoo<S<T>, T>?.self)
}
}
struct One<T> {}
struct Two<T> {}
func recursiveTry<T>(_ t: T.Type, depth: Int = 0) {
if depth > 10 { return }
tryWithType(T.self)
recursiveTry(One<T>.self, depth: depth + 1)
recursiveTry(Two<T>.self, depth: depth + 1)
}
recursiveTry(Int.self)