Skip to content
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

Expose IConfigChangeListener callback service #1734

Merged
merged 1 commit into from
Aug 19, 2022

Conversation

nulltoken
Copy link
Contributor

@nulltoken nulltoken commented May 24, 2022

Fix #1619

From the original issue:

"When dynamically applying loaded configuration (cf. https://microsoft.github.io/reverse-proxy/articles/config-providers.html) after the initial startup, any validation failure will be logged, then silenced.

We would need a way to get notified of both successful configuration changes and invalid configuration rejection."

This is an attempt to propagate whether or not the loaded configuration has been applied or not.

@nulltoken
Copy link
Contributor Author

@Tratcher Thanks for the feedback! I'll come back to you shortly.

@nulltoken nulltoken force-pushed the ntk/config_notif branch 2 times, most recently from 0dcd1ff to 5e0e600 Compare May 30, 2022 15:34
@nulltoken
Copy link
Contributor Author

@Tratcher Thanks for the feedback! I'll come back to you shortly.

Done. I've rebased the PR and applied your recommendations.

@nulltoken
Copy link
Contributor Author

Note to self: Reword commit message and cleanup history.

@adityamandaleeka
Copy link
Member

@samsp-msft PTAL

@Tratcher
Copy link
Member

Tratcher commented Jun 9, 2022

@nulltoken sorry I'm slow reviewing this, I wanted to show the design to the team. I'll get back to it later next week.

@Tratcher
Copy link
Member

Tratcher commented Jun 24, 2022

How would you want to report the config 'version' when there are multiple config's loaded/merged? A version for each source, or a combined count of how many times the config had successfully changed? For the former I think we'd need to add an Id field to IProxyConfig and then invoke a callback whenever that config changed and loaded successfully, passing that instance (and the source?). For the latter, we could have a callback that was invoked every time config changed and you could keep your own count.

@nulltoken nulltoken requested a review from Tratcher July 5, 2022 15:32
@nulltoken nulltoken changed the title Implement ProxyConfig and ProxyConfigProvider callbacks Expose IConfigNotifier callback service Jul 5, 2022
Copy link
Member

@Tratcher Tratcher left a comment

Choose a reason for hiding this comment

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

Thanks for the patience @nulltoken. This looks pretty good. I'll show it to the team and get their thoughts.

@samsp-msft what do you think about this approach to making config updates more trackable? Give each instance an ID and fire a service callback when it's been applied/failed.

/// <summary>
/// Optional configuration version identifier
/// </summary>
string? Id { get; }
Copy link
Member

Choose a reason for hiding this comment

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

We'll want to do this in a API non-breaking way, like using C#'s new Default Interface Methods. I'd also suggest making it non-nullable and assigning it a new random/guid value by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Tratcher I may have misunderstood your proposal and need some guidance here.

If we assign a new random guid to the Id property through the default interface methods, and this property isn't explicitly implemented in derived types (existing code for instance), we will end up with a non stable behavior when retrieving the content of the Id.

Which may make this harder to test and reason about.

Below a repro code putting under the light the potential issue.
Is this what you had in mind or did I misunderstand the intended implementation?

public interface IProxyConfig
{
    string Id { get { return Guid.NewGuid().ToString(); } }

    IReadOnlyList<RouteConfig> Routes { get; }
    IReadOnlyList<ClusterConfig> Clusters { get; }
    IChangeToken ChangeToken { get; }
}

private class SomeInMemoryConfig : IProxyConfig
{
    private readonly CancellationTokenSource _cts = new CancellationTokenSource();

    public SomeInMemoryConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
    {
        Routes = routes;
        Clusters = clusters;
        ChangeToken = new CancellationChangeToken(_cts.Token);
    }

    public IReadOnlyList<RouteConfig> Routes { get; }

    public IReadOnlyList<ClusterConfig> Clusters { get; }

    public IChangeToken ChangeToken { get; }

    internal void SignalChange()
    {
        _cts.Cancel();
    }
}

[Fact]
public void test()
{
    var old = new SomeInMemoryConfig(new List<RouteConfig>(), new List<ClusterConfig>());

    var proxyConfig = old as IProxyConfig;
    output.WriteLine(proxyConfig.Id); // => 9e934246-cab8-48b0-9002-7f228fbd459d
    output.WriteLine(proxyConfig.Id); // => 710fb8a8-daa4-434a-8ecb-08cec511846c
}

Copy link
Member

Choose a reason for hiding this comment

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

Try: string Id { get } = Guid.NewGuid().ToString();

That way it's initialized once when the object is created and then remains stable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Tratcher I may be missing something...

The compiler is complaining with "CS8053 Instance properties in interfaces cannot have initializers."

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Tratcher any feedback regarding my previous comment about not being able to initialize the property once when targeting the interface?

Copy link
Member

Choose a reason for hiding this comment

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

You can use a trick like this:

private static readonly ConditionalWeakTable<IProxyConfig, string> _revisionIdsTable = new();

string? RevisionId => _revisionIdsTable.GetValue(this, static _ => Guid.NewGuid().ToString());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@MihaZupan Thanks for the (really great!) hint. That did the trick.

RevisionId is now non nullable.

src/ReverseProxy/Configuration/InMemoryConfigProvider.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Management/ProxyConfigManager.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Management/ProxyConfigManager.cs Outdated Show resolved Hide resolved
@adityamandaleeka
Copy link
Member

@nulltoken Thanks for submitting this. We're generally happy with the approach, and we intend to take this PR. We'll do some more review and suggest a few minor changes (naming, API design, etc.).

src/ReverseProxy/Configuration/IProxyConfig.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Management/ProxyConfigManager.cs Outdated Show resolved Hide resolved
@nulltoken
Copy link
Contributor Author

@MihaZupan @adityamandaleeka Thanks for the comments. Unfortunately, I won't be able to work on this for the next two weeks. I'll ping you when I'm done.

@nulltoken nulltoken force-pushed the ntk/config_notif branch 2 times, most recently from 1861b59 to 9195ea4 Compare August 16, 2022 10:22
@nulltoken
Copy link
Contributor Author

@MihaZupan @adityamandaleeka Thanks for the comments. Unfortunately, I won't be able to work on this for the next two weeks. I'll ping you when I'm done.

@MihaZupan @adityamandaleeka 👋 I believe all the comments have been dealt with.

Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

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

Thanks @nulltoken

src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/InMemoryConfigProvider.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Management/ProxyConfigManager.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/InMemoryConfigProvider.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IProxyConfig.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/InMemoryConfigProvider.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Management/ProxyConfigManager.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigNotifier.cs Outdated Show resolved Hide resolved
@nulltoken nulltoken force-pushed the ntk/config_notif branch 3 times, most recently from 3f44dcd to aecee74 Compare August 18, 2022 12:56
@nulltoken nulltoken requested review from Tratcher and MihaZupan and removed request for adityamandaleeka, Tratcher and MihaZupan August 18, 2022 13:03
@nulltoken
Copy link
Contributor Author

@Tratcher @MihaZupan Comments have been dealt with. Ready for another pass.

@nulltoken nulltoken changed the title Expose IConfigNotifier callback service Expose IConfigChangeNotifier callback service Aug 18, 2022
Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

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

Thanks for the work here nulltoken and for the patience through reviews.

I think the changes here look good, my only concern is with the interface name.

I'm not a fan of *Notifier given that the interface is being notified, not doing the notifying. Could be something like Listener/Subscriber/Observer/Consumer instead?

Prior art: IClusterChangeListener

I'll bring it up to the team and we should have a decision today either way.

src/ReverseProxy/Management/ProxyConfigManager.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Management/ProxyConfigManager.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Management/ProxyConfigManager.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Configuration/IConfigChangeNotifier.cs Outdated Show resolved Hide resolved
@nulltoken
Copy link
Contributor Author

I think the changes here look good, my only concern is with the interface name.

I'm not a fan of *Notifier given that the interface is being notified, not doing the notifying. Could be something like Subscriber/Observer/Consumer instead? I'll bring it up to the team and we should have a decision today either way.

@MihaZupan No problem. Let me know what's the team's decision regarding the name and I'll update it.

@nulltoken nulltoken changed the title Expose IConfigChangeNotifier callback service Expose IConfigChangeListener callback service Aug 19, 2022
Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

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

Thanks @nulltoken

@MihaZupan MihaZupan added this to the YARP 2.0.0 milestone Aug 19, 2022
@MihaZupan MihaZupan merged commit 9253842 into microsoft:main Aug 19, 2022
@nulltoken nulltoken deleted the ntk/config_notif branch August 19, 2022 18:36
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.

[Question/feature request] How to know if programmatically loaded configuration has been applied or not?
5 participants