Skip to content

Commit f7af268

Browse files
Use System.Text.Json for json (de)serialization (#393)
The serialization and deserialization cost for Newtonsoft.Json causes significant slowdowns, especially in the cases of cached tokens where the process is not long lived. Swapping in System.Text.Json improves startup times. BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.23430.1000) Intel Xeon CPU E5-1650 v3 3.50GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK=8.0.100-preview.2.23157.25 [Host] : .NET 6.0.16 (6.0.1623.17311), X64 RyuJIT AVX2 Job-KGDGIP : .NET 6.0.16 (6.0.1623.17311), X64 RyuJIT AVX2 System.Text.Json: IterationCount=1 RunStrategy=ColdStart WarmupCount=0 | Method | Mean | Error | Allocated | |------------------- |-----------:|------:|-----------:| | CachedSessionToken | 460.5 ms | NA | 519.3 KB | | CachedMsalToken | 1,737.0 ms | NA | 1979.85 KB | IterationCount=3 RunStrategy=ColdStart WarmupCount=0 | Method | Mean | Error | StdDev | Median | Allocated | |------------------- |---------:|------------:|---------:|---------:|-----------:| | CachedSessionToken | 254.9 ms | 2,957.7 ms | 162.1 ms | 163.6 ms | 481.46 KB | | CachedMsalToken | 967.8 ms | 12,418.7 ms | 680.7 ms | 586.7 ms | 1889.65 KB | Newtonsoft.Json: IterationCount=1 RunStrategy=ColdStart WarmupCount=0 | Method | Mean | Error | Allocated | |------------------- |-----------:|------:|----------:| | CachedSessionToken | 586.9 ms | NA | 559.24 KB | | CachedMsalToken | 2,074.3 ms | NA | 2142 KB | IterationCount=3 RunStrategy=ColdStart WarmupCount=0 | Method | Mean | Error | StdDev | Median | Allocated | |------------------- |-----------:|------------:|-----------:|---------:|-----------:| | CachedSessionToken | 343.7 ms | 5,947.7 ms | 326.0 ms | 162.1 ms | 519.8 KB | | CachedMsalToken | 1,277.7 ms | 20,494.6 ms | 1,123.4 ms | 660.3 ms | 2064.86 KB |
1 parent 5177919 commit f7af268

File tree

6 files changed

+36
-30
lines changed

6 files changed

+36
-30
lines changed

CredentialProvider.Microsoft.VSIX/Microsoft.CredentialProvider.swr

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ folder InstallDir:\Common7\IDE\CommonExtensions\Microsoft\NuGet\Plugins\Credenti
115115
file source=$(PluginBinPath)\System.Security.SecureString.dll
116116
file source=$(PluginBinPath)\System.Text.Encoding.dll
117117
file source=$(PluginBinPath)\System.Text.Encoding.Extensions.dll
118+
file source=$(PluginBinPath)\System.Text.Encodings.Web.dll
119+
file source=$(PluginBinPath)\System.Text.Json.dll
118120
file source=$(PluginBinPath)\System.Text.RegularExpressions.dll
119121
file source=$(PluginBinPath)\System.Threading.dll
120122
file source=$(PluginBinPath)\System.Threading.Overlapped.dll

CredentialProvider.Microsoft/CredentialProvider.Microsoft.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
<PackageReference Include="NuGet.Protocol" Version="5.11.3" />
3434
<PackageReference Include="PowerArgs" Version="3.6.0" />
3535
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19554-01" PrivateAssets="All" />
36-
<PackageReference Include="Newtonsoft.json" Version="13.0.2" />
3736
<PackageReference Include="Microsoft.VisualStudioEng.MicroBuild.Core" Version="1.0.0" />
37+
<PackageReference Include="System.Text.Json" Version="6.0.7" />
3838
</ItemGroup>
3939

4040
<ItemGroup>

CredentialProvider.Microsoft/CredentialProviders/Vsts/VstsSessionTokenClient.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
using System.Net.Http;
77
using System.Net.Http.Headers;
88
using System.Text;
9+
using System.Text.Json;
910
using System.Threading;
1011
using System.Threading.Tasks;
11-
using Newtonsoft.Json;
1212
using NuGetCredentialProvider.Logging;
1313
using NuGetCredentialProvider.Util;
1414

@@ -18,6 +18,12 @@ public class VstsSessionTokenClient : IVstsSessionTokenClient
1818
{
1919
private const string TokenScope = "vso.packaging_write vso.drop_write";
2020

21+
private static readonly JsonSerializerOptions options = new JsonSerializerOptions
22+
{
23+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
24+
PropertyNameCaseInsensitive = true
25+
};
26+
2127
private readonly Uri vstsUri;
2228
private readonly string bearerToken;
2329
private readonly IAuthUtil authUtil;
@@ -45,7 +51,7 @@ private HttpRequestMessage CreateRequest(Uri uri, DateTime? validTo)
4551
};
4652

4753
request.Content = new StringContent(
48-
JsonConvert.SerializeObject(tokenRequest),
54+
JsonSerializer.Serialize(tokenRequest, options),
4955
Encoding.UTF8,
5056
"application/json");
5157

@@ -77,7 +83,6 @@ public async Task<string> CreateSessionTokenAsync(VstsTokenType tokenType, DateT
7783
string serializedResponse;
7884
if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
7985
{
80-
8186
request.Dispose();
8287
response.Dispose();
8388

@@ -97,7 +102,7 @@ public async Task<string> CreateSessionTokenAsync(VstsTokenType tokenType, DateT
97102
serializedResponse = await response.Content.ReadAsStringAsync();
98103
}
99104

100-
var responseToken = JsonConvert.DeserializeObject<VstsSessionToken>(serializedResponse);
105+
var responseToken = JsonSerializer.Deserialize<VstsSessionToken>(serializedResponse, options);
101106

102107
if (validTo.Subtract(responseToken.ValidTo.Value).TotalHours > 1.0)
103108
{

CredentialProvider.Microsoft/CredentialProviders/VstsBuildTaskServiceEndpoint/VstsBuildTaskServiceEndpointCredentialProvider.cs

+8-7
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using System.Text.Json;
8+
using System.Text.Json.Serialization;
79
using System.Threading;
810
using System.Threading.Tasks;
9-
using Newtonsoft.Json;
1011
using NuGet.Protocol.Plugins;
1112
using NuGetCredentialProvider.Util;
1213
using ILogger = NuGetCredentialProvider.Logging.ILogger;
@@ -15,17 +16,17 @@ namespace NuGetCredentialProvider.CredentialProviders.VstsBuildTaskServiceEndpoi
1516
{
1617
public class EndpointCredentials
1718
{
18-
[JsonProperty("endpoint")]
19+
[JsonPropertyName("endpoint")]
1920
public string Endpoint { get; set; }
20-
[JsonProperty("username")]
21+
[JsonPropertyName("username")]
2122
public string Username { get; set; }
22-
[JsonProperty("password")]
23+
[JsonPropertyName("password")]
2324
public string Password { get; set; }
2425
}
2526

2627
public class EndpointCredentialsContainer
2728
{
28-
[JsonProperty("endpointCredentials")]
29+
[JsonPropertyName("endpointCredentials")]
2930
public EndpointCredentials[] EndpointCredentials { get; set; }
3031
}
3132

@@ -35,7 +36,7 @@ public sealed class VstsBuildTaskServiceEndpointCredentialProvider : CredentialP
3536

3637
// Dictionary that maps an endpoint string to EndpointCredentials
3738
private Dictionary<string, EndpointCredentials> Credentials => LazyCredentials.Value;
38-
39+
3940
public VstsBuildTaskServiceEndpointCredentialProvider(ILogger logger)
4041
: base(logger)
4142
{
@@ -109,7 +110,7 @@ private Dictionary<string, EndpointCredentials> ParseJsonToDictionary()
109110
// Parse JSON from VSS_NUGET_EXTERNAL_FEED_ENDPOINTS
110111
Verbose(Resources.ParsingJson);
111112
Dictionary<string, EndpointCredentials> credsResult = new Dictionary<string, EndpointCredentials>(StringComparer.OrdinalIgnoreCase);
112-
EndpointCredentialsContainer endpointCredentials = JsonConvert.DeserializeObject<EndpointCredentialsContainer>(feedEndPointsJson);
113+
EndpointCredentialsContainer endpointCredentials = JsonSerializer.Deserialize<EndpointCredentialsContainer>(feedEndPointsJson);
113114
if (endpointCredentials == null)
114115
{
115116
Verbose(Resources.NoEndpointsFound);

CredentialProvider.Microsoft/Program.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
using System;
66
using System.Collections.Generic;
77
using System.IO;
8+
using System.Text.Json;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.Artifacts.Authentication;
11-
using Newtonsoft.Json;
1212
using NuGet.Common;
1313
using NuGet.Protocol.Plugins;
1414
using NuGetCredentialProvider.CredentialProviders;
@@ -165,7 +165,7 @@ public static async Task<int> Main(string[] args)
165165
if (parsedArgs.OutputFormat == OutputFormat.Json)
166166
{
167167
// Manually write the JSON output, since we don't use ConsoleLogger in JSON mode (see above)
168-
Console.WriteLine(JsonConvert.SerializeObject(new CredentialResult(resultUsername, resultPassword)));
168+
Console.WriteLine(JsonSerializer.Serialize(new CredentialResult(resultUsername, resultPassword)));
169169
}
170170
else
171171
{

CredentialProvider.Microsoft/Util/SessionTokenCache.cs

+14-16
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
using System;
66
using System.Collections.Generic;
77
using System.IO;
8+
using System.Text.Json;
89
using System.Threading;
9-
using Newtonsoft.Json;
1010
using NuGetCredentialProvider.Logging;
1111

1212
namespace NuGetCredentialProvider.Util
@@ -27,7 +27,7 @@ public SessionTokenCache(string cacheFilePath, ILogger logger, CancellationToken
2727
this.mutexName = @"Global\" + cacheFilePath.Replace(Path.DirectorySeparatorChar, '_');
2828
}
2929

30-
private Dictionary<Uri, string> Cache
30+
private Dictionary<string, string> Cache
3131
{
3232
get
3333
{
@@ -49,12 +49,12 @@ private Dictionary<Uri, string> Cache
4949
if (index == 1)
5050
{
5151
logger.Verbose(Resources.CancelMessage);
52-
return new Dictionary<Uri, string>();
52+
return new Dictionary<string, string>();
5353
}
5454
else if (index == WaitHandle.WaitTimeout)
5555
{
5656
logger.Verbose(Resources.SessionTokenCacheMutexFail);
57-
return new Dictionary<Uri, string>();
57+
return new Dictionary<string, string>();
5858
}
5959
}
6060
}
@@ -80,7 +80,7 @@ private Dictionary<Uri, string> Cache
8080

8181
public string this[Uri key]
8282
{
83-
get => Cache[key];
83+
get => Cache[key.ToString()];
8484
set
8585
{
8686
bool mutexHeld = false, dummy;
@@ -116,7 +116,7 @@ public string this[Uri key]
116116
mutexHeld = true;
117117

118118
var cache = Cache;
119-
cache[key] = value;
119+
cache[key.ToString()] = value;
120120
WriteFileBytes(Serialize(cache));
121121
}
122122
finally
@@ -132,14 +132,14 @@ public string this[Uri key]
132132

133133
public bool ContainsKey(Uri key)
134134
{
135-
return Cache.ContainsKey(key);
135+
return Cache.ContainsKey(key.ToString());
136136
}
137137

138138
public bool TryGetValue(Uri key, out string value)
139139
{
140140
try
141141
{
142-
return Cache.TryGetValue(key, out value);
142+
return Cache.TryGetValue(key.ToString(), out value);
143143
}
144144
catch (Exception e)
145145
{
@@ -191,7 +191,7 @@ public void Remove(Uri key)
191191
mutexHeld = true;
192192

193193
var cache = Cache;
194-
cache.Remove(key);
194+
cache.Remove(key.ToString());
195195
WriteFileBytes(Serialize(cache));
196196
}
197197
finally
@@ -204,21 +204,19 @@ public void Remove(Uri key)
204204
}
205205
}
206206

207-
private Dictionary<Uri, string> Deserialize(byte[] data)
207+
private Dictionary<string, string> Deserialize(byte[] data)
208208
{
209209
if (data == null)
210210
{
211-
return new Dictionary<Uri, string>();
211+
return new Dictionary<string, string>();
212212
}
213213

214-
var serialized = System.Text.Encoding.UTF8.GetString(data);
215-
return JsonConvert.DeserializeObject<Dictionary<Uri, string>>(serialized);
214+
return JsonSerializer.Deserialize<Dictionary<string, string>>(data);
216215
}
217216

218-
private byte[] Serialize(Dictionary<Uri, string> data)
217+
private byte[] Serialize(Dictionary<string, string> data)
219218
{
220-
var serialized = JsonConvert.SerializeObject(data);
221-
return System.Text.Encoding.UTF8.GetBytes(serialized);
219+
return JsonSerializer.SerializeToUtf8Bytes(data);
222220
}
223221

224222
private byte[] ReadFileBytes()

0 commit comments

Comments
 (0)