Conversation
multithreaded support for timings
| --> | ||
| <system.web> | ||
| <compilation debug="true" targetFramework="4.5"> | ||
| <compilation debug="true" targetFramework="4.6.1"> |
There was a problem hiding this comment.
i needed this to have AsyncLocal
| } | ||
|
|
||
| [Test] | ||
| public async void Current_WhenAsyncMethodReturns_IsCarried( |
There was a problem hiding this comment.
you can use [Theory] instead of [Test] and [Values]
| { | ||
| // This assumes that trivial items do not have any non-trivial children | ||
| timing.Children.RemoveAll(child => child.IsTrivial); | ||
| timing.AccessChildren()?.RemoveAll(child => child.IsTrivial); |
There was a problem hiding this comment.
there are a couple of places where they modify the children timing directly.
| context.Items[WcfCacheKey] = profiler; | ||
| } | ||
|
|
||
| public override Timing GetHead() |
There was a problem hiding this comment.
this is more to support the new methods, the implementation is not very advanced here since i don't really care about wcf
| /// <param name="action">The <see cref="T:System.Action`1"/> delegate to perform on each element of the | ||
| /// <see cref="T:System.Collections.Generic.List`1"/>.</param><exception cref="T:System.ArgumentNullException"> | ||
| /// <paramref name="action"/> is null.</exception><exception cref="T:System.InvalidOperationException">An element in the collection has been modified. CautionThis exception is thrown starting with the .NET Framework 4.5. </exception> | ||
| public static void ForEach<T>(this IList<T> list, Action<T> action) |
There was a problem hiding this comment.
here i could lock on LockObj as well i guess
| /// <param name="list">the list to perform the operations for</param> | ||
| /// <param name="action">The <see cref="T:System.Action`1"/> delegate to perform on each element of the | ||
| /// <see cref="T:System.Collections.Generic.List`1"/>.</param><exception cref="T:System.ArgumentNullException"> | ||
| /// <paramref name="action"/> is null.</exception><exception cref="T:System.InvalidOperationException">An element in the collection has been modified. CautionThis exception is thrown starting with the .NET Framework 4.5. </exception> |
There was a problem hiding this comment.
i'm not very happy that this method is public, i would rather prefer if it was internal. and an alternative to this method is to update the couple of places which try to modify the list so they don't need ForEach and RemoveAll at all
There was a problem hiding this comment.
are these only here because you changed an List to an IList?
If so, then I just think we replace them with traditional for loops
StackExchange.Profiling/Timing.cs
Outdated
| /// </summary> | ||
| [DataMember(Order = 5)] | ||
| public List<Timing> Children { get; set; } | ||
| public IList<Timing> Children |
There was a problem hiding this comment.
Children now returns a copy of children, for 99% of the cases i think this is the best option
StackExchange.Profiling/Timing.cs
Outdated
| /// <summary> | ||
| /// Gets or sets All sub-steps that occur within this Timing step. Add new children through <see cref="AddChild"/> | ||
| /// </summary> | ||
| public IList<Timing> AccessChildren() |
There was a problem hiding this comment.
this method is for the remaining 1% of the cases where the caller wants to modify Timings list. I'm thinking to replace it with explicit methods RemoveTimings or something like this.
There was a problem hiding this comment.
one option is to replace Children with an IImmutableList<T>.
This method only appears to be used to remove all. Perhaps we can just rename this method RemoveChildren()?
There was a problem hiding this comment.
I will need to add dependency on collections in order to start using iimmutablelist. I guess using that interface would make sense to emphasize that the list is not modifiable, not modifying those lists is rather pointless since i return copies.
|
Ideally i think miniprofiler should be split into 2 classes: one class will be responsible for capturing timings. It would provide very limited ways for manipulating captured timings. Then, there would be a way to "complete" the capturing which would return a miniprofiler snapshot that can be modified easily but there would be no multithreaded access to it |
| } | ||
|
|
||
| [Test] | ||
| public async void Current_WhenAsyncMethodReturns_IsCarried( |
There was a problem hiding this comment.
you can use [Theory] instead of [Test] and [Values]
| } | ||
|
|
||
| private AsyncLocal<MiniProfiler> _currentProfiler = new AsyncLocal<MiniProfiler>(); | ||
| private AsyncLocal<Timing> _currentTiming = new AsyncLocal<Timing>(); |
| } | ||
| } | ||
|
|
||
| protected override Timing CurrentHead { |
| /// </summary> | ||
| public abstract MiniProfiler GetCurrentProfiler(); | ||
|
|
||
| public abstract Timing GetHead(); |
There was a problem hiding this comment.
I'm not sure I understand why these methods are necessary.
Since the MiniProfiler.Current is stored in the exact same manner as Head. Seems like you could continue to use MiniProfiler.Current.Head?
There was a problem hiding this comment.
the documentation is provided in the interface
| /// <param name="list">the list to perform the operations for</param> | ||
| /// <param name="action">The <see cref="T:System.Action`1"/> delegate to perform on each element of the | ||
| /// <see cref="T:System.Collections.Generic.List`1"/>.</param><exception cref="T:System.ArgumentNullException"> | ||
| /// <paramref name="action"/> is null.</exception><exception cref="T:System.InvalidOperationException">An element in the collection has been modified. CautionThis exception is thrown starting with the .NET Framework 4.5. </exception> |
There was a problem hiding this comment.
are these only here because you changed an List to an IList?
If so, then I just think we replace them with traditional for loops
StackExchange.Profiling/Timing.cs
Outdated
| /// <summary> | ||
| /// Gets or sets All sub-steps that occur within this Timing step. Add new children through <see cref="AddChild"/> | ||
| /// </summary> | ||
| public IList<Timing> AccessChildren() |
There was a problem hiding this comment.
one option is to replace Children with an IImmutableList<T>.
This method only appears to be used to remove all. Perhaps we can just rename this method RemoveChildren()?
| @@ -299,6 +325,7 @@ internal void RemoveCustomTiming(string category, CustomTiming customTiming) | |||
| } | |||
|
|
|||
| private readonly object _lockObject = new object(); | |||
There was a problem hiding this comment.
Not your code, but I don't understand the locking around CustomTimingList.
The existing GetCustomTimingList acquires the same lock twice, and protects access to the dictionary. But the underlying values in the dictionary (lists of custom timings) are not protected by the lock. I don't see why they would need to protect the dictionary but the lists.
There was a problem hiding this comment.
I think the existing method acquires _lockObject first and then the specific list if it is not null, i dont think this makes sense, since there are many cases where racing might occur.
| } | ||
| } | ||
|
|
||
| private readonly AsyncLocal<MiniProfiler> _currentProfiler = new AsyncLocal<MiniProfiler>(); |
There was a problem hiding this comment.
btw, if we wanted to stay compatible with .net 4 we could use our RevAsyncLocal here instead. functionally it is pretty much the same and available as long as CallContext.SetLogicalData is available.
If we are to suggest a pr for miniprofiler v4 we definitely will need to think about a substitution for AsyncLocal since they try to be backward compatible there
| /// </summary> | ||
| [DataMember(Order = 2)] | ||
| public List<ClientTiming> Timings { get; set; } | ||
| public List<ClientTiming> Timings |
There was a problem hiding this comment.
i'm not sure mt access is possible for custom timings, but i guess it makes sense to do just in case
There was a problem hiding this comment.
seems like we should the same here as what we do for Timings.Children - regarding IReadOnlyCollection or ImmutableArray
StackExchange.Profiling/Timing.cs
Outdated
| /// </summary> | ||
| [DataMember(Order = 5)] | ||
| public List<Timing> Children { get; set; } | ||
| public List<Timing> Children |
There was a problem hiding this comment.
i think using IImmutableList for _children is an interesting idea. That would have indicated to the caller that the collection is immutable and make accessing _children naturally thread safe.
I think adding children might be a bit more expensive since the tree is going to be built underneath but perhaps the downside is not that high, do you think we should make the change? I guess we (nor miniprofiler v4) are not going to support .net below 4.5 (where Immutable would not be available)
It looks like in a single threaded scenario immutablelist is significantly underperforming comparing to list: https://ayende.com/blog/164739/immutable-collections-performance
There was a problem hiding this comment.
hmm yeah, the ImmutableList perf isn't great because inside it's actually a tree.
But they have an ImmutableArray which is faster to read, but more expensive to write http://stackoverflow.com/a/28621409/830724
Anyway, I think perhaps the best thing to do is to have this return an IReadOnlyList or IReadOnlyCollection that way it is obvious that the caller shouldn't mutate it. If we really cared about performance we could return an ImmutableArray or IReadOnlyList here and cache the result, so we don't have to create the copy on every access.
| get | ||
| { | ||
| lock(_lockObject) | ||
| return new List<ClientTiming>(_timings); |
There was a problem hiding this comment.
a case when _timings is null
There was a problem hiding this comment.
if we're going to go w/ ImmutableList for the Miniprofiler.Children, seems like we should do that here too.
| set | ||
| { | ||
| lock(_lockObject) | ||
| _timings = new List<ClientTiming>(value); |
There was a problem hiding this comment.
a case when value is null
There was a problem hiding this comment.
what does this comment mean? you need to handle null?
StackExchange.Profiling/Timing.cs
Outdated
| { | ||
| lock(_lockObject) | ||
| { | ||
| return _customTimings.ToDictionary(p => p.Key, p => new List<CustomTiming>(p.Value)); |
There was a problem hiding this comment.
a case when _customTimings is null
StackExchange.Profiling/Timing.cs
Outdated
| { | ||
| lock(_lockObject) | ||
| { | ||
| _customTimings = value.ToDictionary(p => p.Key, p => new List<CustomTiming>(p.Value)); |
There was a problem hiding this comment.
a case when value is null
| get | ||
| { | ||
| lock(_lockObject) | ||
| return new List<ClientTiming>(_timings); |
There was a problem hiding this comment.
if we're going to go w/ ImmutableList for the Miniprofiler.Children, seems like we should do that here too.
| set | ||
| { | ||
| lock(_lockObject) | ||
| _timings = new List<ClientTiming>(value); |
There was a problem hiding this comment.
what does this comment mean? you need to handle null?
StackExchange.Profiling/Timing.cs
Outdated
| /// </summary> | ||
| [DataMember(Order = 5)] | ||
| public List<Timing> Children { get; set; } | ||
| public List<Timing> Children |
There was a problem hiding this comment.
hmm yeah, the ImmutableList perf isn't great because inside it's actually a tree.
But they have an ImmutableArray which is faster to read, but more expensive to write http://stackoverflow.com/a/28621409/830724
Anyway, I think perhaps the best thing to do is to have this return an IReadOnlyList or IReadOnlyCollection that way it is obvious that the caller shouldn't mutate it. If we really cared about performance we could return an ImmutableArray or IReadOnlyList here and cache the result, so we don't have to create the copy on every access.
StackExchange.Profiling/Timing.cs
Outdated
| { | ||
| lock (_lockObject) | ||
| { | ||
|
|
| /// </summary> | ||
| [DataMember(Order = 2)] | ||
| public List<ClientTiming> Timings { get; set; } | ||
| public List<ClientTiming> Timings |
There was a problem hiding this comment.
seems like we should the same here as what we do for Timings.Children - regarding IReadOnlyCollection or ImmutableArray
| /// </summary> | ||
| [DataMember(Order = 5)] | ||
| public List<Timing> Children { get; set; } | ||
| public IReadOnlyList<Timing> Children |
There was a problem hiding this comment.
so v4 breaks if I use IReadOnlyList there. It breaks because protobuf that is used there doesn't support read only collections. There is an option to use IImmutableList instead (which is supported by protobuf) but then there would be a problem with Dictionary, there is no lightweight implementation of IImmutableDictionary available.
I think the best way is to revert to using List and Dictionary, what do you think?
| <id>MiniProfilerAsync.EF6</id> | ||
| <version>3.0.12</version> | ||
| <authors>rev.com</authors> | ||
| <owners>rev.com</owners> |
There was a problem hiding this comment.
not sure what else should go here
MiniProfiler.EF6.nuspec
Outdated
| <authors>Jarrod Dixon, Yaakov Ellis</authors> | ||
| <owners>Jarrod Dixon, Yaakov Ellis</owners> | ||
| <id>MiniProfilerAsync.EF6</id> | ||
| <version>3.0.12</version> |
There was a problem hiding this comment.
the version needs to be bumped up i believe
MiniProfiler.EF6.nuspec
Outdated
| <version>3.0.11</version> | ||
| <authors>Jarrod Dixon, Yaakov Ellis</authors> | ||
| <owners>Jarrod Dixon, Yaakov Ellis</owners> | ||
| <id>MiniProfilerAsync.EF6</id> |
There was a problem hiding this comment.
i think it's better if id is different
| [assembly: AssemblyVersion("3.0.11")] | ||
| [assembly: AssemblyInformationalVersion("3.0.11")] No newline at end of file | ||
| [assembly: AssemblyVersion("3.0.12")] | ||
| [assembly: AssemblyInformationalVersion("3.0.12")] No newline at end of file |
There was a problem hiding this comment.
just in case i bumped up versions of dlls
| return new JavaScriptSerializer { MaxJsonLength = Settings.MaxJsonResponseSize }; | ||
| var res = new JavaScriptSerializer { MaxJsonLength = Settings.MaxJsonResponseSize }; | ||
|
|
||
| res.RegisterConverters(new[] { new ReadOnlyDictionaryConverter<IReadOnlyList<CustomTiming>>() }); |
There was a problem hiding this comment.
So this is a total disaster, it looks like JavaScriptSerializer doesn't support natively IReadOnlyDictionary, so i had to implement and use a converter for it, luckily i don't think we much depend on JavaScriptSerializer except for unit tests. @eugenep-rev, am I correct that we don't rely on JavaScriptSerializer?
StackExchange.Profiling/Timing.cs
Outdated
| lock(_lockObject) | ||
| { | ||
| return _customTimings?.ToDictionary(p => p.Key, p => new List<CustomTiming>(p.Value)); | ||
| return _customTimings?.ToDictionary(p => p.Key, p => (IReadOnlyList<CustomTiming>)new List<CustomTiming>(p.Value)); |
There was a problem hiding this comment.
I think it would be better to get rid of all these copies on the get (here and Children, and ClientTimings)
I'd rather have explicity Set() methods and have the properties with no setters.
pjhuck
left a comment
There was a problem hiding this comment.
only questions are about assembly and nuget versions.
MiniProfiler.EF6.nuspec
Outdated
| <owners>Jarrod Dixon, Yaakov Ellis</owners> | ||
| <id>RevMiniProfiler.EF6</id> | ||
| <version>3.1.0.2</version> | ||
| <authors>rev.com</authors> |
There was a problem hiding this comment.
i would keep the original authors and just append rev
MiniProfiler.EF6.nuspec
Outdated
| <authors>Jarrod Dixon, Yaakov Ellis</authors> | ||
| <owners>Jarrod Dixon, Yaakov Ellis</owners> | ||
| <id>RevMiniProfiler.EF6</id> | ||
| <version>3.1.0.2</version> |
There was a problem hiding this comment.
how did you come up with this version number? why not 3.1.0?
There was a problem hiding this comment.
it was 3.1.0 but then i bumped it up while making changes.
| [assembly: AssemblyVersion("3.0.11")] | ||
| [assembly: AssemblyInformationalVersion("3.0.11")] No newline at end of file | ||
| [assembly: AssemblyVersion("3.1.0.0")] | ||
| [assembly: AssemblyInformationalVersion("3.1.0.0")] No newline at end of file |
There was a problem hiding this comment.
seems like this should match the version in the nuspec
There was a problem hiding this comment.
I don't think this is necessary nor it is common practice as it seems (e.g. MiniProfiler's nuspec doesn't match with the assembly version). In general i found bumping up assembly version too cumbersome since i needed to go and update the versions in the config files
| [assembly: Guid("44f8ac97-8e2c-4dba-adae-d971fb086a19")] | ||
| [assembly: AssemblyVersion("3.0.11")] | ||
| [assembly: AssemblyInformationalVersion("3.0.11")] | ||
| [assembly: AssemblyVersion("3.1.0.0")] |
| [assembly: AssemblyVersion("3.1")] | ||
| [assembly: AssemblyInformationalVersion("3.1")] | ||
| [assembly: AssemblyVersion("3.3.0.2")] | ||
| [assembly: AssemblyInformationalVersion("3.3.0.2")] |
multithreaded support for timings