-
Notifications
You must be signed in to change notification settings - Fork 9.8k
/
FeatureReferences.cs
157 lines (141 loc) · 6.5 KB
/
FeatureReferences.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Http.Features
{
/// <summary>
/// A reference to a collection of features.
/// </summary>
/// <typeparam name="TCache">The type of the feature.</typeparam>
public struct FeatureReferences<TCache>
{
/// <summary>
/// Initializes a new instance of <see cref="FeatureReferences{TCache}"/>.
/// </summary>
/// <param name="collection">The <see cref="IFeatureCollection"/>.</param>
public FeatureReferences(IFeatureCollection collection)
{
Collection = collection;
Cache = default;
Revision = collection.Revision;
}
/// <summary>
/// Initializes the <see cref="FeatureReferences{TCache}"/>.
/// </summary>
/// <param name="collection">The <see cref="IFeatureCollection"/> to initialize with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initalize(IFeatureCollection collection)
{
Revision = collection.Revision;
Collection = collection;
}
/// <summary>
/// Initializes the <see cref="FeatureReferences{TCache}"/>.
/// </summary>
/// <param name="collection">The <see cref="IFeatureCollection"/> to initialize with.</param>
/// <param name="revision">The version of the <see cref="IFeatureCollection"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initalize(IFeatureCollection collection, int revision)
{
Revision = revision;
Collection = collection;
}
/// <summary>
/// Gets the <see cref="IFeatureCollection"/>.
/// </summary>
public IFeatureCollection Collection { get; private set; }
/// <summary>
/// Gets the revision number.
/// </summary>
public int Revision { get; private set; }
// cache is a public field because the code calling Fetch must
// be able to pass ref values that "dot through" the TCache struct memory,
// if it was a Property then that getter would return a copy of the memory
// preventing the use of "ref"
/// <summary>
/// This API is part of ASP.NET Core's infrastructure and should not be referenced by application code.
/// </summary>
public TCache? Cache;
// Careful with modifications to the Fetch method; it is carefully constructed for inlining
// See: https://github.com/aspnet/HttpAbstractions/pull/704
// This method is 59 IL bytes and at inline call depth 3 from accessing a property.
// This combination is enough for the jit to consider it an "unprofitable inline"
// Aggressively inlining it causes the entire call chain to dissolve:
//
// This means this call graph:
//
// HttpResponse.Headers -> Response.HttpResponseFeature -> Fetch -> Fetch -> Revision
// -> Collection -> Collection
// -> Collection.Revision
// Has 6 calls eliminated and becomes just: -> UpdateCached
//
// HttpResponse.Headers -> Collection.Revision
// -> UpdateCached (not called on fast path)
//
// As this is inlined at the callsite we want to keep the method small, so it only detects
// if a reset or update is required and all the reset and update logic is pushed to UpdateCached.
//
// Generally Fetch is called at a ratio > x4 of UpdateCached so this is a large gain
/// <summary>
/// This API is part of ASP.NET Core's infrastructure and should not be referenced by application code.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TFeature? Fetch<TFeature, TState>(
ref TFeature? cached,
TState state,
Func<TState, TFeature?> factory) where TFeature : class?
{
var flush = false;
var revision = Collection?.Revision ?? ContextDisposed();
if (Revision != revision)
{
// Clear cached value to force call to UpdateCached
cached = null!;
// Collection changed, clear whole feature cache
flush = true;
}
return cached ?? UpdateCached(ref cached!, state, factory, revision, flush);
}
// Update and cache clearing logic, when the fast-path in Fetch isn't applicable
private TFeature? UpdateCached<TFeature, TState>(ref TFeature? cached, TState state, Func<TState, TFeature?> factory, int revision, bool flush) where TFeature : class?
{
if (flush)
{
// Collection detected as changed, clear cache
Cache = default;
}
cached = Collection.Get<TFeature>();
if (cached == null)
{
// Item not in collection, create it with factory
cached = factory(state);
// Add item to IFeatureCollection
Collection.Set(cached);
// Revision changed by .Set, update revision to new value
Revision = Collection.Revision;
}
else if (flush)
{
// Cache was cleared, but item retrieved from current Collection for version
// so use passed in revision rather than making another virtual call
Revision = revision;
}
return cached;
}
/// <summary>
/// This API is part of ASP.NET Core's infrastructure and should not be referenced by application code.
/// </summary>
public TFeature? Fetch<TFeature>(ref TFeature? cached, Func<IFeatureCollection, TFeature?> factory)
where TFeature : class? => Fetch(ref cached, Collection, factory);
private static int ContextDisposed()
{
ThrowContextDisposed();
return 0;
}
private static void ThrowContextDisposed()
{
throw new ObjectDisposedException(nameof(Collection), nameof(IFeatureCollection) + " has been disposed.");
}
}
}