Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit 87c1f1f

Browse files
authored
Eliminate allocations in AuthorizationPolicy.CombineAsync with no policy (#1899)
1 parent e82be83 commit 87c1f1f

File tree

10 files changed

+213
-33
lines changed

10 files changed

+213
-33
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ project.lock.json
3030
/.vs/
3131
.vscode/
3232
global.json
33+
BenchmarkDotNet.Artifacts/

Security.sln

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authen
7979
EndProject
8080
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WsFedSample", "samples\WsFedSample\WsFedSample.csproj", "{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}"
8181
EndProject
82+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{55052FE3-F8C2-4E6C-97B0-C02ED1DBEA62}"
83+
EndProject
84+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Security.Performance", "benchmarks\Microsoft.AspNetCore.Security.Performance\Microsoft.AspNetCore.Security.Performance.csproj", "{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}"
85+
EndProject
8286
Global
8387
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8488
Debug|Any CPU = Debug|Any CPU
@@ -517,6 +521,22 @@ Global
517521
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x64.Build.0 = Release|Any CPU
518522
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.ActiveCfg = Release|Any CPU
519523
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.Build.0 = Release|Any CPU
524+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
525+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
526+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
527+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
528+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x64.ActiveCfg = Debug|Any CPU
529+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x64.Build.0 = Debug|Any CPU
530+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x86.ActiveCfg = Debug|Any CPU
531+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x86.Build.0 = Debug|Any CPU
532+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
533+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Any CPU.Build.0 = Release|Any CPU
534+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
535+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
536+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x64.ActiveCfg = Release|Any CPU
537+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x64.Build.0 = Release|Any CPU
538+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x86.ActiveCfg = Release|Any CPU
539+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x86.Build.0 = Release|Any CPU
520540
EndGlobalSection
521541
GlobalSection(SolutionProperties) = preSolution
522542
HideSolutionNode = FALSE
@@ -549,6 +569,7 @@ Global
549569
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
550570
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
551571
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
572+
{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A} = {55052FE3-F8C2-4E6C-97B0-C02ED1DBEA62}
552573
EndGlobalSection
553574
GlobalSection(ExtensibilityGlobals) = postSolution
554575
SolutionGuid = {ABF8089E-43D0-4010-84A7-7A9DCFE49357}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using BenchmarkDotNet.Attributes;
7+
using Microsoft.AspNetCore.Authorization;
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.AspNetCore.Http.Features;
10+
using Microsoft.Extensions.Options;
11+
12+
namespace Microsoft.AspNetCore.Security
13+
{
14+
public class AuthorizationMiddlewareBenchmark
15+
{
16+
private AuthorizationMiddleware _authorizationMiddleware;
17+
private DefaultHttpContext _httpContextNoEndpoint;
18+
private DefaultHttpContext _httpContextHasEndpoint;
19+
20+
[GlobalSetup]
21+
public void Setup()
22+
{
23+
var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()));
24+
_authorizationMiddleware = new AuthorizationMiddleware((context) => Task.CompletedTask, policyProvider);
25+
26+
_httpContextNoEndpoint = new DefaultHttpContext();
27+
28+
var feature = new EndpointFeature
29+
{
30+
Endpoint = new Endpoint((context) => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint")
31+
};
32+
_httpContextHasEndpoint = new DefaultHttpContext();
33+
_httpContextHasEndpoint.Features.Set<IEndpointFeature>(feature);
34+
}
35+
36+
[Benchmark]
37+
public Task Invoke_NoEndpoint_NoAuthorization()
38+
{
39+
return _authorizationMiddleware.Invoke(_httpContextNoEndpoint);
40+
}
41+
42+
[Benchmark]
43+
public Task Invoke_HasEndpoint_NoAuthorization()
44+
{
45+
return _authorizationMiddleware.Invoke(_httpContextHasEndpoint);
46+
}
47+
48+
private class EndpointFeature : IEndpointFeature
49+
{
50+
public Endpoint Endpoint { get; set; }
51+
}
52+
}
53+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using BenchmarkDotNet.Attributes;
7+
using Microsoft.AspNetCore.Authorization;
8+
using Microsoft.Extensions.Options;
9+
10+
namespace Microsoft.AspNetCore.Security
11+
{
12+
public class AuthorizationPolicyBenchmark
13+
{
14+
private DefaultAuthorizationPolicyProvider _policyProvider;
15+
16+
[GlobalSetup]
17+
public void Setup()
18+
{
19+
_policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()));
20+
}
21+
22+
[Benchmark]
23+
public Task CombineAsync()
24+
{
25+
return AuthorizationPolicy.CombineAsync(_policyProvider, Array.Empty<IAuthorizeData>());
26+
}
27+
}
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp2.2</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<ServerGarbageCollection>true</ServerGarbageCollection>
7+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
8+
<IsPackable>false</IsPackable>
9+
<RootNamespace>Microsoft.AspNetCore.Security</RootNamespace>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authorization.Policy\Microsoft.AspNetCore.Authorization.Policy.csproj" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
18+
<PackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
19+
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
20+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
21+
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
22+
</ItemGroup>
23+
24+
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Compile the solution in Release mode (so binaries are available in release)
2+
3+
To run a specific benchmark add it as parameter.
4+
```
5+
dotnet run -c Release --framework <tfm> <benchmark_name>
6+
```
7+
8+
To run all benchmarks use '*' as the name.
9+
```
10+
dotnet run -c Release --framework <tfm> *
11+
```
12+
13+
If you run without any parameters, you'll be offered the list of all benchmarks and get to choose.
14+
```
15+
dotnet run -c Release --framework <tfm>
16+
```

build/dependencies.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
44
</PropertyGroup>
55
<PropertyGroup Label="Package Versions">
6+
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
67
<InternalAspNetCoreSdkPackageVersion>3.0.0-alpha1-20181004.7</InternalAspNetCoreSdkPackageVersion>
78
<MicrosoftAspNetCoreAuthenticationAbstractionsPackageVersion>3.0.0-alpha1-10670</MicrosoftAspNetCoreAuthenticationAbstractionsPackageVersion>
89
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>3.0.0-alpha1-10670</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
10+
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>3.0.0-alpha1-10657</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
911
<MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>3.0.0-alpha1-10670</MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>
1012
<MicrosoftAspNetCoreDataProtectionPackageVersion>3.0.0-alpha1-10670</MicrosoftAspNetCoreDataProtectionPackageVersion>
1113
<MicrosoftAspNetCoreDiagnosticsPackageVersion>3.0.0-alpha1-10670</MicrosoftAspNetCoreDiagnosticsPackageVersion>

src/Microsoft.AspNetCore.Authorization/AuthorizationPolicy.cs

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -120,52 +120,74 @@ public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyP
120120
throw new ArgumentNullException(nameof(authorizeData));
121121
}
122122

123-
var policyBuilder = new AuthorizationPolicyBuilder();
124-
var any = false;
125-
foreach (var authorizeDatum in authorizeData)
123+
// Avoid allocating enumerator if the data is known to be empty
124+
var skipEnumeratingData = false;
125+
if (authorizeData is IList<IAuthorizeData> dataList)
126126
{
127-
any = true;
128-
var useDefaultPolicy = true;
129-
if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
127+
skipEnumeratingData = dataList.Count == 0;
128+
}
129+
130+
AuthorizationPolicyBuilder policyBuilder = null;
131+
if (!skipEnumeratingData)
132+
{
133+
foreach (var authorizeDatum in authorizeData)
130134
{
131-
var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
132-
if (policy == null)
135+
if (policyBuilder == null)
133136
{
134-
throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
137+
policyBuilder = new AuthorizationPolicyBuilder();
135138
}
136-
policyBuilder.Combine(policy);
137-
useDefaultPolicy = false;
138-
}
139-
var rolesSplit = authorizeDatum.Roles?.Split(',');
140-
if (rolesSplit != null && rolesSplit.Any())
141-
{
142-
var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
143-
policyBuilder.RequireRole(trimmedRolesSplit);
144-
useDefaultPolicy = false;
145-
}
146-
var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
147-
if (authTypesSplit != null && authTypesSplit.Any())
148-
{
149-
foreach (var authType in authTypesSplit)
139+
140+
var useDefaultPolicy = true;
141+
if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
150142
{
151-
if (!string.IsNullOrWhiteSpace(authType))
143+
var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
144+
if (policy == null)
152145
{
153-
policyBuilder.AuthenticationSchemes.Add(authType.Trim());
146+
throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
154147
}
148+
policyBuilder.Combine(policy);
149+
useDefaultPolicy = false;
150+
}
151+
152+
var rolesSplit = authorizeDatum.Roles?.Split(',');
153+
if (rolesSplit != null && rolesSplit.Any())
154+
{
155+
var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
156+
policyBuilder.RequireRole(trimmedRolesSplit);
157+
useDefaultPolicy = false;
158+
}
159+
160+
var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
161+
if (authTypesSplit != null && authTypesSplit.Any())
162+
{
163+
foreach (var authType in authTypesSplit)
164+
{
165+
if (!string.IsNullOrWhiteSpace(authType))
166+
{
167+
policyBuilder.AuthenticationSchemes.Add(authType.Trim());
168+
}
169+
}
170+
}
171+
172+
if (useDefaultPolicy)
173+
{
174+
policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
155175
}
156-
}
157-
if (useDefaultPolicy)
158-
{
159-
policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
160176
}
161177
}
178+
162179
var requiredPolicy = await policyProvider.GetRequiredPolicyAsync();
163180
if (requiredPolicy != null)
164181
{
165-
any = true;
182+
if (policyBuilder == null)
183+
{
184+
policyBuilder = new AuthorizationPolicyBuilder();
185+
}
186+
166187
policyBuilder.Combine(requiredPolicy);
167188
}
168-
return any ? policyBuilder.Build() : null;
189+
190+
return policyBuilder?.Build();
169191
}
170192
}
171193
}

src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationPolicyProvider.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace Microsoft.AspNetCore.Authorization
1414
public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider
1515
{
1616
private readonly AuthorizationOptions _options;
17+
private Task<AuthorizationPolicy> _cachedDefaultPolicy;
18+
private Task<AuthorizationPolicy> _cachedRequiredPolicy;
1719

1820
/// <summary>
1921
/// Creates a new instance of <see cref="DefaultAuthorizationPolicyProvider"/>.
@@ -35,7 +37,7 @@ public DefaultAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options
3537
/// <returns>The default authorization policy.</returns>
3638
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
3739
{
38-
return Task.FromResult(_options.DefaultPolicy);
40+
return GetCachedPolicy(ref _cachedDefaultPolicy, _options.DefaultPolicy);
3941
}
4042

4143
/// <summary>
@@ -44,7 +46,17 @@ public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
4446
/// <returns>The required authorization policy.</returns>
4547
public Task<AuthorizationPolicy> GetRequiredPolicyAsync()
4648
{
47-
return Task.FromResult(_options.RequiredPolicy);
49+
return GetCachedPolicy(ref _cachedRequiredPolicy, _options.RequiredPolicy);
50+
}
51+
52+
private Task<AuthorizationPolicy> GetCachedPolicy(ref Task<AuthorizationPolicy> cachedPolicy, AuthorizationPolicy currentPolicy)
53+
{
54+
var local = cachedPolicy;
55+
if (local == null || local.Result != currentPolicy)
56+
{
57+
cachedPolicy = local = Task.FromResult(currentPolicy);
58+
}
59+
return local;
4860
}
4961

5062
/// <summary>

0 commit comments

Comments
 (0)