Skip to content

Commit aeb9e7d

Browse files
dee-seerjmholt
authored andcommitted
Fix #779: NRE on Dispose in ExecutionTimer (#782)
* Fix ThreadStatic issue with an object pool
1 parent cb713f2 commit aeb9e7d

File tree

4 files changed

+87
-8
lines changed

4 files changed

+87
-8
lines changed

src/PowerShellEditorServices/Utility/ExecutionTimer.cs

+10-8
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ namespace Microsoft.PowerShell.EditorServices.Utility
2020
/// </example>
2121
public struct ExecutionTimer : IDisposable
2222
{
23-
[ThreadStatic]
24-
private static Stopwatch t_stopwatch;
23+
private static readonly ObjectPool<Stopwatch> s_stopwatchPool = new ObjectPool<Stopwatch>();
24+
25+
private Stopwatch _stopwatch;
2526

2627
private readonly ILogger _logger;
2728

@@ -50,7 +51,7 @@ public static ExecutionTimer Start(
5051
[CallerLineNumber] int callerLineNumber = -1)
5152
{
5253
var timer = new ExecutionTimer(logger, message, callerMemberName, callerFilePath, callerLineNumber);
53-
Stopwatch.Start();
54+
timer._stopwatch.Start();
5455
return timer;
5556
}
5657

@@ -66,6 +67,7 @@ internal ExecutionTimer(
6667
_callerMemberName = callerMemberName;
6768
_callerFilePath = callerFilePath;
6869
_callerLineNumber = callerLineNumber;
70+
_stopwatch = s_stopwatchPool.Rent();
6971
}
7072

7173
/// <summary>
@@ -74,16 +76,18 @@ internal ExecutionTimer(
7476
/// </summary>
7577
public void Dispose()
7678
{
77-
t_stopwatch.Stop();
79+
_stopwatch.Stop();
7880

7981
string logMessage = new StringBuilder()
8082
.Append(_message)
8183
.Append(" [")
82-
.Append(t_stopwatch.ElapsedMilliseconds)
84+
.Append(_stopwatch.ElapsedMilliseconds)
8385
.Append("ms]")
8486
.ToString();
8587

86-
t_stopwatch.Reset();
88+
_stopwatch.Reset();
89+
90+
s_stopwatchPool.Return(_stopwatch);
8791

8892
_logger.Write(
8993
LogLevel.Verbose,
@@ -92,7 +96,5 @@ public void Dispose()
9296
callerSourceFile: _callerFilePath,
9397
callerLineNumber: _callerLineNumber);
9498
}
95-
96-
private static Stopwatch Stopwatch => t_stopwatch ?? (t_stopwatch = new Stopwatch());
9799
}
98100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System.Collections.Concurrent;
7+
8+
namespace Microsoft.PowerShell.EditorServices.Utility
9+
{
10+
/// <summary>
11+
/// A basic implementation of the object pool pattern.
12+
/// </summary>
13+
internal class ObjectPool<T>
14+
where T : new()
15+
{
16+
private ConcurrentBag<T> _pool = new ConcurrentBag<T>();
17+
18+
/// <summary>
19+
/// Get an instance of an object, either new or from the pool depending on availability.
20+
/// </summary>
21+
public T Rent() => _pool.TryTake(out var obj) ? obj : new T();
22+
23+
/// <summary>
24+
/// Return an object to the pool.
25+
/// </summary>
26+
/// <param name="obj">The object to return to the pool.</param>
27+
public void Return(T obj) => _pool.Add(obj);
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using Microsoft.PowerShell.EditorServices.Utility;
7+
using System;
8+
using System.IO;
9+
using System.Linq;
10+
using System.Text;
11+
using System.Threading.Tasks;
12+
using Xunit;
13+
14+
namespace Microsoft.PowerShell.EditorServices.Test.Utility
15+
{
16+
public class ExecutionTimerTests
17+
{
18+
[Fact]
19+
public async void DoesNotThrowExceptionWhenDisposedOnAnotherThread()
20+
{
21+
var timer = ExecutionTimer.Start(Logging.CreateLogger().Build(), "Message");
22+
await Task.Run(() => timer.Dispose());
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using Microsoft.PowerShell.EditorServices.Utility;
7+
using Xunit;
8+
9+
namespace Microsoft.PowerShell.EditorServices.Test.Utility
10+
{
11+
public class ObjectPoolTests
12+
{
13+
[Fact]
14+
public void DoesNotCreateNewObjects()
15+
{
16+
var pool = new ObjectPool<object>();
17+
var obj = pool.Rent();
18+
pool.Return(obj);
19+
20+
Assert.Same(obj, pool.Rent());
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)