Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

GraphServiceClient creates ActivitySource objects that are never disposed

オープン
#3,147 コメント 0 件 リアクション 2 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
52/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
静か
技術スタック
azure, csharp

調査の方向性

GraphServiceClient の破棄から始め、BaseGraphRequestAdapter、AzureIdentityAuthenticationProvider、AzureIdentityAccessTokenProvider を通るチェーンを追跡します。クライアントの作成と破棄を繰り返す、提供されたコンソール再現コードを実行し、破棄後に Microsoft.Kiota.Authentication.Azure ActivitySource のカウントがゼロに戻ることを確認します。クライアントが所有する認証リソースが workaround を必要とせずに解放されれば完了です。

索引モデルが issue の本文から書いたものです。

説明

status:waiting-for-triage type:bug
Describe the bug

In our Web application, we use the .NET Graph SDK and create GraphServiceClient objects in many requests. These clients are created by scoped services, and disposed when the associated scope is disposed.

We have seen a memory and CPU increase in our web application over days. A memory dump analysis showed that a huge number of ActivitySource objects with name Microsoft.Kiota.Authentication.Azure are created and never released. Here is the result of the command dotnet-dump analyze <file>.dmp, then dumpheap -stat -live:

Image

Most of these 644,957 ActivitySource objects are named Microsoft.Kiota.Authentication.Azure:

Image

Some more investigation showed that one new ActivitySource is created each time a GraphServiceClient is created.

Expected behavior

A singleton ActivitySource object is created, or the ActivitySource objects are disposed when GraphServiceClient is disposed.

How to reproduce

Create a repro project with the commands

dotnet new console
dotnet add package Microsoft.Graph Azure.Identity

Set the Program.cs source code as:

using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Authentication;
using Microsoft.Kiota.Abstractions.Authentication;
using System.Diagnostics;
using System.Reflection;

var scopes = new[] { "User.Read" };

var options = new InteractiveBrowserCredentialOptions
{
    TenantId = "YOUR_TENANT_ID",
    ClientId = "YOUR_CLIENT_ID",
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
    RedirectUri = new Uri("http://localhost"),
};

var interactiveCredential = new InteractiveBrowserCredential(options);

await GetCurrentUserUserNameAsync(1);
await GetCurrentUserUserNameAsync(2);

async Task GetCurrentUserUserNameAsync(int iteration)
{
    PrintMicrosoftKiotaAbstractionsActivitySourceCount($"Iteration {iteration}: Before creating GraphServiceClient.");
    using (var graphServiceClient = GetGraphServiceClient(scopes, interactiveCredential, useWorkaround: false, out IDisposable? additionalDisposable))
    {
        var user = await graphServiceClient.Me.GetAsync();
        Console.WriteLine($"Hello, {user?.DisplayName}!");
        PrintMicrosoftKiotaAbstractionsActivitySourceCount($"Iteration {iteration}: After request, before disposing GraphServiceClient.");
        additionalDisposable?.Dispose();
    }
    PrintMicrosoftKiotaAbstractionsActivitySourceCount($"Iteration {iteration}: After disposing GraphServiceClient.");
}

void PrintMicrosoftKiotaAbstractionsActivitySourceCount(string message)
{
    FieldInfo? activeSourcesField = typeof(ActivitySource).GetField("s_activeSources", BindingFlags.NonPublic | BindingFlags.Static);
    object? synchronizedListInstance = activeSourcesField?.GetValue(null) ?? throw new InvalidOperationException("s_activeSources field not found.");
    Type synchronizedListType = synchronizedListInstance.GetType();
    FieldInfo? arrayField = synchronizedListType.GetField("_volatileArray", BindingFlags.NonPublic | BindingFlags.Instance);
    var sources = (ActivitySource[])(arrayField?.GetValue(synchronizedListInstance) ?? throw new InvalidOperationException("_volatileArray field not found."));
    var count = sources.Count((ActivitySource s) => s.Name == "Microsoft.Kiota.Authentication.Azure");
    Console.WriteLine($"{message} There are {count} activity sources named 'Microsoft.Kiota.Authentication.Azure'.");
}

static GraphServiceClient GetGraphServiceClient(string[] scopes, InteractiveBrowserCredential interactiveCredential, bool useWorkaround, out IDisposable? additionalDisposable)
{
    additionalDisposable = null;
    if (useWorkaround)
    {
        var appOnlyAccessTokenProvider = new AzureIdentityAccessTokenProvider(interactiveCredential, null, null, true);
        var tokenAuthenticationProvider = new BaseBearerTokenAuthenticationProvider(appOnlyAccessTokenProvider);
        additionalDisposable = appOnlyAccessTokenProvider;
        return new GraphServiceClient(tokenAuthenticationProvider);
    }
    else
    {
        return new GraphServiceClient(interactiveCredential, scopes);
    }
}

In an Entra ID tenant, create an Entra ID application with:

  • Supported account types: Single tenant only
  • Redirect URI: Public client/native, URI: http://localhost

Copy the Client ID and Tenant IDs into the Program.cs source code and run the application with:

dotnet run

The application Creates twice a GraphServiceClient instance and displays the name of the current user. It also uses reflection to show the number of ActivitySource objects named Microsoft.Kiota.Authentication.Azure being created. The output looks like:

Iteration 1: Before creating GraphServiceClient. There are 0 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Hello, <your name>!
Iteration 1: After request, before disposing GraphServiceClient. There are 1 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Iteration 1: After disposing GraphServiceClient. There are 1 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Iteration 2: Before creating GraphServiceClient. There are 1 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Hello, <your name>!
Iteration 2: After request, before disposing GraphServiceClient. There are 2 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Iteration 2: After disposing GraphServiceClient. There are 2 activity sources named 'Microsoft.Kiota.Authentication.Azure'.

As can be seen, two ActivitySource objects have been created and will never be released, and one more will be added each time a new GraphServiceClient object is created, even if it's properly disposed.

SDK Version

6.2.0

Latest version known to work for scenario above?

No response

Known Workarounds

The workaround is to create explicitly the Microsoft.Graph.Authentication.AzureIdentityAccessTokenProvider and dispose it. In that case the ActivitySource is disposed properly. This can be seen in the sample code by setting useWorkaround: true instead of useWorkaround: false, The output is then:

Iteration 1: Before creating GraphServiceClient. There are 0 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Hello, <your name>!
Iteration 1: After request, before disposing GraphServiceClient. There are 1 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Iteration 1: After disposing GraphServiceClient. There are 0 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Iteration 2: Before creating GraphServiceClient. There are 0 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Hello, <your name>!
Iteration 2: After request, before disposing GraphServiceClient. There are 1 activity sources named 'Microsoft.Kiota.Authentication.Azure'.
Iteration 2: After disposing GraphServiceClient. There are 0 activity sources named 'Microsoft.Kiota.Authentication.Azure'.

We see that the ActivitySource are created with the same lifetime as the GraphServiceClient, and are properly disposed.

Debug output

See above for details

Configuration
  • OS: Windows 11
  • Platform: .NET 10
  • Architecture: x64

The problem is reproducible in our Azure App Service as well as on local developer laptops.

Other information

The GraphServiceClient constructor public GraphServiceClient(IRequestAdapter requestAdapter, string baseUrl = null) constructs the following:

  • a GraphServiceClient instance with:
    • property RequestAdapter of type Microsoft.Kiota.Abstractions.IRequestAdapter. Runtime type: Microsoft.Graph.BaseGraphRequestAdapter object
    • implements IDisposable and properly disposes RequestAdapter
  • The Microsoft.Graph.BaseGraphRequestAdapter instance:
    • has a field authProvider of type Microsoft.Kiota.Abstractions.Authentication.IAuthenticationProvider. Runtime type: Microsoft.Graph.Authentication.AzureIdentityAuthenticationProvider
    • implements IDisposable but Dispose() doesn't dispose authProvider.
  • The Microsoft.Graph.Authentication.AzureIdentityAuthenticationProvider instance
    • has a property AccessTokenProvider of type Microsoft.Kiota.Abstractions.Authentication.IAccessTokenProvider. Runtime type: Microsoft.Graph.Authentication.AzureIdentityAccessTokenProvider
    • doesn't implement IDisposable, so cannot dispose AccessTokenProvider
  • The Microsoft.Graph.Authentication.AzureIdentityAccessTokenProvider instance
    • has a field _activitySource of type System.Diagnostics.ActivitySource, named Microsoft.Kiota.Authentication.Azure
    • implements IDisposable and properly disposes _activitySource

Fix

  • Microsoft.Graph.BaseGraphRequestAdapter must dispose authProvider
  • Microsoft.Graph.Authentication.AzureIdentityAccessTokenProvider must implement IDisposable and dispose authProvider

Alternatively, the ActivitySource named Microsoft.Kiota.Authentication.Azure could be created as a static readonly field and be reused by all Microsoft.Graph.Authentication.AzureIdentityAccessTokenProvider instances.

主要言語
C#
スター
791
フォーク
264
平均マージ
5時間 25分
マージ済み PR(30日)
5

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

microsoftgraph/msgraph-sdk-dotnet のほかの issue

microsoftgraph/msgraph-sdk-dotnet の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。