Skip to content

Commit f61443e

Browse files
bartizanwaldekmastykarzCopilot
authored
Fixes concurrency issue in ProxyEngine, ConsoleFormatter, and MockResponsePlugin #1161 (#1162)
* Uses thread-safe tracker of mocking endpoints * Uses thread-safe log of requests * Optimizes code for request logs * Use thread-safe plugin data collection * Fixes error message * Update dev-proxy/ProxyEngine.cs Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Waldek Mastykarz <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 887e946 commit f61443e

File tree

3 files changed

+20
-22
lines changed

3 files changed

+20
-22
lines changed

dev-proxy-plugins/Mocks/MockResponsePlugin.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using DevProxy.Plugins.Behavior;
1616
using System.Text;
1717
using Microsoft.Extensions.Logging;
18+
using System.Collections.Concurrent;
1819

1920
namespace DevProxy.Plugins.Mocks;
2021

@@ -42,7 +43,7 @@ public class MockResponsePlugin(IPluginEvents pluginEvents, IProxyContext contex
4243
private IProxyConfiguration? _proxyConfiguration;
4344
// tracks the number of times a mock has been applied
4445
// used in combination with mocks that have an Nth property
45-
private readonly Dictionary<string, int> _appliedMocks = [];
46+
private readonly ConcurrentDictionary<string, int> _appliedMocks = [];
4647

4748
public override Option[] GetOptions()
4849
{
@@ -243,12 +244,7 @@ _configuration.Mocks is null ||
243244

244245
if (mockResponse is not null && mockResponse.Request is not null)
245246
{
246-
if (!_appliedMocks.TryGetValue(mockResponse.Request.Url, out int value))
247-
{
248-
value = 0;
249-
_appliedMocks.Add(mockResponse.Request.Url, value);
250-
}
251-
_appliedMocks[mockResponse.Request.Url] = ++value;
247+
_appliedMocks.AddOrUpdate(mockResponse.Request.Url, 1, (_, value) => ++value);
252248
}
253249

254250
return mockResponse;

dev-proxy/Logging/ProxyConsoleFormatter.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Collections.Concurrent;
56
using System.Text;
67
using DevProxy.Abstractions;
78
using Microsoft.Extensions.Logging.Abstractions;
@@ -17,7 +18,7 @@ public class ProxyConsoleFormatter : ConsoleFormatter
1718
private const string _boxBottomLeft = "\u2570 ";
1819
// used to align single-line messages
1920
private const string _boxSpacing = " ";
20-
private readonly Dictionary<int, List<RequestLog>> _requestLogs = [];
21+
private readonly ConcurrentDictionary<int, List<RequestLog>> _requestLogs = [];
2122
private readonly ProxyConsoleFormatterOptions _options;
2223
const string labelSpacing = " ";
2324
// label length + 2
@@ -62,24 +63,21 @@ private void LogRequest(RequestLog requestLog, string category, IExternalScopePr
6263
{
6364
if (messageType == MessageType.FinishedProcessingRequest)
6465
{
65-
var lastMessage = _requestLogs[requestId.Value].Last();
6666
// log all request logs for the request
67-
foreach (var log in _requestLogs[requestId.Value])
67+
var currentRequestLogs = _requestLogs[requestId.Value];
68+
var lastIndex = currentRequestLogs.Count - 1;
69+
for (var i = 0; i < currentRequestLogs.Count; ++i)
6870
{
69-
WriteLogMessageBoxedWithInvertedLabels(log, scopeProvider, textWriter, log == lastMessage);
71+
var log = currentRequestLogs[i];
72+
WriteLogMessageBoxedWithInvertedLabels(log, scopeProvider, textWriter, i == lastIndex);
7073
}
71-
_requestLogs.Remove(requestId.Value);
74+
_requestLogs.Remove(requestId.Value, out _);
7275
}
7376
else
7477
{
7578
// buffer request logs until the request is finished processing
76-
if (!_requestLogs.TryGetValue(requestId.Value, out List<RequestLog>? value))
77-
{
78-
value = ([]);
79-
_requestLogs[requestId.Value] = value;
80-
}
81-
8279
requestLog.PluginName = category == DefaultCategoryName ? null : category;
80+
var value = _requestLogs.GetOrAdd(requestId.Value, []);
8381
value.Add(requestLog);
8482
}
8583
}

dev-proxy/ProxyEngine.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using DevProxy.Abstractions;
66
using Microsoft.VisualStudio.Threading;
7+
using System.Collections.Concurrent;
78
using System.Diagnostics;
89
using System.Net;
910
using System.Security.Cryptography.X509Certificates;
@@ -38,7 +39,7 @@ public class ProxyEngine(IProxyConfiguration config, ISet<UrlToWatch> urlsToWatc
3839
private readonly IProxyState _proxyState = proxyState ?? throw new ArgumentNullException(nameof(proxyState));
3940
// Dictionary for plugins to store data between requests
4041
// the key is HashObject of the SessionEventArgs object
41-
private readonly Dictionary<int, Dictionary<string, object>> _pluginData = [];
42+
private readonly ConcurrentDictionary<int, Dictionary<string, object>> _pluginData = [];
4243
private InactivityTimer? _inactivityTimer;
4344

4445
public static X509Certificate2? Certificate => _proxyServer?.CertificateManager.RootCertificate;
@@ -469,7 +470,10 @@ async Task OnRequestAsync(object sender, SessionEventArgs e)
469470
if (IsProxiedHost(e.HttpClient.Request.RequestUri.Host) &&
470471
IsIncludedByHeaders(e.HttpClient.Request.Headers))
471472
{
472-
_pluginData.Add(e.GetHashCode(), []);
473+
if (!_pluginData.TryAdd(e.GetHashCode(), []))
474+
{
475+
throw new Exception($"Unable to initialize the plugin data storage for hash key {e.GetHashCode()}");
476+
}
473477
var responseState = new ResponseState();
474478
var proxyRequestArgs = new ProxyRequestArgs(e, responseState)
475479
{
@@ -607,7 +611,7 @@ async Task OnAfterResponseAsync(object sender, SessionEventArgs e)
607611
if (!proxyResponseArgs.HasRequestUrlMatch(_urlsToWatch))
608612
{
609613
// clean up
610-
_pluginData.Remove(e.GetHashCode());
614+
_pluginData.Remove(e.GetHashCode(), out _);
611615
return;
612616
}
613617

@@ -623,7 +627,7 @@ async Task OnAfterResponseAsync(object sender, SessionEventArgs e)
623627
_logger.LogRequest(message, MessageType.FinishedProcessingRequest, new LoggingContext(e));
624628

625629
// clean up
626-
_pluginData.Remove(e.GetHashCode());
630+
_pluginData.Remove(e.GetHashCode(), out _);
627631
}
628632
}
629633

0 commit comments

Comments
 (0)