Skip to content

Commit

Permalink
Fix #779: NRE on Dispose in ExecutionTimer (#782)
Browse files Browse the repository at this point in the history
* Fix ThreadStatic issue with an object pool
  • Loading branch information
dee-see authored and rjmholt committed Oct 24, 2018
1 parent cb713f2 commit aeb9e7d
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 8 deletions.
18 changes: 10 additions & 8 deletions src/PowerShellEditorServices/Utility/ExecutionTimer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ namespace Microsoft.PowerShell.EditorServices.Utility
/// </example>
public struct ExecutionTimer : IDisposable
{
[ThreadStatic]
private static Stopwatch t_stopwatch;
private static readonly ObjectPool<Stopwatch> s_stopwatchPool = new ObjectPool<Stopwatch>();

private Stopwatch _stopwatch;

private readonly ILogger _logger;

Expand Down Expand Up @@ -50,7 +51,7 @@ public static ExecutionTimer Start(
[CallerLineNumber] int callerLineNumber = -1)
{
var timer = new ExecutionTimer(logger, message, callerMemberName, callerFilePath, callerLineNumber);
Stopwatch.Start();
timer._stopwatch.Start();
return timer;
}

Expand All @@ -66,6 +67,7 @@ internal ExecutionTimer(
_callerMemberName = callerMemberName;
_callerFilePath = callerFilePath;
_callerLineNumber = callerLineNumber;
_stopwatch = s_stopwatchPool.Rent();
}

/// <summary>
Expand All @@ -74,16 +76,18 @@ internal ExecutionTimer(
/// </summary>
public void Dispose()
{
t_stopwatch.Stop();
_stopwatch.Stop();

string logMessage = new StringBuilder()
.Append(_message)
.Append(" [")
.Append(t_stopwatch.ElapsedMilliseconds)
.Append(_stopwatch.ElapsedMilliseconds)
.Append("ms]")
.ToString();

t_stopwatch.Reset();
_stopwatch.Reset();

s_stopwatchPool.Return(_stopwatch);

_logger.Write(
LogLevel.Verbose,
Expand All @@ -92,7 +96,5 @@ public void Dispose()
callerSourceFile: _callerFilePath,
callerLineNumber: _callerLineNumber);
}

private static Stopwatch Stopwatch => t_stopwatch ?? (t_stopwatch = new Stopwatch());
}
}
29 changes: 29 additions & 0 deletions src/PowerShellEditorServices/Utility/ObjectPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Collections.Concurrent;

namespace Microsoft.PowerShell.EditorServices.Utility
{
/// <summary>
/// A basic implementation of the object pool pattern.
/// </summary>
internal class ObjectPool<T>
where T : new()
{
private ConcurrentBag<T> _pool = new ConcurrentBag<T>();

/// <summary>
/// Get an instance of an object, either new or from the pool depending on availability.
/// </summary>
public T Rent() => _pool.TryTake(out var obj) ? obj : new T();

/// <summary>
/// Return an object to the pool.
/// </summary>
/// <param name="obj">The object to return to the pool.</param>
public void Return(T obj) => _pool.Add(obj);
}
}
25 changes: 25 additions & 0 deletions test/PowerShellEditorServices.Test/Utility/ExecutionTimerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using Microsoft.PowerShell.EditorServices.Utility;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace Microsoft.PowerShell.EditorServices.Test.Utility
{
public class ExecutionTimerTests
{
[Fact]
public async void DoesNotThrowExceptionWhenDisposedOnAnotherThread()
{
var timer = ExecutionTimer.Start(Logging.CreateLogger().Build(), "Message");
await Task.Run(() => timer.Dispose());
}
}
}
23 changes: 23 additions & 0 deletions test/PowerShellEditorServices.Test/Utility/ObjectPoolTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using Microsoft.PowerShell.EditorServices.Utility;
using Xunit;

namespace Microsoft.PowerShell.EditorServices.Test.Utility
{
public class ObjectPoolTests
{
[Fact]
public void DoesNotCreateNewObjects()
{
var pool = new ObjectPool<object>();
var obj = pool.Rent();
pool.Return(obj);

Assert.Same(obj, pool.Rent());
}
}
}

0 comments on commit aeb9e7d

Please sign in to comment.