Google DriveService C # утечка объектов HttpConnection - PullRequest
0 голосов
/ 01 ноября 2018

У меня есть приложение, которое использует сервис Google Drive с использованием C # SDK. Я использую учетные данные учетной записи службы, с олицетворением, чтобы указать пользователя, от имени которого я действую.

Проблема, с которой я сталкиваюсь, заключается в том, что мне нужно создать новый DriveService для каждого олицетворенного пользователя, и каждый из этих объектов DriveService создает 2 объекта HttpConnection, которые НЕ очищаются при удалении () Dis Drive () DriveService. Я сделал это в модульном тесте. Я создаю DriveService в этом методе:

public DriveService GetDriveService(string email)
    {
        var serviceAccountEmail = "service account email";
        var privateKey = "private key";

        var serviceAcctCreds =  new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
        {
            User = email,
            Scopes = "scopes" }), 
        }.FromPrivateKey(privateKey));

        var driveService = new DriveService(new BaseClientService.Initializer
        { 
            HttpClientInitializer = serviceAcctCreds,
            ApplicationName = "app name",
        });

        return driveService;
    }

Тогда используйте это так:

public async Task DoTheGDriveMemoryTest()
        {
            var userEmails = new List<string>
            {
                 "email1@example.com", "email2@example.com"
            };
            var i = 1;
            while (true)
            {
                foreach (var email in userEmails)
                {
                    //recommend using Fiddler Autoresponders here
                    using (var service = GetDriveService(email))
                    {
                        var request = service.Changes.List("delta id");                        
                        try
                        {
                            var result = await request.ExecuteAsync();
                        }
                        catch (Exception e)
                        {
                            //do nothing
                        }
                    }
                }
            }
        }

Это приводит к тому, что в памяти хранится постоянно растущее количество объектов HttpConnection, которые не очищаются сборщиком мусора.

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

Как сказал Майк М., это известная проблема с HttpClient в том, что его метод Dispose() на самом деле не избавляется от экземпляра.

К сожалению, поскольку библиотека Google.Apis.Auth связывает вместе HttpClient и учетные данные, я не могу найти хороший обходной путь для этого.

Вот очень хакерский обходной путь, который позволяет вам совместно использовать один HttpClient между всеми RPC. Определить пользовательский IHttpClientFactory:

class SharedHttpClientFactory : IHttpClientFactory
{
    public SharedHttpClientFactory()
    {
        _handler = new ConfigurableMessageHandler(new HttpClientHandler());
        _client = new ConfigurableHttpClient(_handler);
    }

    private readonly ConfigurableMessageHandler _handler;
    private readonly ConfigurableHttpClient _client;
    private Action _clearHandlers = null;

    public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args)
    {
        _clearHandlers?.Invoke();
        var executeInterceptors = args.Initializers.OfType<IHttpExecuteInterceptor>().ToList();
        var unsuccessfulResponseHandlers = args.Initializers.OfType<IHttpUnsuccessfulResponseHandler>().ToList();
        _clearHandlers = () =>
        {
            foreach (var executeInterceptor in executeInterceptors)
            {
                _handler.RemoveExecuteInterceptor(executeInterceptor);
            }
            foreach (var unsuccssfulResponseHandler in unsuccessfulResponseHandlers)
            {
                _handler.RemoveUnsuccessfulResponseHandler(unsuccssfulResponseHandler);
            }
        };
        foreach (var executeInterceptor in executeInterceptors)
        {
            _handler.AddExecuteInterceptor(executeInterceptor);
        }
        foreach (var unsuccssfulResponseHandler in unsuccessfulResponseHandlers)
        {
            _handler.AddUnsuccessfulResponseHandler(unsuccssfulResponseHandler);
        }
        _handler.ApplicationName = args.ApplicationName;
        return _client;
    }
}

Создайте один экземпляр этой фабрики:

var factory = new SharedHttpClientFactory();

Затем создайте каждый DriveService, используя эту фабрику:

var client = new StorageService(new Google.Apis.Services.BaseClientService.Initializer
{
    HttpClientFactory = factory,
    HttpClientInitializer = serviceAcctCreds
});

Предостережения предостаточно:

  • Не выбрасывайте DriveService, поскольку это будет распоряжаться базовым общим HttpClient.
  • Все экземпляры DriveService имеют одинаковые учетные данные; эти учетные данные изменяются каждый раз, когда создается новый DriveService. Это означает, что все предыдущие экземпляры также имеют обновленные учетные данные.
  • Учетные данные, передаваемые в инициализатор HttpClientInitializer свойство , должны быть экземпляром ServiceCredential (как в вашем коде). Это не будет работать, если это GoogleCredential экземпляр.
  • Все это на самом деле не рекомендуется, это определенно неприятный хак, но я не вижу лучшего / более простого обходного пути.
0 голосов
/ 01 ноября 2018

Если DriveService использует HttpClient под прикрытием, вы можете столкнуться с утилизацией, не работающей с общим объектом, или с подключениями, удерживаемыми до истечения времени ожидания.

Но HttpClient отличается. Хотя он реализует интерфейс IDisposable, на самом деле он является общим объектом.

См. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

...