Skip to content

Commit 8ed60f9

Browse files
authored
Code Quality: Pin WinRT server object to prevent object reference being invalid (#15324)
1 parent d1853cb commit 8ed60f9

File tree

5 files changed

+80
-6
lines changed

5 files changed

+80
-6
lines changed

src/Files.App.Server/AppInstanceMonitor.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// Copyright (c) 2024 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4+
using System.Collections.Concurrent;
45
using System.Diagnostics;
56

67
namespace Files.App.Server;
78

89
public sealed class AppInstanceMonitor
910
{
1011
private static int processCount = 0;
12+
internal static ConcurrentDictionary<int, ConcurrentBag<IDisposable>> AppInstanceResources = new();
1113

1214
public static void StartMonitor(int processId)
1315
{
@@ -19,10 +21,18 @@ public static void StartMonitor(int processId)
1921

2022
private static void Process_Exited(object? sender, EventArgs e)
2123
{
22-
if (sender is Process process)
24+
if (sender is Process { Id: var processId } process)
2325
{
2426
process.Dispose();
2527

28+
if (AppInstanceResources.TryRemove(processId, out var instances))
29+
{
30+
foreach (var instance in instances)
31+
{
32+
instance.Dispose();
33+
}
34+
}
35+
2636
if (Interlocked.Decrement(ref processCount) == 0)
2737
{
2838
Program.ExitSignalEvent.Set();

src/Files.App.Server/Database/FileTagsDatabase.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using LiteDB;
77
using Microsoft.Win32;
88
using System.Runtime.CompilerServices;
9+
using System.Runtime.InteropServices;
910
using System.Runtime.InteropServices.WindowsRuntime;
1011
using System.Text;
1112
using Windows.ApplicationModel;
@@ -17,13 +18,16 @@
1718

1819
namespace Files.App.Server.Database
1920
{
20-
public sealed class FileTagsDatabase
21+
public sealed class FileTagsDatabase : IDisposable
2122
{
2223
private readonly static string FileTagsKey = @$"Software\Files Community\{Package.Current.Id.FullName}\v1\FileTags";
2324

2425
private readonly static string FileTagsDbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "filetags.db");
2526
private const string FileTagsCollectionName = "taggedfiles";
2627

28+
private readonly GCHandle _handle;
29+
private bool _disposed = false;
30+
2731
static FileTagsDatabase()
2832
{
2933
if (File.Exists(FileTagsDbPath))
@@ -44,6 +48,25 @@ static FileTagsDatabase()
4448
}
4549
}
4650

51+
public FileTagsDatabase()
52+
{
53+
throw new NotSupportedException($"Instantiating {nameof(FileTagsDatabase)} by non-parameterized constructor is not supported.");
54+
}
55+
56+
public FileTagsDatabase(int processId)
57+
{
58+
_handle = GCHandle.Alloc(this, GCHandleType.Pinned);
59+
60+
if (AppInstanceMonitor.AppInstanceResources.TryGetValue(processId, out var instances))
61+
{
62+
instances.Add(this);
63+
}
64+
else
65+
{
66+
AppInstanceMonitor.AppInstanceResources[processId] = [this];
67+
}
68+
}
69+
4770
private static void UpdateDb(LiteDatabase database)
4871
{
4972
if (database.UserVersion == 0)
@@ -279,5 +302,14 @@ private void IterateKeys(List<TaggedFile> list, string path, int depth)
279302
IterateKeys(list, CombineKeys(path, subKey), depth + 1);
280303
}
281304
}
305+
306+
public void Dispose()
307+
{
308+
if (!_disposed)
309+
{
310+
_disposed = true;
311+
_handle.Free();
312+
}
313+
}
282314
}
283315
}

src/Files.App.Server/Database/LayoutPreferencesDatabase.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using LiteDB;
66
using Microsoft.Win32;
77
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
89
using Windows.ApplicationModel;
910
using Windows.Storage;
1011
using static Files.App.Server.Data.LayoutPreferencesRegistry;
@@ -13,13 +14,16 @@
1314

1415
namespace Files.App.Server.Database
1516
{
16-
public sealed class LayoutPreferencesDatabase
17+
public sealed class LayoutPreferencesDatabase : IDisposable
1718
{
1819
private readonly static string LayoutSettingsKey = @$"Software\Files Community\{Package.Current.Id.FullName}\v1\LayoutPreferences";
1920

2021
private readonly static string LayoutSettingsDbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "user_settings.db");
2122
private const string LayoutSettingsCollectionName = "layoutprefs";
2223

24+
private readonly GCHandle _handle;
25+
private bool _disposed = false;
26+
2327
static LayoutPreferencesDatabase()
2428
{
2529
if (File.Exists(LayoutSettingsDbPath))
@@ -37,6 +41,25 @@ static LayoutPreferencesDatabase()
3741
}
3842
}
3943

44+
public LayoutPreferencesDatabase()
45+
{
46+
throw new NotSupportedException($"Instantiating {nameof(LayoutPreferencesDatabase)} by non-parameterized constructor is not supported.");
47+
}
48+
49+
public LayoutPreferencesDatabase(int processId)
50+
{
51+
_handle = GCHandle.Alloc(this, GCHandleType.Pinned);
52+
53+
if (AppInstanceMonitor.AppInstanceResources.TryGetValue(processId, out var instances))
54+
{
55+
instances.Add(this);
56+
}
57+
else
58+
{
59+
AppInstanceMonitor.AppInstanceResources[processId] = [this];
60+
}
61+
}
62+
4063
public LayoutPreferencesItem? GetPreferences(string filePath, ulong? frn)
4164
{
4265
return FindPreferences(filePath, frn)?.LayoutPreferencesManager;
@@ -197,5 +220,14 @@ private void IterateKeys(List<LayoutPreferences> list, string path, int depth)
197220

198221
return null;
199222
}
223+
224+
public void Dispose()
225+
{
226+
if (!_disposed)
227+
{
228+
_disposed = true;
229+
_handle.Free();
230+
}
231+
}
200232
}
201233
}

src/Files.App/Helpers/Layout/LayoutPreferencesDatabaseManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Files.App.Helpers
1111
public class LayoutPreferencesDatabaseManager
1212
{
1313
// Fields
14-
private readonly Server.Database.LayoutPreferencesDatabase _database = new();
14+
private readonly Server.Database.LayoutPreferencesDatabase _database = new(Environment.ProcessId);
1515

1616
private DetailsLayoutColumnItem FromDatabaseEntity(Server.Data.ColumnPreferencesItem entry)
1717
{

src/Files.App/Utils/FileTags/FileTagsHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ namespace Files.App.Utils.FileTags
1212
{
1313
public static class FileTagsHelper
1414
{
15-
private static readonly Lazy<Server.Database.FileTagsDatabase> dbInstance = new(() => new());
15+
private static readonly Server.Database.FileTagsDatabase dbInstance = new(Environment.ProcessId);
1616

17-
public static Server.Database.FileTagsDatabase GetDbInstance() => dbInstance.Value;
17+
public static Server.Database.FileTagsDatabase GetDbInstance() => dbInstance;
1818

1919
public static string[] ReadFileTag(string filePath)
2020
{

0 commit comments

Comments
 (0)