Skip to content

Commit 44aa468

Browse files
committed
Use DelayableAction to ensure deferred actions are managed correctly
DelayableAction represents an action on a target that can be deferred until a more appropriate time. In this case, we use defer to cleanup objects in the case of early return error handling, but we need to delay this action when passing the target for use into an async completion block.
1 parent 422eb28 commit 44aa468

File tree

1 file changed

+31
-3
lines changed

1 file changed

+31
-3
lines changed

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,25 @@ public final class ManifestLoader: ManifestLoaderProtocol {
505505
)
506506
}
507507

508+
/// Represents behavior that can be deferred until a more appropriate time.
509+
internal struct DelayableAction<T> {
510+
var target: T?
511+
var action: ((T) -> Void)?
512+
513+
func perform() {
514+
if let value = target, let cleanup = action {
515+
cleanup(value)
516+
}
517+
}
518+
519+
mutating func delay() -> DelayableAction {
520+
let next = DelayableAction(target: target, action: action)
521+
target = nil
522+
action = nil
523+
return next
524+
}
525+
}
526+
508527
private func parseAndCacheManifest(
509528
at path: AbsolutePath,
510529
packageIdentity: PackageIdentity,
@@ -530,7 +549,8 @@ public final class ManifestLoader: ManifestLoaderProtocol {
530549
}
531550

532551
// TODO: we could wrap the failure here with diagnostics if it wasn't optional throughout
533-
defer { try? cache?.close() }
552+
var closeAfterRead = DelayableAction(target: cache) { try? $0.close() }
553+
defer { closeAfterRead.perform() }
534554

535555
let key : CacheKey
536556
do {
@@ -564,6 +584,9 @@ public final class ManifestLoader: ManifestLoaderProtocol {
564584
return completion(.failure(error))
565585
}
566586

587+
// delay closing cache until after write.
588+
let closeAfterWrite = closeAfterRead.delay()
589+
567590
// shells out and compiles the manifest, finally output a JSON
568591
self.evaluateManifest(
569592
packageIdentity: key.packageIdentity,
@@ -573,6 +596,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
573596
delegateQueue: delegateQueue
574597
) { result in
575598
do {
599+
defer { closeAfterWrite.perform() }
576600
let evaluationResult = try result.get()
577601
// only cache successfully parsed manifests
578602
let parseManifest = try self.parseManifest(
@@ -818,6 +842,9 @@ public final class ManifestLoader: ManifestLoaderProtocol {
818842

819843
// Compile the manifest.
820844
Process.popen(arguments: cmd, environment: toolchain.swiftCompilerEnvironment) { result in
845+
var cleanupIfError = DelayableAction(target: tmpDir, action: cleanupTmpDir)
846+
defer { cleanupIfError.perform() }
847+
821848
let compilerResult : ProcessResult
822849
do {
823850
compilerResult = try result.get()
@@ -870,9 +897,10 @@ public final class ManifestLoader: ManifestLoaderProtocol {
870897
let windowsPathComponent = runtimePath.pathString.replacingOccurrences(of: "/", with: "\\")
871898
environment["Path"] = "\(windowsPathComponent);\(environment["Path"] ?? "")"
872899
#endif
873-
900+
901+
let cleanupAfterRunning = cleanupIfError.delay()
874902
Process.popen(arguments: cmd, environment: environment) { result in
875-
defer { cleanupTmpDir(tmpDir) }
903+
defer { cleanupAfterRunning.perform() }
876904
fclose(jsonOutputFileDesc)
877905

878906
do {

0 commit comments

Comments
 (0)