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

#1634 #1487 #1329 #1304 #1294 #793 Consul polling of services: enhancements and fix errors #1670

Merged
merged 26 commits into from
Sep 29, 2023

Conversation

ggnaegi
Copy link
Member

@ggnaegi ggnaegi commented Jun 20, 2023

Fixes #1634 #1487 #1329 #1304 #1294 #793

Proposing some improvements for Consul "polling"

Proposed Changes

  • Instead of using a timer, checking if a deadline (in ms) has been reached when getting the services. If the deadline has been reached, then getting the services from Consul, otherwise returning the existing list.
  • Using a pool of discovery providers (1 discovery provider per service name) instead of initializing a new discovery provider for each route
  • Handling threading issues with locks in PollConsul and in the factory.

@ggnaegi ggnaegi changed the title Bug/polling consul #1634 Bug/polling consul Jun 20, 2023
@raman-m
Copy link
Member

raman-m commented Jun 22, 2023

Nice PR, Guillaume! 😸

You forgot to request my review. 😉
And I will review in a couple of work days...

@raman-m raman-m changed the title #1634 Bug/polling consul #1634 Consul polling of services: enhancements and fix errors Jun 22, 2023
@raman-m raman-m added bug Identified as a potential bug feature A new feature labels Jun 22, 2023
@ggnaegi
Copy link
Member Author

ggnaegi commented Jun 22, 2023

@raman-m I can't add you as a reviewer...

@raman-m raman-m self-requested a review June 23, 2023 10:30
Copy link
Member

@raman-m raman-m left a comment

Choose a reason for hiding this comment

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

Everything is fine in this PR except the SemaphoreSlim class usage.
According to the Remarks for SemaphoreSlim:

Semaphores are of two types: local semaphores and named system semaphores. Local semaphores are local to an application, system semaphores are visible throughout the operating system and are suitable for inter-process synchronization.

I see that we need not system semaphore and you just use local semaphore functionality in this PR. So, and I believe we can switch to classic lock-operator to manage access for _services collection.
Another option is just using of any thread-safe collections.

src/Ocelot.Provider.Consul/ConsulProviderFactory.cs Outdated Show resolved Hide resolved
src/Ocelot.Provider.Consul/ConsulProviderFactory.cs Outdated Show resolved Hide resolved
src/Ocelot.Provider.Consul/PollConsul.cs Outdated Show resolved Hide resolved
@raman-m raman-m added the waiting Waiting for answer to question or feedback from issue raiser label Jun 26, 2023
@ggnaegi
Copy link
Member Author

ggnaegi commented Jun 26, 2023

@raman-m Just checked the changes (with lock) in a docker container. It works

@ggnaegi ggnaegi requested a review from raman-m June 26, 2023 20:47
raman-m
raman-m previously approved these changes Jun 27, 2023
Copy link
Member

@raman-m raman-m left a comment

Choose a reason for hiding this comment

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

Thanks! Good job!

If you don't mind, I will make some code readability improvements...

@ggnaegi
Copy link
Member Author

ggnaegi commented Jun 27, 2023

If you don't mind, I will make some code readability improvements...

What do you mean? Maybe in the future an editorconfig file would help?
https://learn.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options?view=vs-2022

@raman-m
Copy link
Member

raman-m commented Jun 27, 2023

What do you mean? Maybe in the future an editorconfig file would help?
Docs: Create portable, custom editor settings with EditorConfig

Interesting Visual Studio feature, but not now!

I meant the improvements in your code to read. Not in entire project! 🤣
You will see...

@ggnaegi
Copy link
Member Author

ggnaegi commented Jun 27, 2023

I meant the improvements in your code to read. Not in entire project! 🤣 You will see...

Ok, but, in general, it would be a nice addition.

raman-m
raman-m previously approved these changes Jun 27, 2023
Copy link
Member

@raman-m raman-m left a comment

Choose a reason for hiding this comment

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

+ Approved once again

I've made some code improvements with a little refactoring, plus updated unit tests.


I'm just thinking...
Should we update the documentation or not?
There are the same Consul and PollConsul types of ServiceDiscoveryProvider(s).
Maybe it makes sense to emphasize that Consul is default type provider even if type is not specified (in case of .AddConsul())...
What do you think?

@raman-m raman-m added accepted Bug or feature would be accepted as a PR or is being worked on and removed waiting Waiting for answer to question or feedback from issue raiser labels Jun 27, 2023
@raman-m raman-m requested a review from wast June 27, 2023 18:14
@ggnaegi
Copy link
Member Author

ggnaegi commented Jun 27, 2023

raman-m left a comment

Maybe it makes sense to emphasize that Consul is default type provider even if type is not specified (in case of .AddConsul())...

Yeah, it makes sense

@ggnaegi
Copy link
Member Author

ggnaegi commented Jun 27, 2023

@raman-m So, I thought we could do the same with the kubernetes provider factory, like Eureka and Consul...

@raman-m
Copy link
Member

raman-m commented Jun 28, 2023

@raman-m So, I thought we could do the same with the kubernetes provider factory, like Eureka and Consul...

Yes, we could!
It seems I have to review once again... 😫

@ggnaegi
Copy link
Member Author

ggnaegi commented Jun 28, 2023

@raman-m commented on Jun 28

😅 Yeah, sorry about this. I just modified that bit, since it seems logical to me, that the "getter logic" should be applied everywhere.

Copy link
Member

@raman-m raman-m left a comment

Choose a reason for hiding this comment

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

Please, fix these minor issues 👇

@ggnaegi
Copy link
Member Author

ggnaegi commented Jun 29, 2023

@raman-m Hello, it's done...

@RaynaldM RaynaldM self-requested a review September 27, 2023 08:02
RaynaldM
RaynaldM previously approved these changes Sep 27, 2023
@ggnaegi
Copy link
Member Author

ggnaegi commented Sep 27, 2023

@raman-m @RaynaldM I might have a solution to generalize the "polling" for Consul, Kubernetes and Eureka... Should I push it, or would you like to merge first and then open a new PR?

using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Providers;

namespace Ocelot.Polling;

public class PollingServicesManager<T, TU>
    where T : class, IServiceDiscoveryProvider
    where TU : ServicePollingHandler<T>
{
    private readonly object _lockObject = new();
    private readonly List<ServicePollingHandler<T>> _serviceDiscoveryProviders = new();

    public ServicePollingHandler<T> GetServicePollingHandler(T baseProvider, string serviceName, int pollingInterval,
        IOcelotLoggerFactory factory)
    {
        lock (_lockObject)
        {
            var discoveryProvider = _serviceDiscoveryProviders.FirstOrDefault(x => x.ServiceName == serviceName);
            if (discoveryProvider != null)
            {
                return discoveryProvider;
            }

            discoveryProvider =
                (TU)Activator.CreateInstance(typeof(TU), baseProvider, pollingInterval, serviceName, factory);
            _serviceDiscoveryProviders.Add(discoveryProvider);

            return (TU)discoveryProvider;
        }
    }
}
using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;

namespace Ocelot.Polling;

public abstract class ServicePollingHandler<T> : IServiceDiscoveryProvider
    where T : class, IServiceDiscoveryProvider
{
    private readonly T _baseProvider;
    private readonly object _lockObject = new();
    private readonly IOcelotLogger _logger;
    private readonly int _pollingInterval;

    private DateTime _lastUpdateTime;
    private List<Service> _services;

    protected ServicePollingHandler(T baseProvider, int pollingInterval, string serviceName,
        IOcelotLoggerFactory factory)
    {
        _logger = factory.CreateLogger<ServicePollingHandler<T>>();
        _pollingInterval = pollingInterval;

        // Initialize by DateTime.MinValue as lowest value.
        // Polling will occur immediately during the first call
        _lastUpdateTime = DateTime.MinValue;
        _services = new List<Service>();
        ServiceName = serviceName;
        _baseProvider = baseProvider;
    }

    public string ServiceName { get; protected set; }

    public Task<List<Service>> Get()
    {
        lock (_lockObject)
        {
            var refreshTime = _lastUpdateTime.AddMilliseconds(_pollingInterval);

            // Check if any services available
            if (refreshTime >= DateTime.UtcNow && _services.Any())
            {
                return Task.FromResult(_services);
            }

            _logger.LogInformation($"Retrieving new client information for service: {ServiceName}.");
            _services = _baseProvider.Get().Result;
            _lastUpdateTime = DateTime.UtcNow;

            return Task.FromResult(_services);
        }
    }
}
using Ocelot.Logging;
using Ocelot.Polling;

namespace Ocelot.Provider.Consul;

public class PollConsul : ServicePollingHandler<Consul>
{
    public PollConsul(Consul baseProvider, int pollingInterval, string serviceName, IOcelotLoggerFactory factory) : base(baseProvider, pollingInterval, serviceName, factory)
    {
    }
}
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.Polling;
using Ocelot.ServiceDiscovery.Providers;

namespace Ocelot.Provider.Consul;

public static class ConsulProviderFactory
{
    /// <summary>
    ///     String constant used for provider type definition.
    /// </summary>
    public const string PollConsul = nameof(Provider.Consul.PollConsul);
    private static readonly PollingServicesManager<Consul, PollConsul> ServicesManager = new();

    public static ServiceDiscoveryFinderDelegate Get { get; } = CreateProvider;

    private static IServiceDiscoveryProvider CreateProvider(IServiceProvider provider,
        ServiceProviderConfiguration config, DownstreamRoute route)
    {
        var factory = provider.GetService<IOcelotLoggerFactory>();
        var consulFactory = provider.GetService<IConsulClientFactory>();

        var consulRegistryConfiguration = new ConsulRegistryConfiguration(
            config.Scheme, config.Host, config.Port, route.ServiceName, config.Token);

        var consulProvider = new Consul(consulRegistryConfiguration, factory, consulFactory);

        if (PollConsul.Equals(config.Type, StringComparison.OrdinalIgnoreCase))
        {
            return ServicesManager.GetServicePollingHandler(consulProvider, route.ServiceName, config.PollingInterval, factory);
        }

        return consulProvider;
    }
}

@raman-m
Copy link
Member

raman-m commented Sep 27, 2023

@ggnaegi commented on Sep 27, 1:38pm:

I might have a solution to generalize the "polling" for Consul, Kubernetes and Eureka...
Should I push it, or would you like to merge first and then open a new PR?

Thank you for your intention to improve the code quality! But not in this PR please which is related to Consul services problems.
Let's create a separate follow up PR, please...
I am afraid this PR cannot be included into September's release if we have (will have) multiple review for a 3 month and more. Multiple code reviews makes delivery very slow.
And, improvements for other service discovery providers can be included in October's release, not urgent.
So, delivery of Consul improvements is very important in current upcoming release. The PR will close 6 issues! 😻

@raman-m
Copy link
Member

raman-m commented Sep 27, 2023

@ggnaegi commented on Sep 26

I would create an new PR per Provider later, ok?

Totally 🆗 😉 See my opinion above plz! ☝️
But not per provider! I believe these improvements can be combined into one PR...

@raman-m
Copy link
Member

raman-m commented Sep 28, 2023

@ggnaegi
What are we waiting for?
What is the rest of work?

@ggnaegi
Copy link
Member Author

ggnaegi commented Sep 28, 2023

@ggnaegi What are we waiting for? What is the rest of work?

I don't know, you said docs?

@raman-m
Copy link
Member

raman-m commented Sep 28, 2023

@ggnaegi commented on Sep 28

Yeah, I see:

@raman-m commented on Jun 29

Will update docs soon...

@ggnaegi
Copy link
Member Author

ggnaegi commented Sep 28, 2023

@raman-m so, first merge to develop then update the docs?

@raman-m
Copy link
Member

raman-m commented Sep 29, 2023

@ggnaegi commented:
so, first merge to develop then update the docs?

No!
First, Update feature branch by top commits from develop
Second, Update docs by committing to feature branch
Third, I will merge to develop

I believe you did everything for this PR if you cannot (don't know how) to review docs.
I suggest you to focus on other PRs... Follow up PR for this one with refactoring other service providers (Eureka, etc.)
Or, you can look into the bug #1706

I hope, this your PR will be updated & merged right today, because I've planned to work on it today.

P.S. And, don't forget to sync fork, update develop of your forked repo.

@raman-m
Copy link
Member

raman-m commented Sep 29, 2023

If you don't mind, I will make some improvements to the code...

@ggnaegi
Copy link
Member Author

ggnaegi commented Sep 29, 2023

@raman-m what do you mean by code improvements? We already had several reviews and changes...
@raman-m You mean some missing braces? Yeah, ok, you could add them. I should modify my Resharper config.

@raman-m
Copy link
Member

raman-m commented Sep 29, 2023

Please stop committing to the branch!


@ggnaegi commented on Sep 29:
You mean some missing braces? Yeah, ok, you could add them. I should modify my Resharper config.

This explains clearly why I see StyleCop violations...
Please, don't use ReSharper for this project! All settings, style checks, code checks, IDE settings are provided for Visual Studio only!
Please, use Visual Studio instead of Resharper!
If you are able to create settings for Resharper IDE then I will welcome that.
But I guess it won't be easy to provide complete clone of settings.


Maybe it would be wise to had some code improvements later, in the next PR...

🆗 I agree on this! Going to create a next PR... after merging your one.

@ggnaegi
Copy link
Member Author

ggnaegi commented Sep 29, 2023

You mean some missing braces? Yeah, ok, you could add them. I should modify my Resharper config.

This explains clearly why I see StyleCop violations... Please, don't use ReSharper for this project! All settings, style checks, code checks, IDE settings are provided for Visual Studio only! Please, use Visual Studio instead of Resharper! If you are able to create settings for Resharper IDE then I will welcome that. But I guess it won't be easy to provide complete clone of settings.

@raman-m Yeah, Sorry, I'm using Resharper with my colleagues, I forgot that you are using StyleCop... :-)

@ggnaegi
Copy link
Member Author

ggnaegi commented Sep 29, 2023

@ThreeMammals ThreeMammals locked as too heated and limited conversation to collaborators Sep 29, 2023
@raman-m
Copy link
Member

raman-m commented Sep 29, 2023

@ggnaegi Docs has been updated by commit 1111071!
If Ray will approve, then I'm going to merge...

@raman-m
Copy link
Member

raman-m commented Sep 29, 2023

@RaynaldM Could you look at this PR finally please, going to Files changes tab?
And approve it ASAP! We should merge this today and celebrate together! 😉

var result = Wait.WaitFor(3000).Until(() =>
try
{
_result = provider.Get().GetAwaiter().GetResult();
Copy link
Member

Choose a reason for hiding this comment

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

Why do we have so strange sentence?
Can it be simplified?

@raman-m raman-m merged commit f8eb5e1 into ThreeMammals:develop Sep 29, 2023
2 checks passed
@raman-m
Copy link
Member

raman-m commented Sep 29, 2023

@ggnaegi Guillaume, congrats on merging it! 🎉
We've spent 3 months to work on that. It is a great PR! 🤝
Thank you very much for this contribution!
And, I hope you will come back to us with another PRs and discussions 😉

Happy Friday! 🥳

@raman-m raman-m added accepted Bug or feature would be accepted as a PR or is being worked on Service Discovery Ocelot feature: Service Discovery and removed needs feedback Issue is waiting on feedback before acceptance labels Sep 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted Bug or feature would be accepted as a PR or is being worked on bug Identified as a potential bug feature A new feature Service Discovery Ocelot feature: Service Discovery
Projects
None yet
3 participants