Skip to content

Commit af00e15

Browse files
committed
[PackageGraph] Add cancellation support in DependencyResolver
<rdar://problem/48341369> Add cancellation support in DependencyResolver Unfortunately, I can't think of a good way to unit test this.
1 parent fb27fbc commit af00e15

File tree

2 files changed

+53
-5
lines changed

2 files changed

+53
-5
lines changed

Sources/PackageGraph/DependencyResolver.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public enum DependencyResolverError: Error, Equatable, CustomStringConvertible {
2828
/// The resolver found missing versions for the given constraints.
2929
case missingVersions([PackageContainerConstraint])
3030

31+
/// The resolution was cancelled.
32+
case cancelled
33+
3134
public static func == (lhs: DependencyResolverError, rhs: DependencyResolverError) -> Bool {
3235
switch (lhs, rhs) {
3336
case (.unsatisfiable, .unsatisfiable):
@@ -47,11 +50,17 @@ public enum DependencyResolverError: Error, Equatable, CustomStringConvertible {
4750
return lhs == rhs
4851
case (.missingVersions, _):
4952
return false
53+
case (.cancelled, .cancelled):
54+
return true
55+
case (.cancelled, _):
56+
return false
5057
}
5158
}
5259

5360
public var description: String {
5461
switch self {
62+
case .cancelled:
63+
return "the dependency resolution was cancelled"
5564
case .unsatisfiable:
5665
return "unable to resolve dependencies"
5766
case .cycle(let package):
@@ -821,10 +830,20 @@ public class DependencyResolver {
821830
/// Skip updating containers while fetching them.
822831
private let skipUpdate: Bool
823832

833+
/// Lock used to get and set the error variable.
834+
private let errorLock: Lock = Lock()
835+
824836
// FIXME: @testable private
825837
//
826838
/// Contains any error encountered during dependency resolution.
827-
var error: Swift.Error?
839+
var error: Swift.Error? {
840+
get {
841+
return errorLock.withLock { self.__error }
842+
} set {
843+
errorLock.withLock { self.__error = newValue }
844+
}
845+
}
846+
var __error: Swift.Error?
828847

829848
/// Key used to cache a resolved subtree.
830849
private struct ResolveSubtreeCacheKey: Hashable {
@@ -884,6 +903,13 @@ public class DependencyResolver {
884903
case error(Swift.Error)
885904
}
886905

906+
/// Cancel the dependency resolution operation.
907+
///
908+
/// This method is thread-safe.
909+
public func cancel() {
910+
self.error = DependencyResolverError.cancelled
911+
}
912+
887913
/// Execute the resolution algorithm to find a valid assignment of versions.
888914
///
889915
/// If a valid assignment is not found, the resolver will go into incomplete

Sources/Workspace/Workspace.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ public class Workspace {
300300
/// Skip updating containers while fetching them.
301301
fileprivate let skipUpdate: Bool
302302

303+
/// The active package resolver. This is set during a dependency resolution operation.
304+
fileprivate var activeResolver: PackageResolver?
305+
303306
/// Typealias for dependency resolver we use in the workspace.
304307
fileprivate typealias PackageDependencyResolver = DependencyResolver
305308
fileprivate typealias PubgrubResolver = PubgrubDependencyResolver
@@ -539,6 +542,17 @@ extension Workspace {
539542
try? fileSystem.removeFileTree(dataPath)
540543
}
541544

545+
/// Cancel the active dependency resolution operation.
546+
///
547+
/// Only legacy resolver is supported right now. This method is thread-safe.
548+
public func cancelActiveResolverOperation() {
549+
switch activeResolver {
550+
case .legacy(let resolver)?:
551+
resolver.cancel()
552+
default: break
553+
}
554+
}
555+
542556
/// Updates the current dependencies.
543557
///
544558
/// - Parameters:
@@ -579,7 +593,14 @@ extension Workspace {
579593
let resolutionStartTime = Date()
580594

581595
// Resolve the dependencies.
582-
let updateResults = resolveDependencies(dependencies: updateConstraints, diagnostics: diagnostics)
596+
let resolver = createResolver()
597+
activeResolver = resolver
598+
599+
let updateResults = resolveDependencies(resolver: resolver, dependencies: updateConstraints, diagnostics: diagnostics)
600+
601+
// Reset the active resolver.
602+
activeResolver = nil
603+
583604
guard !diagnostics.hasErrors else { return }
584605

585606
// Emit the time taken to perform dependency resolution.
@@ -1217,8 +1238,11 @@ extension Workspace {
12171238
// Perform dependency resolution.
12181239
let resolverDiagnostics = DiagnosticsEngine()
12191240
var resolver = createResolver()
1241+
activeResolver = resolver
1242+
12201243
var result = resolveDependencies(
12211244
resolver: resolver, dependencies: constraints, pins: validPins, diagnostics: resolverDiagnostics)
1245+
activeResolver = nil
12221246

12231247
// If we fail, we just try again without any pins because the pins might
12241248
// be completely incompatible.
@@ -1586,13 +1610,11 @@ extension Workspace {
15861610

15871611
/// Runs the dependency resolver based on constraints provided and returns the results.
15881612
fileprivate func resolveDependencies(
1589-
resolver: PackageResolver? = nil,
1613+
resolver: PackageResolver,
15901614
dependencies: [RepositoryPackageConstraint],
15911615
pins: [RepositoryPackageConstraint] = [],
15921616
diagnostics: DiagnosticsEngine
15931617
) -> [(container: PackageReference, binding: BoundVersion)] {
1594-
let resolver = resolver ?? createResolver()
1595-
15961618
let result = resolver.resolve(dependencies: dependencies, pins: pins)
15971619

15981620
// Take an action based on the result.

0 commit comments

Comments
 (0)