Skip to content

Conversation

leonardochaia
Copy link
Contributor

@leonardochaia leonardochaia commented Sep 26, 2024

What this PR does / why we need it

This PR improves CopyAsync and CopyGraphAsync to include the CopyGraphOptions, inspired from oras-go.

The current implementation:

  1. adds support for CopyOptions events, like OnPreCopy, these were implemented in the form of Action<T>s

Pending items

  • Define Copy signature

Which issue(s) this PR resolves / fixes

Please check the following list

  • Does the affected code have corresponding tests, e.g. unit test, E2E test?
  • Does this change require a documentation update?
  • Does this introduce breaking changes that would require an announcement or bumping the major version?
  • Do all new files have an appropriate license header?

@leonardochaia leonardochaia changed the title feat(copy): support mounting existing descriptors from other repos feat(copy): makes copy more efficient Sep 26, 2024
@leonardochaia leonardochaia force-pushed the feat/improve-copy-performance branch from 847ecb0 to 6817504 Compare September 26, 2024 02:00
Signed-off-by: Leonardo Chaia <[email protected]>
@leonardochaia
Copy link
Contributor Author

This is a work in progress. Seems to be working running locally. Tests are failing and need more work.

@leonardochaia leonardochaia marked this pull request as ready for review October 17, 2024 18:41
@leonardochaia
Copy link
Contributor Author

Hi. I propose concurrency support is implemented in a separate PR. Otherwise this should be ready to review :+

Leo.

@leonardochaia
Copy link
Contributor Author

Example usage:

List<string> knownReferences = [];
foreach (var toReplicate in toReplicateArray)
{
    var copyOpts = new CopyOptions()
    {
        MountFrom = _ => knownReferences.ToArray(),
    };

    copyOpts.OnCopySkipped += d => this.logger.LogTrace("{Digest}: Already exists.", d.Digest);
    copyOpts.OnPreCopy += d => this.logger.LogTrace("{Digest}: Copying..", d.Digest);
    copyOpts.OnPostCopy += d => this.logger.LogTrace("{Digest}: Copied", d.Digest);
    copyOpts.OnMounted += (d, from) => this.logger.LogTrace("{Digest}: Mounted from {From}", d.Digest, from);

    await sourceRepository.CopyAsync(
        toReplicate.SourceImage,
        destinationRepository,
        toReplicate.DestinationImage,
        cancellationToken,
        copyOpts);

    knownReferences.Add(toReplicate.DestinationImage);
}

@leonardochaia
Copy link
Contributor Author

Hi all, any feedback is appreciated 🥇

Thanks!
Leo.

Copy link

codecov bot commented Oct 28, 2024

Codecov Report

Attention: Patch coverage is 91.66667% with 3 lines in your changes missing coverage. Please review.

Project coverage is 89.25%. Comparing base (97f1225) to head (20ac068).

Files with missing lines Patch % Lines
src/OrasProject.Oras/AsyncInvocationExtensions.cs 62.50% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #145      +/-   ##
==========================================
+ Coverage   89.06%   89.25%   +0.19%     
==========================================
  Files          39       41       +2     
  Lines        1591     1620      +29     
  Branches      204      211       +7     
==========================================
+ Hits         1417     1446      +29     
+ Misses        113      112       -1     
- Partials       61       62       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Wwwsylvia
Copy link
Member

Hi @leonardochaia , thank you for your contribution! Sorry for the delay. We need a bit more time to review your PR, but we’ll provide feedback as soon as we can. Thank you for your understanding!

@Wwwsylvia
Copy link
Member

Hi. I propose concurrency support is implemented in a separate PR. Otherwise this should be ready to review :+

Do we want to move the mounting support to a separate PR as well?

@leonardochaia
Copy link
Contributor Author

Hi. I propose concurrency support is implemented in a separate PR. Otherwise this should be ready to review :+

Do we want to move the mounting support to a separate PR as well?

Hi @Wwwsylvia , thanks for the review. I'll apply the changes and report back.
We can move the mounting to a separate PR. In such case we would leave only the CopyOptions and related events on this PR, and implement the IMounter and mounting support on copy on a separate PR.

My goal is getting the mounting support when copying graphs since we are using this to replicate a bunch of images between registries and need to make it faster.

@Wwwsylvia
Copy link
Member

@leonardochaia Sounds good. Thank you!

@leonardochaia
Copy link
Contributor Author

PR simplified to only introduce the CopyGraphOptions. Next step is to update the tests to cover the events.

Separate PRs/issues to be created:

  1. Copy to support concurrency
  2. Repository to support mounting
  3. Copy to support mounting

@leonardochaia
Copy link
Contributor Author

leonardochaia commented Nov 11, 2024

Usually CancellationToken is left as the last parameter, however, that would introduce a breaking change. Are we good with introducing a breaking change?

public static async Task<Descriptor> CopyAsync(this ITarget src, string srcRef, ITarget dst, string dstRef, CopyGraphOptions? copyOptions = default, CancellationToken cancellationToken = default)
    
public static async Task CopyGraphAsync(this ITarget src, ITarget dst, Descriptor node, , CopyGraphOptions? copyOptions = default, CancellationToken cancellationToken)

EDIT: Disregard, I've added an overload to keep the CancellationToken last but also support the current signature

Signed-off-by: Leonardo Chaia <[email protected]>
@leonardochaia leonardochaia changed the title feat(copy): makes copy more efficient feat(copy): introduces CopyGraphOptions with events support Nov 11, 2024
@leonardochaia
Copy link
Contributor Author

Quick update, I've converted this PR to only include the CopyGraphOptions with events support and corresponding tests.

Next steps:

  1. Add support for mounting to Repository feat(repository): mounting support #152
  2. Add support for mounting to Copy (after this and feat(repository): mounting support #152 are merged)

@Wwwsylvia
Copy link
Member

Hi @leonardochaia , happy new year! Would you like to continue our discussion on this?

@Wwwsylvia
Copy link
Member

We can merge this first and iterate it later.

@Wwwsylvia
Copy link
Member

I will make changes based on this branch.

Wwwsylvia added 2 commits May 14, 2025 21:45
Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
@Wwwsylvia Wwwsylvia requested a review from Copilot May 14, 2025 13:56
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces enhancements to the copy functionality by adding CopyGraphOptions with event support for both synchronous and asynchronous operations. Key changes include:

  • Extending CopyAsync and CopyGraphAsync to support new CopyOptions and CopyGraphOptions (with events hooks).
  • Adding new structs (CopyOptions and CopyGraphOptions) and a helper extension (AsyncInvocationExtensions) to safely invoke asynchronous event delegates.

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/OrasProject.Oras.Tests/CopyTest.cs Added multiple tests to validate copy operations with event hooks and platform filtering.
src/OrasProject.Oras/Extensions.cs Modified CopyAsync and CopyGraphAsync signatures to accept CopyOptions and CopyGraphOptions.
src/OrasProject.Oras/CopyOptions.cs Introduced CopyOptions struct to encapsulate CopyGraphOptions.
src/OrasProject.Oras/CopyGraphOptions.cs Added CopyGraphOptions struct containing pre/post copy and skipped events.
src/OrasProject.Oras/AsyncInvocationExtensions.cs Provided an extension for invoking asynchronous event delegates using new C# syntax.
Comments suppressed due to low confidence (1)

tests/OrasProject.Oras.Tests/CopyTest.cs:153

  • [nitpick] Consider renaming the test 'TestCopyExistedRoot' to 'TestCopyExistingRoot' for improved clarity.
[Fact] public async Task TestCopyExistedRoot()

Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
@Wwwsylvia Wwwsylvia requested review from Wwwsylvia and Copilot May 14, 2025 14:04
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces event-driven graph copy options, enhancing the CopyAsync and CopyGraphAsync APIs with pre/post copy and skip hooks, and adds supporting types and tests.

  • Added CopyOptions and CopyGraphOptions structs with event delegates.
  • Updated Extensions.CopyAsync and CopyGraphAsync to invoke the new event hooks.
  • Introduced AsyncInvocationExtensions for safe parallel invocation of async event handlers.
  • Expanded unit tests in CopyTest.cs to cover skip, pre-copy, and post-copy events, and tracking behavior.

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/OrasProject.Oras.Tests/CopyTest.cs Added tests for skip, pre-copy, and post-copy event handlers; implemented StorageTracker.
src/OrasProject.Oras/Extensions.cs Overloaded CopyAsync/CopyGraphAsync to accept options and trigger CopyGraphOptions events.
src/OrasProject.Oras/CopyOptions.cs Introduced CopyOptions struct wrapping CopyGraphOptions.
src/OrasProject.Oras/CopyGraphOptions.cs Defined CopyGraphOptions with sync/async event delegates and internal invocation methods.
src/OrasProject.Oras/AsyncInvocationExtensions.cs Added InvokeAsync extension to safely call async event delegates in parallel.

Copy link
Contributor

@shizhMSFT shizhMSFT left a comment

Choose a reason for hiding this comment

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

LGTM

Wwwsylvia added 2 commits May 15, 2025 12:00
Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
@Wwwsylvia
Copy link
Member

Refactored the code a bit more, please take a look again @leonardochaia @shizhMSFT @pat-pan

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces CopyGraphOptions with event support to improve the copy operations. Key changes include:

  • New CopyGraphOptions and CopyOptions classes supporting pre-/post-copy and copy-skipped event hooks.
  • Updated Extensions methods to pass and use these options.
  • Expanded tests to verify the behavior of copy operations with the new event hooks.

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/OrasProject.Oras.Tests/CopyTest.cs Added tests for full graph copy, copying an existing root, and copy operations using hooks.
src/OrasProject.Oras/Extensions.cs Updated copy method signatures to accept new CopyOptions and CopyGraphOptions, and integrated event hook calls.
src/OrasProject.Oras/CopyOptions.cs Introduced CopyOptions as a subclass of CopyGraphOptions.
src/OrasProject.Oras/CopyGraphOptions.cs Added a new class that facilitates pre-/post-copy and copy-skipped events with asynchronous support.
src/OrasProject.Oras/AsyncInvocationExtensions.cs Added extension methods to safely invoke event delegates asynchronously.

public int PushCount { get; private set; }
public int ExistsCount { get; private set; }

public IList<string> Fetched { get; } = [];
Copy link
Preview

Copilot AI May 15, 2025

Choose a reason for hiding this comment

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

The property 'Fetched' is initialized with an empty array literal, which results in an immutable collection. Consider initializing it with a modifiable collection such as 'new List()' to allow dynamic additions.

Suggested change
public IList<string> Fetched { get; } = [];
public IList<string> Fetched { get; } = new List<string>();

Copilot uses AI. Check for mistakes.

Copy link
Contributor

@shizhMSFT shizhMSFT left a comment

Choose a reason for hiding this comment

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

LGTM

return eventDelegate
.GetInvocationList()
.Select(d => d is Func<TEventArgs, Task> handler ? handler(args) : Task.CompletedTask)
.ToArray();
Copy link
Contributor

Choose a reason for hiding this comment

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

Could the preCopy/postCopy/CopySkip turn into a multicast list?

/// <summary>
/// PreCopy handles the current descriptor before it is copied.
/// </summary>
public event Action<Descriptor>? PreCopy;
Copy link
Contributor

Choose a reason for hiding this comment

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

just curious about the rationale behind providing this synchronous version of handlers

Copy link
Member

Choose a reason for hiding this comment

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

It's more convenient to use and users don't have to return Tasks.CompletedTask if they do something synchonously.
See #145 (comment)

/// <summary>
/// CopyOptions contains parameters for <see cref="Extensions.CopyAsync(OrasProject.Oras.ITarget,string,OrasProject.Oras.ITarget,string,System.Threading.CancellationToken)"/>
/// </summary>
public class CopyOptions : CopyGraphOptions { }
Copy link
Contributor

Choose a reason for hiding this comment

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

Would composition make more sense here? Otherwise, it seems like CopyOptions doesn't do much here. Just wonder why it is needed here

Copy link
Member

Choose a reason for hiding this comment

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

For now, CopyOptions is just a placeholder. We will add more options into it.
I think inheritance makes it easier for users to initialize and use. They can just do:

var opts = new CopyOptions();
await CopyAsync(src, srcRef, dst, dstRef, opts);
await CopyGraphAsync(src, dst, root, opts);

and don't need to do:

var opts = new CopyOptions() {
    CopyGraphOptions = new CopyGraphOptions()
};

await CopyAsync(src, srcRef, dst, dstRef, opts);
await CopyGraphAsync(src, dst, root, opts.CopyGraphOptions);

Comment on lines +28 to +55
public event Func<Descriptor, Task>? PreCopyAsync;

/// <summary>
/// PreCopy handles the current descriptor before it is copied.
/// </summary>
public event Action<Descriptor>? PreCopy;

/// <summary>
/// PostCopyAsync handles the current descriptor after it is copied.
/// </summary>
public event Func<Descriptor, Task>? PostCopyAsync;

/// <summary>
/// PostCopy handles the current descriptor after it is copied.
/// </summary>
public event Action<Descriptor>? PostCopy;

/// <summary>
/// CopySkippedAsync will be called when the sub-DAG rooted by the current node
/// is skipped.
/// </summary>
public event Func<Descriptor, Task>? CopySkippedAsync;

/// <summary>
/// CopySkipped will be called when the sub-DAG rooted by the current node
/// is skipped.
/// </summary>
public event Action<Descriptor>? CopySkipped;
Copy link
Member

Choose a reason for hiding this comment

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

Just realized that the event handlers cannot be overwritten, which might not meet our need.

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.

4 participants