-
Notifications
You must be signed in to change notification settings - Fork 553
Add --max-concurrent-downloads flag for parallel layer downloads #716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jglogan
merged 19 commits into
apple:main
from
sbhavani:feature/parallel-layer-downloads
Dec 7, 2025
Merged
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
53e6c45
Add parallel layer downloads support for image pull operations
sbhavani 903beb8
Merge branch 'main' into feature/parallel-layer-downloads
sbhavani 2c97436
chore: upgrade to containerization 0.11.0
sbhavani 21ac59f
refactor: remove unused concurrent progress methods
sbhavani 9659c53
refactor: rename test files to camelCase
sbhavani b2e13d9
fix: validate maxConcurrentDownloads > 0
sbhavani efa7b87
chore: trigger CI
sbhavani 341c918
Merge upstream/main into feature/parallel-layer-downloads
sbhavani ae30d88
refactor: move download option to ImageFetch group
sbhavani c905f84
refactor: use .init instead of explicit type
sbhavani 2029f72
revert: restore scVersion to 0.13.0
sbhavani e1c03a2
fix: improve maxConcurrentDownloads error message
sbhavani 3374833
test: add maxConcurrentDownloads CLI tests
sbhavani b9e673a
chore: remove standalone test scripts
sbhavani 7359006
style: apply code formatting
sbhavani 0996e40
Merge upstream main into feature/parallel-layer-downloads
sbhavani 04c9d8e
Fix duplicate Int64 set method declaration in ImageServiceXPCKeys
sbhavani f51d64b
fix(xpc): remove duplicate Int64 setter and restore main branch metho…
sbhavani 5cacdd6
fix(images): improve max concurrent downloads messages
sbhavani File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -208,7 +208,15 @@ public struct Flags { | |
| self.disableProgressUpdates = disableProgressUpdates | ||
| } | ||
|
|
||
| public init(disableProgressUpdates: Bool, maxConcurrentDownloads: Int) { | ||
| self.disableProgressUpdates = disableProgressUpdates | ||
| self.maxConcurrentDownloads = maxConcurrentDownloads | ||
dkovba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| @Flag(name: .long, help: "Disable progress bar updates") | ||
| public var disableProgressUpdates = false | ||
|
|
||
| @Option(name: .long, help: "Maximum number of concurrent layer downloads (default: 3)") | ||
|
||
| public var maxConcurrentDownloads: Int = 3 | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #!/usr/bin/env swift | ||
|
|
||
| import Foundation | ||
|
|
||
| func testConcurrentDownloads() async throws { | ||
| print("Testing concurrent download behavior...\n") | ||
|
|
||
| // Track concurrent task count | ||
| actor ConcurrencyTracker { | ||
| var currentCount = 0 | ||
| var maxObservedCount = 0 | ||
| var completedTasks = 0 | ||
|
|
||
| func taskStarted() { | ||
| currentCount += 1 | ||
| maxObservedCount = max(maxObservedCount, currentCount) | ||
| } | ||
|
|
||
| func taskCompleted() { | ||
| currentCount -= 1 | ||
| completedTasks += 1 | ||
| } | ||
|
|
||
| func getStats() -> (max: Int, completed: Int) { | ||
| return (maxObservedCount, completedTasks) | ||
| } | ||
|
|
||
| func reset() { | ||
| currentCount = 0 | ||
| maxObservedCount = 0 | ||
| completedTasks = 0 | ||
| } | ||
| } | ||
|
|
||
| let tracker = ConcurrencyTracker() | ||
|
|
||
| // Test with different concurrency limits | ||
| for maxConcurrent in [1, 3, 6] { | ||
| await tracker.reset() | ||
|
|
||
| // Simulate downloading 20 layers | ||
| let layerCount = 20 | ||
| let layers = Array(0..<layerCount) | ||
|
|
||
| print("Testing maxConcurrent=\(maxConcurrent) with \(layerCount) layers...") | ||
|
|
||
| let startTime = Date() | ||
|
|
||
| try await withThrowingTaskGroup(of: Void.self) { group in | ||
| var iterator = layers.makeIterator() | ||
|
|
||
| // Start initial batch based on maxConcurrent | ||
| for _ in 0..<maxConcurrent { | ||
| if iterator.next() != nil { | ||
| group.addTask { | ||
| await tracker.taskStarted() | ||
| try await Task.sleep(nanoseconds: 10_000_000) | ||
| await tracker.taskCompleted() | ||
| } | ||
| } | ||
| } | ||
| for try await _ in group { | ||
| if iterator.next() != nil { | ||
| group.addTask { | ||
| await tracker.taskStarted() | ||
| try await Task.sleep(nanoseconds: 10_000_000) | ||
| await tracker.taskCompleted() | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| let duration = Date().timeIntervalSince(startTime) | ||
| let stats = await tracker.getStats() | ||
|
|
||
| print(" ✓ Completed: \(stats.completed)/\(layerCount)") | ||
| print(" ✓ Max concurrent: \(stats.max)") | ||
| print(" ✓ Duration: \(String(format: "%.3f", duration))s") | ||
|
|
||
| guard stats.max <= maxConcurrent + 1 else { | ||
| throw TestError.concurrencyLimitExceeded | ||
| } | ||
|
|
||
| guard stats.completed == layerCount else { | ||
| throw TestError.incompleteTasks | ||
| } | ||
|
|
||
| print(" ✅ PASSED\n") | ||
| } | ||
|
|
||
| print("All tests passed!") | ||
| } | ||
|
|
||
| enum TestError: Error { | ||
| case concurrencyLimitExceeded | ||
| case incompleteTasks | ||
| } | ||
|
|
||
| Task { | ||
| do { | ||
| try await testConcurrentDownloads() | ||
| exit(0) | ||
| } catch { | ||
| print("Test failed: \(error)") | ||
| exit(1) | ||
| } | ||
| } | ||
|
|
||
| RunLoop.main.run() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.