Skip to content

Conversation

@sbhavani
Copy link

@sbhavani sbhavani commented Oct 4, 2025

Adds --max-concurrent-downloads flag to container image pull for configurable concurrent layer downloads.

Fixes #715
Depends on apple/containerization#311

Usage:

container image pull nginx:latest --max-concurrent-downloads 6

Changes:

  • Add CLI flag (default: 3)
  • Thread parameter through XPC stack
  • Update to use forked containerization with configurable concurrency

Performance: ~1.2-1.3x faster pulls for multi-layer images with higher concurrency

Tests: Included standalone tests verify concurrency behavior and parameter flow

@@ -0,0 +1,109 @@
#!/usr/bin/env swift
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this and test_parameter_flow.swift in the top level directory?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should they belong in Tests/CLITests/Subcommands/Images/?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this might be a good place for the tests.

@dcantah dcantah marked this pull request as draft October 10, 2025 00:54
guard err.isCode(.notFound) else {
throw err
}
return try await Self.pull(reference: reference, platform: platform, scheme: scheme, progressUpdate: progressUpdate)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm somewhat confused by the commit description however. I thought we had found we do pull layers in parallel, but that it's just a hardcoded 8.

Copy link
Author

@sbhavani sbhavani Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry I originally thought it wasn't pulling in parallel but I noticed it was hardcoded later

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated original issue to more accurately reflect this: #715

@sbhavani sbhavani force-pushed the feature/parallel-layer-downloads branch from c843892 to cc15745 Compare October 14, 2025 06:21
@sbhavani sbhavani marked this pull request as ready for review October 14, 2025 06:23
@sbhavani sbhavani force-pushed the feature/parallel-layer-downloads branch from cc15745 to 26ebe63 Compare October 14, 2025 06:25
Implements concurrent layer downloads to improve image pull performance by fetching multiple layers simultaneously instead of sequentially.
@sbhavani sbhavani force-pushed the feature/parallel-layer-downloads branch from 26ebe63 to 53e6c45 Compare October 14, 2025 06:27
@sbhavani
Copy link
Author

@dcantah anything else needed to merge this?

@dcantah
Copy link
Member

dcantah commented Oct 21, 2025

@sbhavani Yes. We need a tag of Containerization that has the changes. I'm making some changes over there but should have a tag out within the next day or so and then we can bump to this. We should not have our dependency on Containerization be to a branch, and especially not main 😄

Copy link
Contributor

@jglogan jglogan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbhavani Just a couple of nits if you still have time to move this forward. Thank you for contributing!

.package(url: "https://github.com/orlandos-nl/DNSClient.git", from: "2.4.1"),
.package(url: "https://github.com/Bouke/DNS.git", from: "1.2.0"),
.package(url: "https://github.com/apple/containerization.git", exact: Version(stringLiteral: scVersion)),
.package(url: "https://github.com/apple/containerization.git", branch: "main"),
Copy link
Contributor

@jglogan jglogan Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbhavani As Danny mentioned this needs to be reverted. If you rebase the PR, scVersion should be at the point where your ImageStore.pull() call will build without issue.

Copy link
Contributor

@dkovba dkovba Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this has to be reverted, and scVersion should be updated to 0.11.0. Services/ContainerSandboxService/SandboxService.swift will need to be updated after that.


public init(disableProgressUpdates: Bool, maxConcurrentDownloads: Int) {
self.disableProgressUpdates = disableProgressUpdates
self.maxConcurrentDownloads = maxConcurrentDownloads
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason behind putting this in the progress group?

The problem I see here is that any command that wants to present the --progress flag (--disable-progress-updates is gone, apologies for letting this PR go so long so as to force a rebase here) will also see --max-concurrent-downloads whether downloads are relevant to the other command or not. Is there a better home for this? Or do we need to create another reusable group especially for download-related options?

I don't see any harm in copy/pasting this option into each command that needs it either, unless there's simply too many of them.

@sbhavani
Copy link
Author

@sbhavani Just a couple of nits if you still have time to move this forward. Thank you for contributing!

no problem! I'll take a look and make the changes

Copy link
Contributor

@dkovba dkovba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the contribution 👍
The default value 3 seems reasonable.

@@ -0,0 +1,109 @@
#!/usr/bin/env swift
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project is currently using camelCase for filenames.

.package(url: "https://github.com/orlandos-nl/DNSClient.git", from: "2.4.1"),
.package(url: "https://github.com/Bouke/DNS.git", from: "1.2.0"),
.package(url: "https://github.com/apple/containerization.git", exact: Version(stringLiteral: scVersion)),
.package(url: "https://github.com/apple/containerization.git", branch: "main"),
Copy link
Contributor

@dkovba dkovba Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this has to be reverted, and scVersion should be updated to 0.11.0. Services/ContainerSandboxService/SandboxService.swift will need to be updated after that.

/// Starts multiple concurrent tasks and returns them.
/// - Parameter count: The number of concurrent tasks to start.
/// - Returns: An array of ProgressTask instances.
public func startConcurrentTasks(count: Int) -> [ProgressTask] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is unused and can be removed.

/// - Parameters:
/// - task: The task whose progress is being updated.
/// - progressUpdate: The handler to invoke when progress updates are received.
public static func concurrentHandler(for task: ProgressTask, from progressUpdate: @escaping ProgressUpdateHandler) -> ProgressUpdateHandler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is unused and can be removed.


let insecure = try scheme.schemeFor(host: host) == .http
request.set(key: .insecureFlag, value: insecure)
request.set(key: .maxConcurrentDownloads, value: Int64(maxConcurrentDownloads))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check that maxConcurrentDownloads > 0?

@@ -0,0 +1,109 @@
#!/usr/bin/env swift
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this might be a good place for the tests.

public struct ProgressTask: Sendable, Equatable, Hashable {
private var id = UUID()
private var coordinator: ProgressTaskCoordinator
internal var coordinator: ProgressTaskCoordinator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the unused methods are removed, coordinator can be kept private.

/// A type that coordinates progress tasks to ignore updates from completed tasks.
public actor ProgressTaskCoordinator {
var currentTask: ProgressTask?
var activeTasks: Set<ProgressTask> = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the concurrent download occurs inside a single task. Can we simplify the logic and remove activeTasks and hash()?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Request]: Support configurable parallel layer downloads during image pull

5 participants