diff --git a/LiteDB/Client/Shared/SharedEngine.cs b/LiteDB/Client/Shared/SharedEngine.cs
index 0e4cb72c5..1b7bb0b0c 100644
--- a/LiteDB/Client/Shared/SharedEngine.cs
+++ b/LiteDB/Client/Shared/SharedEngine.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
+using LiteDB.Client.Shared;
using LiteDB.Vector;
namespace LiteDB
@@ -18,7 +19,7 @@ public SharedEngine(EngineSettings settings)
{
_settings = settings;
- var name = Path.GetFullPath(settings.Filename).ToLower().Sha1();
+ var name = SharedMutexNameFactory.Create(settings.Filename, settings.SharedMutexNameStrategy);
try
{
diff --git a/LiteDB/Client/Shared/SharedMutexNameFactory.cs b/LiteDB/Client/Shared/SharedMutexNameFactory.cs
new file mode 100644
index 000000000..8f0198bb0
--- /dev/null
+++ b/LiteDB/Client/Shared/SharedMutexNameFactory.cs
@@ -0,0 +1,92 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+using LiteDB.Engine;
+
+namespace LiteDB.Client.Shared;
+
+internal static class SharedMutexNameFactory
+{
+ // Effective Windows limit for named mutexes (conservative).
+ private const int WINDOWS_MUTEX_NAME_MAX = 250;
+
+ // If the caller adds "Global\" (7 chars) + the name + ".Mutex" (7 chars) to the mutex name,
+ // we account for it conservatively here without baking it into the return value.
+ // Adjust if your caller prepends something longer.
+ private const int CONSERVATIVE_EXTERNAL_PREFIX_LENGTH = 13; // e.g., "Global\\" + name + ".Mutex"
+
+ internal static string Create(string fileName, SharedMutexNameStrategy strategy)
+ {
+ return strategy switch
+ {
+ SharedMutexNameStrategy.Default => CreateUsingUriEncodingWithFallback(fileName),
+ SharedMutexNameStrategy.UriEscape => CreateUsingUriEncoding(fileName),
+ SharedMutexNameStrategy.Sha1Hash => CreateUsingSha1(fileName),
+ _ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, null)
+ };
+ }
+
+ private static string CreateUsingUriEncodingWithFallback(string fileName)
+ {
+ var normalized = Normalize(fileName);
+ var uri = Uri.EscapeDataString(normalized);
+
+ if (IsWindows() &&
+ uri.Length + CONSERVATIVE_EXTERNAL_PREFIX_LENGTH > WINDOWS_MUTEX_NAME_MAX)
+ {
+ // Short, stable fallback well under the limit.
+ return "sha1-" + ComputeSha1Hex(normalized);
+ }
+
+ return uri;
+ }
+
+ private static string CreateUsingUriEncoding(string fileName)
+ {
+ var normalized = Normalize(fileName);
+ var uri = Uri.EscapeDataString(normalized);
+
+ if (IsWindows() &&
+ uri.Length + CONSERVATIVE_EXTERNAL_PREFIX_LENGTH > WINDOWS_MUTEX_NAME_MAX)
+ {
+ // Fallback to SHA to avoid ArgumentException on Windows.
+ return "sha1-" + ComputeSha1Hex(normalized);
+ }
+
+ return uri;
+ }
+
+ private static bool IsWindows()
+ {
+ return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ }
+
+ internal static string CreateUsingSha1(string value)
+ {
+ var normalized = Normalize(value);
+ return ComputeSha1Hex(normalized);
+ }
+
+ private static string Normalize(string path)
+ {
+ // Invariant casing + absolute path yields stable identity.
+ return Path.GetFullPath(path).ToLowerInvariant();
+ }
+
+ private static string ComputeSha1Hex(string input)
+ {
+ var data = Encoding.UTF8.GetBytes(input);
+ using var sha = SHA1.Create();
+ var hashData = sha.ComputeHash(data);
+
+ var sb = new StringBuilder(hashData.Length * 2);
+ foreach (var b in hashData)
+ {
+ sb.Append(b.ToString("X2"));
+ }
+
+ return sb.ToString();
+ }
+}
diff --git a/LiteDB/Engine/EngineSettings.cs b/LiteDB/Engine/EngineSettings.cs
index e78034ab2..8400c7f63 100644
--- a/LiteDB/Engine/EngineSettings.cs
+++ b/LiteDB/Engine/EngineSettings.cs
@@ -71,6 +71,11 @@ public class EngineSettings
/// Is used to transform a from the database on read. This can be used to upgrade data from older versions.
///
public Func ReadTransform { get; set; }
+
+ ///
+ /// Determines how the mutex name is generated.
+ ///
+ public SharedMutexNameStrategy SharedMutexNameStrategy { get; set; }
///
/// Create new IStreamFactory for datafile
diff --git a/LiteDB/Engine/SharedMutexNameStrategy.cs b/LiteDB/Engine/SharedMutexNameStrategy.cs
new file mode 100644
index 000000000..4683264ec
--- /dev/null
+++ b/LiteDB/Engine/SharedMutexNameStrategy.cs
@@ -0,0 +1,9 @@
+
+namespace LiteDB.Engine;
+
+public enum SharedMutexNameStrategy
+{
+ Default,
+ UriEscape,
+ Sha1Hash
+}
\ No newline at end of file
diff --git a/LiteDB/Utils/Extensions/StringExtensions.cs b/LiteDB/Utils/Extensions/StringExtensions.cs
index 00ed9b54c..c34afebf8 100644
--- a/LiteDB/Utils/Extensions/StringExtensions.cs
+++ b/LiteDB/Utils/Extensions/StringExtensions.cs
@@ -37,24 +37,6 @@ public static bool IsWord(this string str)
return true;
}
- public static string Sha1(this string value)
- {
- var data = Encoding.UTF8.GetBytes(value);
-
- using (var sha = SHA1.Create())
- {
- var hashData = sha.ComputeHash(data);
- var hash = new StringBuilder();
-
- foreach (var b in hashData)
- {
- hash.Append(b.ToString("X2"));
- }
-
- return hash.ToString();
- }
- }
-
///
/// Implement SqlLike in C# string - based on
/// https://stackoverflow.com/a/8583383/3286260