Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory Leaks #14348

Merged
merged 69 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
40a87ee
TenantHttpClientFactory
jtkech Sep 17, 2023
18bc9d5
Missing updates
jtkech Sep 17, 2023
3a2abd4
Minor change
jtkech Sep 17, 2023
8be30d0
wip and tests
jtkech Sep 21, 2023
82cfb84
wip
jtkech Sep 21, 2023
61311d6
Minor changes
jtkech Sep 21, 2023
0e54ea0
tweaks
jtkech Sep 22, 2023
5a84b51
Shared Compiler even if no Runtime Compilation.
jtkech Sep 22, 2023
e1dcf4c
Prevent one loading of View Descriptors
jtkech Sep 23, 2023
05bff5d
Minor change
jtkech Sep 23, 2023
d69ce87
Cache views feature once
jtkech Sep 23, 2023
d067461
Revert some changes from the original by using nullable context.
jtkech Sep 23, 2023
89c93d7
Load compiled items once and prevent duplicate razor strings.
jtkech Sep 23, 2023
016254e
Revert one change now quite useless
jtkech Sep 24, 2023
952d48b
Minor tweaks and cleanups
jtkech Sep 24, 2023
f7fc2ae
Minor tweaks
jtkech Sep 24, 2023
907ecf2
tweaks
jtkech Sep 25, 2023
096b129
tweaks
jtkech Sep 25, 2023
0f0be7e
Minor tweaks
jtkech Sep 26, 2023
5f25913
Useless file and add folder
jtkech Sep 26, 2023
6e3e1bd
Don't hold a released shell one minute in the background
jtkech Sep 26, 2023
3a770fc
tweaks
jtkech Sep 26, 2023
5b25b59
Tweak
jtkech Sep 26, 2023
c700b53
tweaks
jtkech Sep 26, 2023
4fc8709
Tweaks
jtkech Sep 27, 2023
90bad7f
Merge remote-tracking branch 'origin/main' into jtkech/http-client-fa…
jtkech Sep 27, 2023
ac40c91
tweaks
jtkech Sep 27, 2023
7595f57
Fix unit tests
jtkech Sep 27, 2023
bfc5b8d
Call CreateChildContainer() only once, and other testing tweaks
jtkech Sep 28, 2023
b92477b
tweaks
jtkech Sep 29, 2023
b616ed7
tweaks
jtkech Sep 29, 2023
9638683
tweaks
jtkech Sep 29, 2023
a54d754
Simplification and tweaks
jtkech Sep 30, 2023
da04b80
Minor changes
jtkech Sep 30, 2023
34a6a02
tweaks
jtkech Sep 30, 2023
236dc2a
tweaks
jtkech Oct 1, 2023
0be99fd
Renaming
jtkech Oct 1, 2023
b8bca19
tweaks
jtkech Oct 1, 2023
af3b458
Revert testing setting
jtkech Oct 1, 2023
0611012
Comment change
jtkech Oct 2, 2023
b059478
Minor changes
jtkech Oct 2, 2023
2b726de
Tweaks
jtkech Oct 2, 2023
3af09be
Simplification
jtkech Oct 2, 2023
312e3bf
Simplification
jtkech Oct 2, 2023
2e34f56
wip
jtkech Oct 3, 2023
d5a188f
Include RecaptchaClient on demand.
jtkech Oct 3, 2023
8aa9802
tweaks
jtkech Oct 4, 2023
1e9fb95
Minor change
jtkech Oct 4, 2023
13e1c6a
Tweaks
jtkech Oct 4, 2023
6faf9d7
Last tweaks I hope ;)
jtkech Oct 4, 2023
044b3db
Simplification
jtkech Oct 4, 2023
207789c
tweaks
jtkech Oct 5, 2023
f0b931d
Minor change
jtkech Oct 5, 2023
f4f628f
Merge remote-tracking branch 'origin/main' into jtkech/http-client-fa…
jtkech Oct 5, 2023
5db680d
Last tweaks
jtkech Oct 5, 2023
75d06c3
Last tweak
jtkech Oct 6, 2023
8c15410
Missing JsonIgnore
jtkech Oct 6, 2023
45735a1
Merge remote-tracking branch 'origin/main' into jtkech/http-client-fa…
jtkech Oct 6, 2023
7de45f5
tweaks
jtkech Oct 7, 2023
3585fe5
tweaks
jtkech Oct 7, 2023
16f93c2
Minor renaming
jtkech Oct 8, 2023
eb65593
tweaks
jtkech Oct 9, 2023
62f9ab2
Comment change
jtkech Oct 9, 2023
9d406dd
Check DistributedShellMarkerService
jtkech Oct 9, 2023
aaf4fc9
Revert "Check DistributedShellMarkerService"
jtkech Oct 9, 2023
bb801c0
wip
jtkech Oct 10, 2023
aa09edd
Renaming and tweaks
jtkech Oct 11, 2023
414da7d
Minor change
jtkech Oct 11, 2023
653be9f
Fix build
jtkech Oct 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.Extensions.Hosting;
using OrchardCore.Mvc;

namespace OrchardCore.Admin
{
internal class AdminPageRouteModelProvider : IPageRouteModelProvider
{
private const string RazorPageDocumentKind = "mvc.1.0.razor-page";

private readonly IHostEnvironment _hostingEnvironment;
private readonly ApplicationPartManager _applicationManager;

Expand Down Expand Up @@ -75,7 +74,8 @@ public void OnProvidersExecuted(PageRouteModelProviderContext context)
{
}

private static IEnumerable<CompiledViewDescriptor> GetPageDescriptors<T>(ApplicationPartManager applicationManager) where T : ViewsFeature, new()
private static IEnumerable<CompiledViewDescriptor> GetPageDescriptors<T>(ApplicationPartManager applicationManager)
where T : ViewsFeature, new()
{
if (applicationManager == null)
{
Expand All @@ -100,7 +100,8 @@ public void OnProvidersExecuted(PageRouteModelProviderContext context)
}
}

private static bool IsRazorPage(CompiledViewDescriptor viewDescriptor) => viewDescriptor.Item?.Kind == RazorPageDocumentKind;
private static bool IsRazorPage(CompiledViewDescriptor viewDescriptor) =>
viewDescriptor.Item?.Kind == RazorPageDocumentClassifierPass.RazorPageDocumentKind;

private static T GetViewFeature<T>(ApplicationPartManager applicationManager) where T : ViewsFeature, new()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ public async Task InvokeAsync(HttpContext httpContext)
}

// Check if the tenant was installed by another instance.
var settings = await _shellSettingsManager.LoadSettingsAsync(_shellSettings.Name);
using var settings = (await _shellSettingsManager
.LoadSettingsAsync(_shellSettings.Name))
.AsDisposable();

if (!settings.IsUninitialized())
{
await _shellHost.ReloadShellContextAsync(_shellSettings, eventSource: false);
Expand Down Expand Up @@ -204,9 +207,10 @@ public async Task<bool> SetupTenantAsync(ISetupService setupService, TenantSetup
/// <returns>The <see cref="ShellSettings"/>.</returns>
public async Task<ShellSettings> CreateTenantSettingsAsync(TenantSetupOptions setupOptions)
{
var shellSettings = _shellSettingsManager
using var shellSettings = _shellSettingsManager
.CreateDefaultSettings()
.AsUninitialized();
.AsUninitialized()
.AsDisposable();

shellSettings.Name = setupOptions.ShellName;
shellSettings.RequestUrlHost = setupOptions.RequestUrlHost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,10 @@ public async Task<IActionResult> Create()
var recipes = recipeCollections.SelectMany(x => x).Where(x => x.IsSetupRecipe).OrderBy(r => r.DisplayName).ToArray();

// Creates a default shell settings based on the configuration.
var shellSettings = _shellSettingsManager.CreateDefaultSettings();
using var shellSettings = _shellSettingsManager
.CreateDefaultSettings()
.AsUninitialized()
.AsDisposable();

var currentFeatureProfiles = shellSettings.GetFeatureProfiles();
var featureProfiles = await GetFeatureProfilesAsync(currentFeatureProfiles);
Expand Down Expand Up @@ -348,9 +351,10 @@ public async Task<IActionResult> Create(EditTenantViewModel model)
if (ModelState.IsValid)
{
// Creates a default shell settings based on the configuration.
var shellSettings = _shellSettingsManager
using var shellSettings = _shellSettingsManager
.CreateDefaultSettings()
.AsUninitialized();
.AsUninitialized()
.AsDisposable();

shellSettings.Name = model.Name;
shellSettings.RequestUrlHost = model.RequestUrlHost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ public async Task<IActionResult> Create(TenantApiModel model)
if (model.IsNewTenant)
{
// Creates a default shell settings based on the configuration.
var shellSettings = _shellSettingsManager
using var shellSettings = _shellSettingsManager
.CreateDefaultSettings()
.AsUninitialized();
.AsUninitialized()
.AsDisposable();

shellSettings.Name = model.Name;
shellSettings.RequestUrlHost = model.RequestUrlHost;
Expand All @@ -124,14 +125,15 @@ public async Task<IActionResult> Create(TenantApiModel model)
shellSettings["FeatureProfile"] = string.Join(',', model.FeatureProfiles ?? Array.Empty<string>());

await _shellHost.UpdateShellSettingsAsync(shellSettings);
var reloadedSettings = _shellHost.GetSettings(shellSettings.Name);

var token = CreateSetupToken(shellSettings);
var token = CreateSetupToken(reloadedSettings);

return Ok(GetEncodedUrl(shellSettings, token));
return Ok(GetEncodedUrl(reloadedSettings, token));
}
else
{
// Site already exists, return 201 for indempotency purposes.
// Site already exists, return 201 for idempotency purposes.

var token = CreateSetupToken(settings);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -82,7 +81,11 @@ public async Task<IEnumerable<ModelError>> ValidateAsync(TenantModelBase model)
if (existingShellSettings is null)
{
// Set the settings to be validated.
shellSettings = _shellSettingsManager.CreateDefaultSettings();
shellSettings = _shellSettingsManager
.CreateDefaultSettings()
.AsUninitialized()
.AsDisposable();

shellSettings.Name = model.Name;
}
else if (existingShellSettings.IsDefaultShell())
Expand All @@ -106,6 +109,9 @@ public async Task<IEnumerable<ModelError>> ValidateAsync(TenantModelBase model)

if (shellSettings is not null)
{
// A newly loaded settings from the configuration should be disposed.
using var disposable = existingShellSettings is null ? shellSettings : null;

var validationContext = new DbConnectionValidatorContext(shellSettings, model);
await ValidateConnectionAsync(validationContext, errors);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ public async override Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecuti
var featureProfile = (await ExpressionEvaluator.EvaluateAsync(FeatureProfile, workflowContext, null))?.Trim();

// Creates a default shell settings based on the configuration.
var shellSettings = ShellSettingsManager.CreateDefaultSettings();
using var shellSettings = ShellSettingsManager
.CreateDefaultSettings()
.AsUninitialized()
.AsDisposable();

shellSettings.Name = tenantName;

Expand Down Expand Up @@ -172,9 +175,10 @@ public async override Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecuti
shellSettings["Secret"] = Guid.NewGuid().ToString();

await ShellHost.UpdateShellSettingsAsync(shellSettings);
var reloadedSettings = ShellHost.GetSettings(shellSettings.Name);

workflowContext.LastResult = shellSettings;
workflowContext.CorrelationId = shellSettings.Name;
workflowContext.LastResult = reloadedSettings;
workflowContext.CorrelationId = reloadedSettings.Name;

return Outcomes("Done");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,24 @@ namespace OrchardCore.Environment.Shell.Builders
/// </summary>
public class ShellContext : IDisposable, IAsyncDisposable
{
private bool _disposed;
private List<WeakReference<ShellContext>> _dependents;
private readonly SemaphoreSlim _semaphore = new(1);
private bool _disposed;

internal volatile int _refCount;
internal volatile int _terminated;
internal bool _released;

/// <summary>
/// Initializes a new <see cref="ShellContext"/>.
/// </summary>
public ShellContext() => UtcTicks = DateTime.UtcNow.Ticks;

/// <summary>
/// The creation date and time of this shell context in ticks.
/// </summary>
public long UtcTicks { get; }

/// <summary>
/// The <see cref="ShellSettings"/> holding the tenant settings and configuration.
/// </summary>
Expand Down Expand Up @@ -56,7 +66,6 @@ public class PlaceHolder : ShellContext
public PlaceHolder()
{
_released = true;
_disposed = true;
}

/// <summary>
Expand Down Expand Up @@ -101,7 +110,12 @@ public async Task<ShellScope> CreateScopeAsync()
public int ActiveScopes => _refCount;

/// <summary>
/// Mark the <see cref="ShellContext"/> as released and then a candidate to be disposed.
/// Whether or not this instance uses shared <see cref="Settings"/> that should not be disposed.
/// </summary>
public bool SharedSettings { get; internal set; }

/// <summary>
/// Marks the <see cref="ShellContext"/> as released and then a candidate to be disposed.
/// </summary>
public Task ReleaseAsync() => ReleaseInternalAsync();

Expand All @@ -111,6 +125,14 @@ public async Task<ShellScope> CreateScopeAsync()

internal async Task ReleaseInternalAsync(ReleaseMode mode = ReleaseMode.Normal)
{
// A 'PlaceHolder' is always released.
if (this is PlaceHolder)
{
// But still try to dispose the settings.
await DisposeAsync();
return;
}

if (_released)
{
// Prevent infinite loops with circular dependencies
Expand Down Expand Up @@ -239,7 +261,6 @@ private void Close()

_disposed = true;

// Disposes all the services registered for this shell.
if (ServiceProvider is IDisposable disposable)
{
disposable.Dispose();
Expand All @@ -257,7 +278,6 @@ private async ValueTask CloseAsync()

_disposed = true;

// Disposes all the services registered for this shell.
if (ServiceProvider is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
Expand All @@ -276,6 +296,15 @@ private void Terminate()
IsActivated = false;
Blueprint = null;
Pipeline = null;

_semaphore?.Dispose();

if (SharedSettings)
{
return;
}

Settings?.Dispose();
}

~ShellContext() => Close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;

namespace OrchardCore.Environment.Shell.Configuration.Internal
{
internal class UpdatableDataProvider : IConfigurationProvider, IEnumerable<KeyValuePair<string, string>>
internal class UpdatableDataProvider : IConfigurationProvider, IConfigurationSource, IEnumerable<KeyValuePair<string, string>>
{
private ConfigurationReloadToken _reloadToken = new();
public UpdatableDataProvider() => Data = new(StringComparer.OrdinalIgnoreCase);

public UpdatableDataProvider(IEnumerable<KeyValuePair<string, string>> initialData)
{
Data = new ConcurrentDictionary<string, string>(initialData, StringComparer.OrdinalIgnoreCase);
initialData ??= Enumerable.Empty<KeyValuePair<string, string>>();
Data = new(initialData, StringComparer.OrdinalIgnoreCase);
}

protected IDictionary<string, string> Data { get; set; }

public void Add(string key, string value) => Data.Add(key, value);
protected ConcurrentDictionary<string, string> Data { get; set; }

public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => Data.GetEnumerator();

Expand Down Expand Up @@ -51,17 +50,10 @@ private static string Segment(string key, int prefixLength)
return indexOf < 0 ? key[prefixLength..] : key[prefixLength..indexOf];
}

public IChangeToken GetReloadToken()
{
return _reloadToken;
}

protected void OnReload()
{
var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
previousToken.OnReload();
}
public IChangeToken GetReloadToken() => NullChangeToken.Singleton;

public override string ToString() => $"{GetType().Name}";

public IConfigurationProvider Build(IConfigurationBuilder builder) => this;
}
}
Loading