Skip to content
3 changes: 2 additions & 1 deletion LiteDB/Client/Shared/SharedEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using LiteDB.Client.Shared;
using LiteDB.Vector;

namespace LiteDB
Expand All @@ -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
{
Expand Down
92 changes: 92 additions & 0 deletions LiteDB/Client/Shared/SharedMutexNameFactory.cs
Original file line number Diff line number Diff line change
@@ -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;
Comment on lines +31 to +43

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fallback ignores mutex name length limits on Unix

The default mutex name builder only switches to a SHA‑1 hash when the URI-encoded path exceeds 250 characters and the runtime is Windows. However SharedMutexFactory.Create passes the name to new Mutex(false, fullName) on every platform, and the POSIX implementation also rejects names longer than 251 bytes. With a moderately long database path (≈90+ characters after encoding the / separators) the Linux/OSX runtime will throw ArgumentException: name too long, making SharedEngine unusable for that file. The previous SHA‑1 approach always produced a short, safe name. Consider applying the length check on all platforms or hashing when the encoded value exceeds the POSIX limit as well.

Useful? React with 👍 / 👎.

}

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();
}
}
5 changes: 5 additions & 0 deletions LiteDB/Engine/EngineSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public class EngineSettings
/// Is used to transform a <see cref="BsonValue"/> from the database on read. This can be used to upgrade data from older versions.
/// </summary>
public Func<string, BsonValue, BsonValue> ReadTransform { get; set; }

/// <summary>
/// Determines how the mutex name is generated.
/// </summary>
public SharedMutexNameStrategy SharedMutexNameStrategy { get; set; }

/// <summary>
/// Create new IStreamFactory for datafile
Expand Down
9 changes: 9 additions & 0 deletions LiteDB/Engine/SharedMutexNameStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

namespace LiteDB.Engine;

public enum SharedMutexNameStrategy
{
Default,
UriEscape,
Sha1Hash
}
18 changes: 0 additions & 18 deletions LiteDB/Utils/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

/// <summary>
/// Implement SqlLike in C# string - based on
/// https://stackoverflow.com/a/8583383/3286260
Expand Down
Loading