Skip to content

Commit 9adbf50

Browse files
Merge branch 'main' into v210
2 parents 49287cb + e9d21a1 commit 9adbf50

22 files changed

+344
-35
lines changed

DevProxy.Abstractions/Plugins/BaseLoader.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public abstract class BaseLoader(HttpClient httpClient, ILogger logger, IProxyCo
2121

2222
private FileSystemWatcher? _watcher;
2323
private Timer? _debounceTimer;
24-
private bool _isDisposed;
24+
private volatile bool _isDisposed;
2525

2626
protected abstract string FilePath { get; }
2727
protected ILogger Logger { get; } = logger;
@@ -65,13 +65,14 @@ protected virtual void Dispose(bool disposing)
6565
return;
6666
}
6767

68+
// Set flag first to prevent in-flight timer callbacks from proceeding
69+
_isDisposed = true;
70+
6871
if (disposing)
6972
{
7073
_watcher?.Dispose();
7174
_debounceTimer?.Dispose();
7275
}
73-
74-
_isDisposed = true;
7576
}
7677

7778
private async Task<bool> ValidateFileContentsAsync(string fileContents, CancellationToken cancellationToken)
@@ -104,6 +105,11 @@ private async Task<bool> ValidateFileContentsAsync(string fileContents, Cancella
104105

105106
private async Task LoadFileContentsAsync(CancellationToken cancellationToken)
106107
{
108+
if (_isDisposed)
109+
{
110+
return;
111+
}
112+
107113
if (!File.Exists(FilePath))
108114
{
109115
Logger.LogWarning("File {File} not found. No data will be loaded", FilePath);
@@ -129,6 +135,12 @@ private async Task LoadFileContentsAsync(CancellationToken cancellationToken)
129135

130136
private void File_Changed(object sender, FileSystemEventArgs e)
131137
{
138+
// Don't process file changes if this loader has been disposed
139+
if (_isDisposed)
140+
{
141+
return;
142+
}
143+
132144
lock (_debounceLock)
133145
{
134146
_debounceTimer?.Dispose();

DevProxy.Abstractions/Plugins/BasePlugin.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.CommandLine;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Globalization;
78
using System.Text.Json;
89
using DevProxy.Abstractions.Proxy;
@@ -15,7 +16,7 @@ namespace DevProxy.Abstractions.Plugins;
1516

1617
public abstract class BasePlugin(
1718
ILogger logger,
18-
ISet<UrlToWatch> urlsToWatch) : IPlugin
19+
ISet<UrlToWatch> urlsToWatch) : IPlugin, IDisposable
1920
{
2021
public bool Enabled { get; protected set; } = true;
2122
protected ILogger Logger { get; } = logger;
@@ -64,6 +65,17 @@ public virtual Task MockRequestAsync(EventArgs e, CancellationToken cancellation
6465
{
6566
return Task.CompletedTask;
6667
}
68+
69+
protected virtual void Dispose(bool disposing)
70+
{
71+
// Override in derived classes to dispose managed resources
72+
}
73+
74+
public void Dispose()
75+
{
76+
Dispose(true);
77+
GC.SuppressFinalize(this);
78+
}
6779
}
6880

6981
public abstract class BasePlugin<TConfiguration>(
@@ -74,27 +86,30 @@ public abstract class BasePlugin<TConfiguration>(
7486
IConfigurationSection pluginConfigurationSection) :
7587
BasePlugin(logger, urlsToWatch), IPlugin<TConfiguration> where TConfiguration : new()
7688
{
77-
private TConfiguration? _configuration;
89+
#pragma warning disable CA2213 // HttpClient is injected from DI and should not be disposed by this class
7890
private readonly HttpClient _httpClient = httpClient;
91+
#pragma warning restore CA2213
7992

8093
protected IProxyConfiguration ProxyConfiguration { get; } = proxyConfiguration;
94+
95+
[AllowNull]
8196
public TConfiguration Configuration
8297
{
8398
get
8499
{
85-
if (_configuration is null)
100+
if (field is null)
86101
{
87102
if (!ConfigurationSection.Exists())
88103
{
89-
_configuration = new();
104+
field = new();
90105
}
91106
else
92107
{
93-
_configuration = ConfigurationSection.Get<TConfiguration>();
108+
field = ConfigurationSection.Get<TConfiguration>();
94109
}
95110
}
96111

97-
return _configuration!;
112+
return field!;
98113
}
99114
}
100115
public IConfigurationSection ConfigurationSection { get; } = pluginConfigurationSection;

DevProxy.Plugins/Behavior/GenericRandomErrorPlugin.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,13 @@ private static bool HasMatchingBody(GenericErrorResponse errorResponse, Request
345345

346346
// throttle requests per host
347347
private static string BuildThrottleKey(Request r) => r.RequestUri.Host;
348+
349+
protected override void Dispose(bool disposing)
350+
{
351+
if (disposing)
352+
{
353+
_loader?.Dispose();
354+
}
355+
base.Dispose(disposing);
356+
}
348357
}

DevProxy.Plugins/Behavior/LanguageModelRateLimitingPlugin.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,4 +348,13 @@ private void ThrottleResponse(ProxyRequestArgs e)
348348
}
349349

350350
private static string BuildThrottleKey(Request r) => r.RequestUri.Host;
351+
352+
protected override void Dispose(bool disposing)
353+
{
354+
if (disposing)
355+
{
356+
_loader?.Dispose();
357+
}
358+
base.Dispose(disposing);
359+
}
351360
}

DevProxy.Plugins/Behavior/RateLimitingPlugin.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,4 +337,13 @@ private static string BuildThrottleKey(Request r)
337337
return r.RequestUri.Host;
338338
}
339339
}
340+
341+
protected override void Dispose(bool disposing)
342+
{
343+
if (disposing)
344+
{
345+
_loader?.Dispose();
346+
}
347+
base.Dispose(disposing);
348+
}
340349
}

DevProxy.Plugins/Generation/ApiCenterOnboardingPlugin.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,4 +402,13 @@ async Task ImportApiDefinitionAsync(string apiDefinitionId, string openApiSpecFi
402402
Logger.LogError("Failed to import API definition for {ApiDefinition}. Status: {Status}, reason: {Reason}", apiDefinitionId, res.StatusCode, resContent);
403403
}
404404
}
405+
406+
protected override void Dispose(bool disposing)
407+
{
408+
if (disposing)
409+
{
410+
_apiCenterClient?.Dispose();
411+
}
412+
base.Dispose(disposing);
413+
}
405414
}

DevProxy.Plugins/Guidance/GraphSelectGuidancePlugin.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ public sealed class GraphSelectGuidancePlugin(
1717
ISet<UrlToWatch> urlsToWatch,
1818
MSGraphDb msGraphDb) : BasePlugin(logger, urlsToWatch)
1919
{
20+
#pragma warning disable CA2213 // MSGraphDb is DI-injected singleton, disposal is managed by the DI container
2021
private readonly MSGraphDb _msGraphDb = msGraphDb;
22+
#pragma warning restore CA2213
2123

2224
public override string Name => nameof(GraphSelectGuidancePlugin);
2325

@@ -112,4 +114,5 @@ private static string GetTokenizedUrl(string absoluteUrl)
112114
var sanitizedUrl = ProxyUtils.SanitizeUrl(absoluteUrl);
113115
return "/" + string.Join("", new Uri(sanitizedUrl).Segments.Skip(2).Select(Uri.UnescapeDataString));
114116
}
117+
115118
}

DevProxy.Plugins/Inspection/DevToolsPlugin.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public sealed class DevToolsPlugin(
5050
logger,
5151
urlsToWatch,
5252
proxyConfiguration,
53-
pluginConfigurationSection), IDisposable
53+
pluginConfigurationSection)
5454
{
5555
private readonly Dictionary<string, GetResponseBodyResultParams> _responseBody = [];
5656

@@ -471,8 +471,12 @@ private static bool IsTextResponse(string? contentType)
471471
return isTextResponse;
472472
}
473473

474-
public void Dispose()
474+
protected override void Dispose(bool disposing)
475475
{
476-
_webSocket?.Dispose();
476+
if (disposing)
477+
{
478+
_webSocket?.Dispose();
479+
}
480+
base.Dispose(disposing);
477481
}
478482
}

DevProxy.Plugins/Inspection/OpenAITelemetryPlugin.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public sealed class OpenAITelemetryPlugin(
5353
logger,
5454
urlsToWatch,
5555
proxyConfiguration,
56-
pluginConfigurationSection), IDisposable
56+
pluginConfigurationSection)
5757
{
5858
private const string ActivitySourceName = "DevProxy.OpenAI";
5959
private const string OpenAISystem = "openai";
@@ -1008,11 +1008,15 @@ private static string GetOperationName(OpenAIRequest request)
10081008
};
10091009
}
10101010

1011-
public void Dispose()
1011+
protected override void Dispose(bool disposing)
10121012
{
1013-
_loader?.Dispose();
1014-
_activitySource?.Dispose();
1015-
_tracerProvider?.Dispose();
1016-
_meterProvider?.Dispose();
1013+
if (disposing)
1014+
{
1015+
_loader?.Dispose();
1016+
_activitySource?.Dispose();
1017+
_tracerProvider?.Dispose();
1018+
_meterProvider?.Dispose();
1019+
}
1020+
base.Dispose(disposing);
10171021
}
10181022
}

DevProxy.Plugins/Manipulation/RewritePlugin.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,13 @@ public override Task BeforeRequestAsync(ProxyRequestArgs e, CancellationToken ca
104104
Logger.LogTrace("Left {Name}", nameof(BeforeRequestAsync));
105105
return Task.CompletedTask;
106106
}
107+
108+
protected override void Dispose(bool disposing)
109+
{
110+
if (disposing)
111+
{
112+
_loader?.Dispose();
113+
}
114+
base.Dispose(disposing);
115+
}
107116
}

0 commit comments

Comments
 (0)