-
Notifications
You must be signed in to change notification settings - Fork 95
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
Memory Leak in the services (GoogleAdsServiceClientBase) #355
Comments
@cbeauchemin I'll investigate this further, but my understanding is that Channels are expensive to create, so it is recommended that we leave them alone and do not shut them down when a service goes out of scope. @jskeet @jtattermusch can you please confirm? |
Assuming this is Grpc.Core, my understanding is that unless there are unique channel options coming into play (which may be part of the additional code in the Ads client), new .NET But even so, in general I would recommend keeping a Channel alive for as long as possible, rather than deliberately shutting them down and recreating them. |
Ok thanks for answering quickly. As explained in the first description, i create a GoogleAdsClient and GoogleAdsConfig each time i process something for a client. For that client, i use 2 or 3 services. Those services asks the GoogleAdsConfig to Create them a credential object. Credential objects are never left alone and never garbage collected. The solution to close the Channel was the only solution i found to make it happen. Otherwise, memory is crashing the service. Option to not cache channel should not create a leak. Actually, with my modified version of your library, i have no problem anymore. It should be the decision of the developper to close the channel or not. Maybe the Dispose pattern i not the right way. Just a function inside the servicebase could work for me. I am open to solutions/avenues but keeping a Credential object that is out of date is not an option for me. (100000 clients * (size of the interceptors in memory + size of the credential object in memory + size of the channel in memory)) |
Oh... I didn't realize that it caused your server to eventually crash... I'll take another look at the code to see how best to integrate your suggestion. |
By Also, it would be good to know how the credentials get applied to the underlying grpc |
Yes, its really GoogleAuthInterceptors. I did not created classes with that name. The credential i created is derived from Google.Apis.Auth.OAuth2.ICredential. If you can, provide me a safe way to send you my code so you can have a better idea. Thanks Claude |
Feel free to send an e-mail to the e-mail address associated with my github account (can be found e.g. int the git commit log of https://github.com/grpc/grpc/commits/master). Otherwise, just knowing how the Google.Apis.Auth.OAuth2.ICredential credential you created is passed to the client library would probably be enough. |
Hi Jan, |
Hi Jan, |
@cbeauchemin based on the code you provided, it seems that this is pretty much the place where gRPC
The way the credential objects are applied to the grpc channels is here:
ChannelCredentials ).
The To react to the question from @AnashOommen ( #355 (comment)) The channels that get created should eventually be cleaned up (by calling channel.ShutdownAsync()), otherwise the resources associated with the channel will not get released (and it looks like a leak). So never calling ShutdownAsync() is basically a bug. |
@jtattermusch I'll fix this in that case. One question though -- does channel.ShutdownAsync() exist in grpc-dotnet? If not, this fix can be pointless in the future... @jskeet is cross-library channel.ShutdownAsync() something you'd expose in Gax.Grpc? |
Yes, ChannelBase.ShutdownAsync exists: https://github.com/grpc/grpc/blob/master/src/csharp/Grpc.Core.Api/ChannelBase.cs#L65 |
@AnashOommen in grpc-dotnet the Note that GrpcChannel from grpc-dotnet implements ChannelBase, but currently when you invoke channelBase.ShutdownAsync() on a grpc-dotnet channel, that will basically fall back to the default implementation here (noop): https://github.com/grpc/grpc/blob/d303f81ab4775da2eb1f40055f716ded24a4e7f7/src/csharp/Grpc.Core.Api/ChannelBase.cs#L72 |
@cbeauchemin I'll incorporate your fix in the next library release (along with v10_1 API release). |
@AnashOommen Great news! |
This is fixed in 12.0.0. |
Describe the bug:
We are actually using the Google Ads library for a great number of client. Each of those clients have their own set of credentials to use. Our app is not responsible of refreshing those token (it can but its not what we usually do). Another application is responsible for creating the initial refresh token and can refresh the access token when asked to. Basically, when our app need a credential, we ask it from a central application and it give us back the access token we need. The way the GoogleAdsClient is done is more oriented toward using it for one set of credential and for one client. Proof of that is that the class GoogleAdsConfig contain a field named RefreshToken. The class GoogleAdsClient is built using a GoogleAdsConfig. Of course, we have the possibility to new up some GoogleAdsClient each time we need one and that is the way we actually use it. So new GoogleAdsClient and new GoogleAdsConfig each time we need one. The problem we faced in the past is that Google was caching the channels it create for each of the services under a GoogleAdsClient. As thing progressed, when the library v7 was released, we could set the configuration property UseChannelCache to false to prevent services to stack up in the global static caching variable for them. We don’t want to cache things because credential could change and our app is really not responsible for that.
We made our own GoogleAdsConfig derived from the original one as described in this link https://developers.google.com/google-ads/api/docs/client-libs/dotnet/performance.
Using the technique described, we were having the control upon where the credential was coming from when it was requested and in the same time, re-use the same access token among all our systems for the same client instead of emitting a new access token for each service each time we need one.
The actual problem we are facing is that even when setting ChannelCaching to false, there seem to be a leak from somewhere in the GoogleAdsLibrary. The UserCredential emitted by the GoogleAdsConfig get trapped in the GoogleAuthInterceptors. Those interceptor are kept alive by each service channel.
Steps to Reproduce:
Use a derived GoogleAdsConfig that redefine the CreateCredentials
`
`
instanciate a lot of GoogleAdsClient with new GoogleAdsConfig each time.
Do this like 100 times
You will see that even if the GoogleAdsClient and GoogleAdsConfig get garbage collected, the Credential stay there
Solution Proposed
I managed to fix it by shutdowning the channel inside of the services generated by the client.
I copied all the source code to make my test so I am sure of the result. Here is my suggestions:
GoogleAdsServiceClientBase:
Keep track of the Channel created by the GoogleAdsServiceClientFactory and implement the IDisposable that will call Channel.ShutdownAsync().Wait(); when disposed
GoogleAdsServiceClientFactory:
When the function GetService is called, set the GoogleAdsServiceClientBase property to keep track of the channel. Also, the line : .Intercept(GoogleAdsGrpcInterceptor.GetInstance(config));
Create a static interceptor for the first GoogleAdsConfig created. I should be new each time because configuration instance is not always the same. So I would do instead: .Intercept(new GoogleAdsGrpcInterceptor (config));
Expected behavior:
Grips to the UserCredential get release by the channel/interceptor so it can be garbage collected in the same time as the GoogleAdsClient
Client library version and API version:
Client library version: 10.2.0
Google Ads API version: 9
.NET version: 5.0
Operating system (Linux, Windows, ...) and version (if the bug is platform-specific): Windows
Request/Response Logs:
Not related to that part
The text was updated successfully, but these errors were encountered: